/* 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 ( "reflect" "testing" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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" "sigs.k8s.io/controller-runtime/pkg/client/fake" policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" "github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager" ) func Test_cleanPPUnmatchedRBs(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{{Group: "apps", Version: "v1"}}) deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment") restMapper.Add(deploymentGVK, meta.RESTScopeNamespace) tests := []struct { name string policyID string policyName string policyNamespace string selectors []policyv1alpha1.ResourceSelector wantErr bool setupClient func() *fake.ClientBuilder existingObject *unstructured.Unstructured expectedBindings *workv1alpha2.ResourceBindingList }{ { name: "clean unmatched binding resource with policy and namespace", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", policyNamespace: "fake-namespace-1", selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, } rb := &workv1alpha2.ResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Namespace: "fake-namespace-1", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, Annotations: map[string]string{ policyv1alpha1.PropagationPolicyNamespaceAnnotation: "deploy-match-namespace-1", policyv1alpha1.PropagationPolicyNameAnnotation: "deploy-match-name-1", }, }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj, rb).WithRESTMapper(restMapper) }, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, }, expectedBindings: &workv1alpha2.ResourceBindingList{Items: []workv1alpha2.ResourceBinding{}}, }, { name: "cannot list unmatched binding resource with policy and namespace", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-2", policyNamespace: "fake-namespace-2", selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithRESTMapper(restMapper) }, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, }, expectedBindings: nil, }, } 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.existingObject) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, RESTMapper: fakeClient.RESTMapper(), InformerManager: genMgr, } err := resourceDetector.cleanPPUnmatchedRBs(tt.policyID, tt.policyNamespace, tt.policyName, tt.selectors) if (err != nil) != tt.wantErr { t.Errorf("cleanPPUnmatchedRBs() error = %v, wantErr %v", err, tt.wantErr) } bindings, err := resourceDetector.listPPDerivedRBs(tt.policyID, tt.policyNamespace, tt.policyName) if (err != nil) != tt.wantErr { t.Errorf("listPPDerivedRBs() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(tt.expectedBindings, bindings) { t.Errorf("listPPDerivedRBs() = %v, want %v", bindings, tt.expectedBindings) } }) } } func Test_cleanUnmatchedRBs(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{{Group: "apps", Version: "v1"}}) deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment") restMapper.Add(deploymentGVK, meta.RESTScopeNamespace) tests := []struct { name string policyID string policyName string selectors []policyv1alpha1.ResourceSelector wantErr bool setupClient func() *fake.ClientBuilder existingObject *unstructured.Unstructured expectedBindings *workv1alpha2.ResourceBindingList }{ { name: "clean unmatched binding resource", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, } rb := &workv1alpha2.ResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, Annotations: map[string]string{ policyv1alpha1.ClusterPropagationPolicyAnnotation: "deploy-match-name-1", }, }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj, rb).WithRESTMapper(restMapper) }, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, }, expectedBindings: &workv1alpha2.ResourceBindingList{Items: []workv1alpha2.ResourceBinding{}}, }, { name: "cannot list unmatched binding resource", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithRESTMapper(restMapper) }, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, }, expectedBindings: nil, }, } 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.existingObject) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, RESTMapper: fakeClient.RESTMapper(), InformerManager: genMgr, } err := resourceDetector.cleanCPPUnmatchedRBs(tt.policyID, tt.policyName, tt.selectors) if (err != nil) != tt.wantErr { t.Errorf("cleanCPPUnmatchedRBs() error = %v, wantErr %v", err, tt.wantErr) } bindings, err := resourceDetector.listCPPDerivedRBs(tt.policyID, tt.policyName) if (err != nil) != tt.wantErr { t.Errorf("listCPPDerivedRBs() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(tt.expectedBindings, bindings) { t.Errorf("listCPPDerivedRBs() = %v, want %v", bindings, tt.expectedBindings) } }) } } func Test_cleanUnmatchedCRBs(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{{Group: "apps", Version: "v1"}}) deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment") restMapper.Add(deploymentGVK, meta.RESTScopeNamespace) tests := []struct { name string policyID string policyName string selectors []policyv1alpha1.ResourceSelector wantErr bool setupClient func() *fake.ClientBuilder existingObject *unstructured.Unstructured expectedBindings *workv1alpha2.ClusterResourceBindingList }{ { name: "clean unmatched cluster binding resource", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, } rb := &workv1alpha2.ClusterResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, Annotations: map[string]string{ policyv1alpha1.ClusterPropagationPolicyAnnotation: "deploy-match-name-1", }, }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj, rb).WithRESTMapper(restMapper) }, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, }, expectedBindings: &workv1alpha2.ClusterResourceBindingList{Items: []workv1alpha2.ClusterResourceBinding{}}, }, { name: "cannot list unmatched cluster binding resource", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithRESTMapper(restMapper) }, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", }, }, }, expectedBindings: nil, }, } 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.existingObject) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, RESTMapper: fakeClient.RESTMapper(), InformerManager: genMgr, } err := resourceDetector.cleanUnmatchedCRBs(tt.policyID, tt.policyName, tt.selectors) if (err != nil) != tt.wantErr { t.Errorf("cleanUnmatchedCRBs() error = %v, wantErr %v", err, tt.wantErr) } bindings, err := resourceDetector.listCPPDerivedCRBs(tt.policyID, tt.policyName) if (err != nil) != tt.wantErr { t.Errorf("listCPPDerivedCRBs() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(tt.expectedBindings, bindings) { t.Errorf("listCPPDerivedCRBs() = %v, want %v", bindings, tt.expectedBindings) } }) } } func Test_removeRBsClaimMetadata(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{{Group: "apps", Version: "v1"}}) deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment") restMapper.Add(deploymentGVK, meta.RESTScopeNamespace) tests := []struct { name string bindings *workv1alpha2.ResourceBindingList selectors []policyv1alpha1.ResourceSelector existingObject *unstructured.Unstructured removeLabels []string removeAnnotations []string wantErr bool setupClient func() *fake.ClientBuilder }{ { name: "cannot remove resource binding with matching selectors", bindings: &workv1alpha2.ResourceBindingList{ Items: []workv1alpha2.ResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, }, }, }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", }, }, removeLabels: []string{"app"}, removeAnnotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } return fake.NewClientBuilder(). WithScheme(scheme). WithObjects(obj).WithRESTMapper(restMapper) }, }, { name: "remove resource binding with non-matching selectors", bindings: &workv1alpha2.ResourceBindingList{ Items: []workv1alpha2.ResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, }, }, }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, removeLabels: []string{"app"}, removeAnnotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } rb := &workv1alpha2.ResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, } return fake.NewClientBuilder(). WithScheme(scheme). WithObjects(obj, rb).WithRESTMapper(restMapper) }, }, { name: "failed to remove resource binding with non-matching selectors", bindings: &workv1alpha2.ResourceBindingList{ Items: []workv1alpha2.ResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, }, }, }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, removeLabels: []string{"app"}, removeAnnotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithRESTMapper(restMapper) }, }, } 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.existingObject) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, RESTMapper: fakeClient.RESTMapper(), InformerManager: genMgr, } err := resourceDetector.removeRBsClaimMetadata(tt.bindings, tt.selectors, tt.removeLabels, tt.removeAnnotations) if (err != nil) != tt.wantErr { t.Errorf("removeRBsClaimMetadata() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_removeCRBsClaimMetadata(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{{Group: "apps", Version: "v1"}}) deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment") restMapper.Add(deploymentGVK, meta.RESTScopeNamespace) tests := []struct { name string bindings *workv1alpha2.ClusterResourceBindingList selectors []policyv1alpha1.ResourceSelector existingObject *unstructured.Unstructured removeLabels []string removeAnnotations []string wantErr bool setupClient func() *fake.ClientBuilder }{ { name: "cannot remove cluster resource binding with matching selectors", bindings: &workv1alpha2.ClusterResourceBindingList{ Items: []workv1alpha2.ClusterResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, }, }, }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", }, }, removeLabels: []string{"app"}, removeAnnotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } return fake.NewClientBuilder(). WithScheme(scheme). WithObjects(obj).WithRESTMapper(restMapper) }, }, { name: "remove cluster resource binding with non-matching selectors", bindings: &workv1alpha2.ClusterResourceBindingList{ Items: []workv1alpha2.ClusterResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, }, }, }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, removeLabels: []string{"app"}, removeAnnotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } crb := &workv1alpha2.ClusterResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, } return fake.NewClientBuilder(). WithScheme(scheme). WithObjects(obj, crb).WithRESTMapper(restMapper) }, }, { name: "failed to remove cluster resource binding with non-matching selectors", bindings: &workv1alpha2.ClusterResourceBindingList{ Items: []workv1alpha2.ClusterResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", }, Spec: workv1alpha2.ResourceBindingSpec{ Resource: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, }, }, }, }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, removeLabels: []string{"app"}, removeAnnotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithRESTMapper(restMapper) }, }, } 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.existingObject) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, RESTMapper: fakeClient.RESTMapper(), InformerManager: genMgr, } err := resourceDetector.removeCRBsClaimMetadata(tt.bindings, tt.selectors, tt.removeLabels, tt.removeAnnotations) if (err != nil) != tt.wantErr { t.Errorf("removeCRBsClaimMetadata() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_removeResourceClaimMetadataIfNotMatched(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{{Group: "apps", Version: "v1"}}) deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment") restMapper.Add(deploymentGVK, meta.RESTScopeNamespace) tests := []struct { name string objectReference workv1alpha2.ObjectReference selectors []policyv1alpha1.ResourceSelector labels []string annotations []string existingObject *unstructured.Unstructured wantUpdated bool wantErr bool setupClient func() *fake.ClientBuilder }{ { name: "update with non matching selectors", objectReference: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, labels: []string{"app"}, annotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantUpdated: true, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj).WithRESTMapper(restMapper) }, }, { name: "cannot update with matching selectors", objectReference: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", }, }, labels: []string{"app"}, annotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantUpdated: false, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } return fake.NewClientBuilder(). WithScheme(scheme). WithObjects(obj).WithRESTMapper(restMapper) }, }, { name: "failed to update with non matching selectors", objectReference: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Pod", Namespace: "default", }, }, labels: []string{"app"}, annotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ "name": "deployment", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantUpdated: false, wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder().WithRESTMapper(restMapper) }, }, { name: "restmapper does not contain required scheme", objectReference: workv1alpha2.ObjectReference{ APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", Name: "deployment", }, selectors: []policyv1alpha1.ResourceSelector{ { APIVersion: "apps/v1", Kind: "Deployment", Namespace: "test", }, }, labels: []string{"app"}, annotations: []string{"foo"}, existingObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Pod", "metadata": map[string]interface{}{ "name": "pod", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, }, wantUpdated: false, wantErr: false, setupClient: func() *fake.ClientBuilder { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Pod", "metadata": map[string]interface{}{ "name": "pod", "namespace": "test", "labels": map[string]interface{}{"app": "nginx"}, "annotations": map[string]interface{}{"foo": "bar"}, }, }, } return fake.NewClientBuilder(). WithScheme(scheme). WithObjects(obj).WithRESTMapper(restMapper) }, }, } 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.existingObject) genMgr := genericmanager.NewSingleClusterInformerManager(fakeDynamicClient, 0, stopCh) resourceDetector := &ResourceDetector{ Client: fakeClient, DynamicClient: fakeDynamicClient, RESTMapper: fakeClient.RESTMapper(), InformerManager: genMgr, } updated, err := resourceDetector.removeResourceClaimMetadataIfNotMatched(tt.objectReference, tt.selectors, tt.labels, tt.annotations) if (err != nil) != tt.wantErr { t.Errorf("removeResourceClaimMetadataIfNotMatched() error = %v, wantErr %v", err, tt.wantErr) } if updated != tt.wantUpdated { t.Errorf("removeResourceClaimMetadataIfNotMatched() = %v, want %v", updated, tt.wantUpdated) } }) } } func Test_listPPDerivedRBs(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) tests := []struct { name string policyID string policyName string policyNamespace string wantErr bool setupClient func() *fake.ClientBuilder expectedBindings *workv1alpha2.ResourceBindingList }{ { name: "list resource binding with policy and namespace", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", policyNamespace: "fake-namespace-1", wantErr: false, setupClient: func() *fake.ClientBuilder { rb := &workv1alpha2.ResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(rb) }, expectedBindings: &workv1alpha2.ResourceBindingList{ Items: []workv1alpha2.ResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "fake-namespace-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.PropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, }, }, }, }, }, { name: "cannot list resource binding with policy and namespace", policyID: "g5609cgb-f3f3-4a4b-b289-4512a4fef979", policyName: "test-policy-2", policyNamespace: "fake-namespace-2", wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder() }, expectedBindings: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeClient := tt.setupClient().Build() resourceDetector := &ResourceDetector{ Client: fakeClient, } bindings, err := resourceDetector.listPPDerivedRBs(tt.policyID, tt.policyNamespace, tt.policyName) if (err != nil) != tt.wantErr { t.Errorf("listPPDerivedRBs() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(tt.expectedBindings, bindings) { t.Errorf("listPPDerivedRBs() = %v, want %v", bindings, tt.expectedBindings) } }) } } func Test_listCPPDerivedRBs(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) tests := []struct { name string policyID string policyName string wantErr bool setupClient func() *fake.ClientBuilder expectedBindings *workv1alpha2.ResourceBindingList }{ { name: "list resource binding with policy", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", wantErr: false, setupClient: func() *fake.ClientBuilder { rb := &workv1alpha2.ResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(rb) }, expectedBindings: &workv1alpha2.ResourceBindingList{ Items: []workv1alpha2.ResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, }, }, }, }, }, { name: "cannot list resource binding with policy", policyID: "g5609cgb-f3f3-4a4b-b289-4512a4fef979", policyName: "test-policy-2", wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder() }, expectedBindings: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeClient := tt.setupClient().Build() resourceDetector := &ResourceDetector{ Client: fakeClient, } bindings, err := resourceDetector.listCPPDerivedRBs(tt.policyID, tt.policyName) if (err != nil) != tt.wantErr { t.Errorf("listCPPDerivedRBs() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(tt.expectedBindings, bindings) { t.Errorf("listCPPDerivedRBs() = %v, want %v", bindings, tt.expectedBindings) } }) } } func Test_listCPPDerivedCRBs(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(workv1alpha2.Install(scheme)) tests := []struct { name string policyID string policyName string wantErr bool setupClient func() *fake.ClientBuilder expectedBindings *workv1alpha2.ClusterResourceBindingList }{ { name: "list cluster binding with policy", policyID: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", policyName: "test-policy-1", wantErr: false, setupClient: func() *fake.ClientBuilder { rb := &workv1alpha2.ClusterResourceBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, }, } return fake.NewClientBuilder().WithScheme(scheme).WithObjects(rb) }, expectedBindings: &workv1alpha2.ClusterResourceBindingList{ Items: []workv1alpha2.ClusterResourceBinding{ { ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", ResourceVersion: "999", Labels: map[string]string{ policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", }, }, }, }, }, }, { name: "cannot list cluster binding with policy", policyID: "g5609cgb-f3f3-4a4b-b289-4512a4fef979", policyName: "test-policy-2", wantErr: true, setupClient: func() *fake.ClientBuilder { return fake.NewClientBuilder() }, expectedBindings: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeClient := tt.setupClient().Build() resourceDetector := &ResourceDetector{ Client: fakeClient, } bindings, err := resourceDetector.listCPPDerivedCRBs(tt.policyID, tt.policyName) if (err != nil) != tt.wantErr { t.Errorf("listCPPDerivedCRBs() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(tt.expectedBindings, bindings) { t.Errorf("listCPPDerivedCRBs() = %v, want %v", bindings, tt.expectedBindings) } }) } } func Test_excludeClusterPolicy(t *testing.T) { tests := []struct { name string obj metav1.Object result metav1.Object hasClaimedClusterPolicy bool }{ { name: "propagation policy was claimed", obj: &unstructured.Unstructured{ Object: map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{}, }, }, }, result: &unstructured.Unstructured{ Object: map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{}, }, }, }, hasClaimedClusterPolicy: false, }, { name: "propagation policy was not claimed", obj: &unstructured.Unstructured{ Object: map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "f2507cgb-f3f3-4a4b-b289-5691a4fef979", "foo": "bar"}, "annotations": map[string]interface{}{policyv1alpha1.ClusterPropagationPolicyAnnotation: "nginx", "foo1": "bar1"}, }, }, }, result: &unstructured.Unstructured{ Object: map[string]interface{}{ "metadata": map[string]interface{}{ "labels": map[string]interface{}{"foo": "bar"}, "annotations": map[string]interface{}{"foo1": "bar1"}, }, }, }, hasClaimedClusterPolicy: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := excludeClusterPolicy(tt.obj) assert.Equal(t, tt.obj, tt.result) assert.Equal(t, tt.hasClaimedClusterPolicy, got) }) } }