From 6af9994ca7c3a303d6492a09257c4385b4aa753b Mon Sep 17 00:00:00 2001 From: Mohamed Awnallah Date: Thu, 28 Nov 2024 20:20:15 +0200 Subject: [PATCH] pkg/karmadactl: unit test promote In this commit, we unti test promoting a namespace-scoped or cluster-scoped resource in a legacy clsuter and validating the promote options on karmadactl command. Signed-off-by: Mohamed Awnallah --- pkg/karmadactl/promote/promote.go | 65 ++-- pkg/karmadactl/promote/promote_test.go | 511 +++++++++++++++++++++++++ 2 files changed, 550 insertions(+), 26 deletions(-) create mode 100644 pkg/karmadactl/promote/promote_test.go diff --git a/pkg/karmadactl/promote/promote.go b/pkg/karmadactl/promote/promote.go index 2cb99167c..d03eca099 100644 --- a/pkg/karmadactl/promote/promote.go +++ b/pkg/karmadactl/promote/promote.go @@ -18,6 +18,7 @@ package promote import ( "context" + "errors" "fmt" "os" @@ -93,6 +94,18 @@ var ( %[1]s promote deployment nginx -n default -C cluster1 --cluster-kubeconfig= --cluster-context=`) ) +var ( + dynamicClientBuilder = func(cfg *rest.Config) dynamic.Interface { + return dynamic.NewForConfigOrDie(cfg) + } + kubeClientBuilder = func(cfg *rest.Config) kubeclientset.Interface { + return kubeclientset.NewForConfigOrDie(cfg) + } + karmadaClientBuilder = func(cfg *rest.Config) karmadaclientset.Interface { + return karmadaclientset.NewForConfigOrDie(cfg) + } +) + // NewCmdPromote defines the `promote` command that promote resources from legacy clusters func NewCmdPromote(f util.Factory, parentCommand string) *cobra.Command { opts := CommandPromoteOption{} @@ -197,7 +210,7 @@ func (o *CommandPromoteOption) Complete(f util.Factory, args []string) error { var err error if len(args) != 2 { - return fmt.Errorf("incorrect command format, please use correct command format") + return errors.New("incorrect command format, please use correct command format") } o.name = args[1] @@ -235,11 +248,11 @@ func (o *CommandPromoteOption) Complete(f util.Factory, args []string) error { // Validate checks to the PromoteOptions to see if there is sufficient information run the command func (o *CommandPromoteOption) Validate() error { if o.Cluster == "" { - return fmt.Errorf("the cluster cannot be empty") + return errors.New("the cluster cannot be empty") } if o.OutputFormat != "" && o.OutputFormat != "yaml" && o.OutputFormat != "json" { - return fmt.Errorf("output format is only one of json and yaml") + return errors.New("invalid output format: supported formats are json and yaml") } return nil @@ -294,11 +307,11 @@ func (o *CommandPromoteOption) Run(f util.Factory, args []string) error { } } - return o.promote(controlPlaneRestConfig, obj, gvr, o.Deps) + return o.promote(controlPlaneRestConfig, obj, gvr) } // revertPromotedDeps reverts promoted dependencies of the resource -func (o *CommandPromoteOption) revertPromotedDeps(memberClusterFactory cmdutil.Factory, dependencies []configv1alpha1.DependentObjectReference, mapper meta.RESTMapper, controlPlaneDynamicClient *dynamic.DynamicClient, index int) { +func (o *CommandPromoteOption) revertPromotedDeps(memberClusterFactory cmdutil.Factory, dependencies []configv1alpha1.DependentObjectReference, mapper meta.RESTMapper, controlPlaneDynamicClient dynamic.Interface, index int) { memberDynamicClient, err := memberClusterFactory.DynamicClient() if err != nil { klog.Errorf("revertPromotedDeps failed to get dynamic client of member cluster: %v", err) @@ -364,7 +377,7 @@ func (o *CommandPromoteOption) revertPromotedDeps(memberClusterFactory cmdutil.F // doPromoteDeps promotes dependencies of the resource func (o *CommandPromoteOption) doPromoteDeps(memberClusterFactory cmdutil.Factory, dependencies []configv1alpha1.DependentObjectReference, mapper meta.RESTMapper, config *rest.Config) error { - controlPlaneDynamicClient := dynamic.NewForConfigOrDie(config) + controlPlaneDynamicClient := dynamicClientBuilder(config) for i, dep := range dependencies { depInfo, err := o.getObjInfo(memberClusterFactory, o.Cluster, []string{dep.Kind, dep.Name}) if err != nil { @@ -441,17 +454,17 @@ func (o *CommandPromoteOption) promoteDeps(memberClusterFactory cmdutil.Factory, // create resource interpreter stopCh := make(chan struct{}) defer close(stopCh) - dynamicClientSet := dynamic.NewForConfigOrDie(config) + dynamicClientSet := dynamicClientBuilder(config) controlPlaneInformerManager := genericmanager.NewSingleClusterInformerManager(dynamicClientSet, 0, stopCh) - controlPlaneKubeClientSet := kubeclientset.NewForConfigOrDie(config) + controlPlaneKubeClientSet := kubeClientBuilder(config) sharedFactory := informers.NewSharedInformerFactory(controlPlaneKubeClientSet, 0) serviceLister := sharedFactory.Core().V1().Services().Lister() sharedFactory.Start(stopCh) sharedFactory.WaitForCacheSync(stopCh) controlPlaneInformerManager.Start() if sync := controlPlaneInformerManager.WaitForCacheSync(); sync == nil { - return fmt.Errorf("informer factory for cluster does not exist") + return errors.New("informer factory for cluster does not exist") } defaultInterpreter := native.NewDefaultInterpreter() @@ -468,7 +481,7 @@ func (o *CommandPromoteOption) promoteDeps(memberClusterFactory cmdutil.Factory, !configurableInterpreter.HookEnabled(obj.GroupVersionKind(), configv1alpha1.InterpreterOperationInterpretDependency) && !customizedInterpreter.HookEnabled(obj.GroupVersionKind(), configv1alpha1.InterpreterOperationInterpretDependency) { // if there is no interpreter configured, abort this dependency promotion but continue to promote the resource - klog.Warningf("there is no interpreter configured support to interpret dependencies") + klog.Warning("there is no interpreter configured support to interpret dependencies") return nil } @@ -478,7 +491,7 @@ func (o *CommandPromoteOption) promoteDeps(memberClusterFactory cmdutil.Factory, return fmt.Errorf("configurable interpreter failed to get dependencies of resource %q(%s/%s): %v", gvr, obj.GetNamespace(), obj.GetName(), err) } if hookEnabled { - fmt.Printf("use configurable interpreter\n") + fmt.Println("use configurable interpreter") err := o.doPromoteDeps(memberClusterFactory, dependencies, mapper, config) return err } @@ -492,7 +505,7 @@ func (o *CommandPromoteOption) promoteDeps(memberClusterFactory cmdutil.Factory, return fmt.Errorf("customized interpreter failed to get dependencies of resource %q(%s/%s): %v", gvr, obj.GetNamespace(), obj.GetName(), err) } if hookEnabled { - fmt.Printf("use customized interpreter\n") + fmt.Println("use customized interpreter") err := o.doPromoteDeps(memberClusterFactory, dependencies, mapper, config) return err } @@ -500,10 +513,10 @@ func (o *CommandPromoteOption) promoteDeps(memberClusterFactory cmdutil.Factory, // thirdparty interpreter has higher priority than default interpreter dependencies, hookEnabled, err = thirdpartyInterpreter.GetDependencies(obj) if err != nil { - return fmt.Errorf("thirdparty interpreter failed to get dependencies of resource %q(%s/%s): %v", gvr, obj.GetNamespace(), obj.GetName(), err) + return fmt.Errorf("third-party interpreter failed to get dependencies of resource %q(%s/%s): %v", gvr, obj.GetNamespace(), obj.GetName(), err) } if hookEnabled { - fmt.Printf("use thirdparty interpreter\n") + fmt.Println("use third-party interpreter") err := o.doPromoteDeps(memberClusterFactory, dependencies, mapper, config) return err } @@ -513,12 +526,12 @@ func (o *CommandPromoteOption) promoteDeps(memberClusterFactory cmdutil.Factory, if err != nil { return fmt.Errorf("default interpreter failed to get dependencies of resource %q(%s/%s): %v", gvr, obj.GetNamespace(), obj.GetName(), err) } - fmt.Printf("use default interpreter\n") + fmt.Println("use default interpreter") err = o.doPromoteDeps(memberClusterFactory, dependencies, mapper, config) return err } -func (o *CommandPromoteOption) promote(controlPlaneRestConfig *rest.Config, obj *unstructured.Unstructured, gvr schema.GroupVersionResource, isDep bool) error { +func (o *CommandPromoteOption) promote(controlPlaneRestConfig *rest.Config, obj *unstructured.Unstructured, gvr schema.GroupVersionResource) error { if err := preprocessResource(obj); err != nil { return fmt.Errorf("failed to preprocess resource %q(%s/%s) in control plane: %v", gvr, o.Namespace, o.name, err) } @@ -534,11 +547,11 @@ func (o *CommandPromoteOption) promote(controlPlaneRestConfig *rest.Config, obj return nil } - controlPlaneDynamicClient := dynamic.NewForConfigOrDie(controlPlaneRestConfig) + controlPlaneDynamicClient := dynamicClientBuilder(controlPlaneRestConfig) + karmadaClient := karmadaClientBuilder(controlPlaneRestConfig) - karmadaClient := karmadaclientset.NewForConfigOrDie(controlPlaneRestConfig) - - if len(obj.GetNamespace()) == 0 { + isClusterScoped := len(obj.GetNamespace()) == 0 + if isClusterScoped { _, err := controlPlaneDynamicClient.Resource(gvr).Get(context.TODO(), o.name, metav1.GetOptions{}) if err == nil { klog.Warningf("Resource %q(%s) already exist in karmada control plane, you can edit PropagationPolicy and OverridePolicy to propagate it\n", @@ -557,7 +570,7 @@ func (o *CommandPromoteOption) promote(controlPlaneRestConfig *rest.Config, obj fmt.Printf("ResourceTemplate (%s/%s) is created successfully\n", o.Namespace, o.name) if o.AutoCreatePolicy { - policyName, err := o.createClusterPropagationPolicy(karmadaClient, gvr, isDep) + policyName, err := o.createClusterPropagationPolicy(karmadaClient, gvr) if err != nil { return err } @@ -584,7 +597,7 @@ func (o *CommandPromoteOption) promote(controlPlaneRestConfig *rest.Config, obj fmt.Printf("ResourceTemplate (%s/%s) is created successfully\n", o.Namespace, o.name) if o.AutoCreatePolicy { - policyName, err := o.createPropagationPolicy(karmadaClient, gvr, isDep) + policyName, err := o.createPropagationPolicy(karmadaClient, gvr) if err != nil { return err } @@ -663,7 +676,7 @@ func (o *CommandPromoteOption) printObjectAndPolicy(obj *unstructured.Unstructur } // createPropagationPolicy create PropagationPolicy in karmada control plane -func (o *CommandPromoteOption) createPropagationPolicy(karmadaClient *karmadaclientset.Clientset, gvr schema.GroupVersionResource, isDep bool) (string, error) { +func (o *CommandPromoteOption) createPropagationPolicy(karmadaClient karmadaclientset.Interface, gvr schema.GroupVersionResource) (string, error) { var policyName string if o.PolicyName == "" { policyName = names.GeneratePolicyName(o.Namespace, o.name, o.gvk.String()) @@ -673,7 +686,7 @@ func (o *CommandPromoteOption) createPropagationPolicy(karmadaClient *karmadacli _, err := karmadaClient.PolicyV1alpha1().PropagationPolicies(o.Namespace).Get(context.TODO(), policyName, metav1.GetOptions{}) if err != nil && apierrors.IsNotFound(err) { - pp := buildPropagationPolicy(o.name, policyName, o.Namespace, o.Cluster, gvr, o.gvk, isDep) + pp := buildPropagationPolicy(o.name, policyName, o.Namespace, o.Cluster, gvr, o.gvk, o.Deps) _, err = karmadaClient.PolicyV1alpha1().PropagationPolicies(o.Namespace).Create(context.TODO(), pp, metav1.CreateOptions{}) return policyName, err @@ -687,7 +700,7 @@ func (o *CommandPromoteOption) createPropagationPolicy(karmadaClient *karmadacli } // createClusterPropagationPolicy create ClusterPropagationPolicy in karmada control plane -func (o *CommandPromoteOption) createClusterPropagationPolicy(karmadaClient *karmadaclientset.Clientset, gvr schema.GroupVersionResource, isDep bool) (string, error) { +func (o *CommandPromoteOption) createClusterPropagationPolicy(karmadaClient karmadaclientset.Interface, gvr schema.GroupVersionResource) (string, error) { var policyName string if o.PolicyName == "" { policyName = names.GeneratePolicyName("", o.name, o.gvk.String()) @@ -697,7 +710,7 @@ func (o *CommandPromoteOption) createClusterPropagationPolicy(karmadaClient *kar _, err := karmadaClient.PolicyV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), policyName, metav1.GetOptions{}) if err != nil && apierrors.IsNotFound(err) { - cpp := buildClusterPropagationPolicy(o.name, policyName, o.Cluster, gvr, o.gvk, isDep) + cpp := buildClusterPropagationPolicy(o.name, policyName, o.Cluster, gvr, o.gvk, o.Deps) _, err = karmadaClient.PolicyV1alpha1().ClusterPropagationPolicies().Create(context.TODO(), cpp, metav1.CreateOptions{}) return policyName, err diff --git a/pkg/karmadactl/promote/promote_test.go b/pkg/karmadactl/promote/promote_test.go new file mode 100644 index 000000000..44d62264f --- /dev/null +++ b/pkg/karmadactl/promote/promote_test.go @@ -0,0 +1,511 @@ +/* +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 promote + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "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" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/client-go/dynamic" + fakedynamic "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/rest" + coretesting "k8s.io/client-go/testing" + + policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" + karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned" + fakekarmadaclient "github.com/karmada-io/karmada/pkg/generated/clientset/versioned/fake" + "github.com/karmada-io/karmada/pkg/generated/clientset/versioned/scheme" + "github.com/karmada-io/karmada/pkg/util/names" +) + +func TestValidatePromoteOptions(t *testing.T) { + tests := []struct { + name string + promoteOpts *CommandPromoteOption + wantErr bool + errMsg string + }{ + { + name: "Validate_WithoutCluster_ClusterCanNotBeEmpty", + promoteOpts: &CommandPromoteOption{Cluster: ""}, + wantErr: true, + errMsg: "the cluster cannot be empty", + }, + { + name: "Validate_WithXMLOutputFormat_InvalidOutputFormat", + promoteOpts: &CommandPromoteOption{ + Cluster: "member1", + OutputFormat: "xml", + }, + wantErr: true, + errMsg: "invalid output format", + }, + { + name: "Validate_WithValidOptions_PromoteOptionsValidated", + promoteOpts: &CommandPromoteOption{ + Cluster: "member1", + OutputFormat: "json", + }, + wantErr: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.promoteOpts.Validate() + if err == nil && test.wantErr { + t.Fatal("expected an error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error, got: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error()) + } + }) + } +} + +func TestPromoteResourceInLegacyCluster(t *testing.T) { + tests := []struct { + name string + promoteOpts *CommandPromoteOption + controlPlaneRestConfig *rest.Config + obj *unstructured.Unstructured + controlPlaneDynamicClient dynamic.Interface + karmadaClient karmadaclientset.Interface + gvr schema.GroupVersionResource + prep func(dynamic.Interface, karmadaclientset.Interface, schema.GroupVersionResource) error + verify func(controlPlaneDynamicClient dynamic.Interface, karmadaClient karmadaclientset.Interface, gvr schema.GroupVersionResource, namespace, resourceName, policyName string) error + wantErr bool + errMsg string + }{ + { + name: "Promote_PrintObjectAndPolicy_FailedToInitializeK8sPrinter", + promoteOpts: &CommandPromoteOption{ + name: "demo-crd", + OutputFormat: "json", + Printer: func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error) { + return nil, errors.New("unknown type in printer") + }, + }, + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{ + "name": "demo-crd", + }, + }, + }, + prep: func(dynamic.Interface, karmadaclientset.Interface, schema.GroupVersionResource) error { return nil }, + verify: func(dynamic.Interface, karmadaclientset.Interface, schema.GroupVersionResource, string, string, string) error { + return nil + }, + wantErr: true, + errMsg: "unknown type in printer", + }, + { + name: "Promote_CreateClusterScopedResourceInControlPlane_FailedToCreateResource", + promoteOpts: &CommandPromoteOption{ + name: "demo-crd", + }, + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{ + "name": "demo-crd", + }, + }, + }, + controlPlaneRestConfig: &rest.Config{}, + controlPlaneDynamicClient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme), + gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, + prep: func(controlPlaneDynamicClient dynamic.Interface, _ karmadaclientset.Interface, gvr schema.GroupVersionResource) error { + controlPlaneDynamicClient.(*fakedynamic.FakeDynamicClient).Fake.PrependReactor("create", gvr.Resource, func(coretesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("unexpected error; encountered network issue while creating resources") + }) + dynamicClientBuilder = func(*rest.Config) dynamic.Interface { + return controlPlaneDynamicClient + } + return nil + }, + verify: func(dynamic.Interface, karmadaclientset.Interface, schema.GroupVersionResource, string, string, string) error { + return nil + }, + wantErr: true, + errMsg: "encountered network issue while creating resources", + }, + { + name: "Promote_CreateClusterScopedResourceInControlPlane_ResourceCreated", + promoteOpts: &CommandPromoteOption{ + name: "demo-crd", + Cluster: "member1", + PolicyName: "demo-crd-cluster-propagationpolicy", + AutoCreatePolicy: true, + Deps: true, + }, + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{ + "name": "demo-crd", + }, + }, + }, + controlPlaneRestConfig: &rest.Config{}, + controlPlaneDynamicClient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme), + karmadaClient: fakekarmadaclient.NewSimpleClientset(), + gvr: schema.GroupVersionResource{ + Group: "apiextensions.k8s.io", + Version: "v1", + Resource: "customresourcedefinitions", + }, + prep: func(controlPlaneDynamicClient dynamic.Interface, karmadaClient karmadaclientset.Interface, _ schema.GroupVersionResource) error { + dynamicClientBuilder = func(*rest.Config) dynamic.Interface { + return controlPlaneDynamicClient + } + karmadaClientBuilder = func(*rest.Config) karmadaclientset.Interface { + return karmadaClient + } + return nil + }, + verify: func(controlPlaneDynamicClient dynamic.Interface, karmadaClient karmadaclientset.Interface, gvr schema.GroupVersionResource, _, resourceName, policyName string) error { + if _, err := controlPlaneDynamicClient.Resource(gvr).Get(context.TODO(), resourceName, metav1.GetOptions{}); err != nil { + return fmt.Errorf("failed to get resource %v, but got error: %v", resourceName, err) + } + if _, err := karmadaClient.PolicyV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), policyName, metav1.GetOptions{}); err != nil { + return fmt.Errorf("failed to get cluster propagation policy %s, got error: %v", policyName, err) + } + return nil + }, + wantErr: false, + }, + { + name: "Promote_CreateNamespaceScopedResourceInControlPlane_ResourceCreated", + promoteOpts: &CommandPromoteOption{ + name: "demo-deployment", + Cluster: "member1", + PolicyName: "demo-deployment-propagationpolicy", + Namespace: names.NamespaceDefault, + AutoCreatePolicy: true, + }, + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deploymnet", + "metadata": map[string]interface{}{ + "name": "demo-deployment", + "namespace": names.NamespaceDefault, + }, + }, + }, + controlPlaneRestConfig: &rest.Config{}, + controlPlaneDynamicClient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme), + karmadaClient: fakekarmadaclient.NewSimpleClientset(), + gvr: schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "deployments", + }, + prep: func(controlPlaneDynamicClient dynamic.Interface, karmadaClient karmadaclientset.Interface, _ schema.GroupVersionResource) error { + dynamicClientBuilder = func(*rest.Config) dynamic.Interface { + return controlPlaneDynamicClient + } + karmadaClientBuilder = func(*rest.Config) karmadaclientset.Interface { + return karmadaClient + } + return nil + }, + verify: func(controlPlaneDynamicClient dynamic.Interface, karmadaClient karmadaclientset.Interface, gvr schema.GroupVersionResource, namespace, resourceName, policyName string) error { + if _, err := controlPlaneDynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}); err != nil { + return fmt.Errorf("failed to get resource %v, but got error: %v", resourceName, err) + } + if _, err := karmadaClient.PolicyV1alpha1().PropagationPolicies(namespace).Get(context.TODO(), policyName, metav1.GetOptions{}); err != nil { + return fmt.Errorf("failed to get propagation policy %s in namespace %s, got error: %v", policyName, namespace, err) + } + return nil + }, + wantErr: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := test.prep(test.controlPlaneDynamicClient, test.karmadaClient, test.gvr); err != nil { + t.Fatalf("failed to prep test environment, got error: %v", err) + } + err := test.promoteOpts.promote(test.controlPlaneRestConfig, test.obj, test.gvr) + if err == nil && test.wantErr { + t.Fatal("expected an error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error, got: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error()) + } + if err := test.verify(test.controlPlaneDynamicClient, test.karmadaClient, test.gvr, test.obj.GetNamespace(), test.obj.GetName(), test.promoteOpts.PolicyName); err != nil { + t.Errorf("failed to verify promoting the resource %s in the legacy cluster %s, got error: %v", test.gvr.Resource, test.promoteOpts.Cluster, err) + } + }) + } +} + +func TestCreatePropagationPolicy(t *testing.T) { + tests := []struct { + name string + promoteOpts *CommandPromoteOption + client karmadaclientset.Interface + gvr schema.GroupVersionResource + prep func(karmadaclientset.Interface, *CommandPromoteOption) error + verify func(karmadaclientset.Interface, *CommandPromoteOption) error + wantErr bool + errMsg string + }{ + { + name: "CreatePropagationPolicy_PropagationPolicyAlreadyExists_ReturnTheExistingOne", + promoteOpts: &CommandPromoteOption{ + Namespace: names.NamespaceDefault, + PolicyName: "webserver-propagation", + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(client karmadaclientset.Interface, promoteOpts *CommandPromoteOption) error { + pp := &policyv1alpha1.PropagationPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: promoteOpts.PolicyName, + Namespace: promoteOpts.Namespace, + }, + } + if _, err := client.PolicyV1alpha1().PropagationPolicies(promoteOpts.Namespace).Create(context.TODO(), pp, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("failed to create propagation policy %s, got error: %v", promoteOpts.PolicyName, err) + } + return nil + }, + verify: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + wantErr: true, + errMsg: fmt.Sprintf("PropagationPolicy(%s/%s) already exist", names.NamespaceDefault, "webserver-propagation"), + }, + { + name: "CreatePropagationPolicy_GetPropagationPolicyFromK8s_GotNetworkIssue", + promoteOpts: &CommandPromoteOption{ + Namespace: names.NamespaceDefault, + PolicyName: "webserver-propagation", + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(client karmadaclientset.Interface, _ *CommandPromoteOption) error { + client.(*fakekarmadaclient.Clientset).Fake.PrependReactor("get", "propagationpolicies", func(coretesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("unexpected error: encountered a network issue while getting the propagationpolicies") + }) + return nil + }, + verify: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + wantErr: true, + errMsg: "encountered a network issue while getting the propagationpolicies", + }, + { + name: "CreatePropagationPolicy_CreatePropagationPolicy_FailedToCreatePropagationPolicy", + promoteOpts: &CommandPromoteOption{ + Namespace: names.NamespaceDefault, + PolicyName: "webserver-propagation", + Cluster: "member1", + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(client karmadaclientset.Interface, _ *CommandPromoteOption) error { + client.(*fakekarmadaclient.Clientset).Fake.PrependReactor("get", "propagationpolicies", func(coretesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("unexpected error: encountered a network issue while creating the propagationpolicies") + }) + return nil + }, + verify: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + gvr: schema.GroupVersionResource{}, + wantErr: true, + errMsg: "encountered a network issue while creating the propagationpolicies", + }, + { + name: "CreatePropagationPolicy_CreatePropagationPolicy_PropagationPolicyCreated", + promoteOpts: &CommandPromoteOption{ + name: "nginx-deployment", + Namespace: names.NamespaceDefault, + PolicyName: "webserver-propagation", + Cluster: "member1", + gvk: schema.GroupVersionKind{ + Kind: "Deployment", + }, + Deps: true, + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + verify: func(client karmadaclientset.Interface, promoteOpts *CommandPromoteOption) error { + if _, err := client.PolicyV1alpha1().PropagationPolicies(promoteOpts.Namespace).Get(context.TODO(), promoteOpts.PolicyName, metav1.GetOptions{}); err != nil { + return fmt.Errorf("failed to create propagation policy %s in namespace %s, got error: %v", promoteOpts.PolicyName, promoteOpts.Namespace, err) + } + return nil + }, + gvr: schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "deployments", + }, + wantErr: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := test.prep(test.client, test.promoteOpts); err != nil { + t.Fatalf("failed to prep test environment, got error: %v", err) + } + _, err := test.promoteOpts.createPropagationPolicy(test.client, test.gvr) + if err == nil && test.wantErr { + t.Fatal("expcted an error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error, got: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error()) + } + if err := test.verify(test.client, test.promoteOpts); err != nil { + t.Errorf("failed to verify creating propagation policy, got error: %v", err) + } + }) + } +} + +func TestCreateClusterPropagationPolicy(t *testing.T) { + tests := []struct { + name string + promoteOpts *CommandPromoteOption + client karmadaclientset.Interface + gvr schema.GroupVersionResource + prep func(karmadaclientset.Interface, *CommandPromoteOption) error + verify func(karmadaclientset.Interface, *CommandPromoteOption) error + wantErr bool + errMsg string + }{ + { + name: "CreateClusterPropagationPolicy_ClusterPropagationPolicyAlreadyExists_ReturnTheExistingOne", + promoteOpts: &CommandPromoteOption{ + PolicyName: "crd-cluster-propagation", + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(client karmadaclientset.Interface, promoteOpts *CommandPromoteOption) error { + cpp := &policyv1alpha1.ClusterPropagationPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: promoteOpts.PolicyName, + }, + } + if _, err := client.PolicyV1alpha1().ClusterPropagationPolicies().Create(context.TODO(), cpp, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("failed to create cluster propagation policy %s, got error: %v", promoteOpts.PolicyName, err) + } + return nil + }, + verify: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + wantErr: true, + errMsg: "ClusterPropagationPolicy(crd-cluster-propagation) already exist", + }, + { + name: "CreateClusterPropagationPolicy_GetClusterPropagationPolicyFromK8s_GotNetworkIssue", + promoteOpts: &CommandPromoteOption{ + PolicyName: "crd-cluster-propagation", + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(client karmadaclientset.Interface, _ *CommandPromoteOption) error { + client.(*fakekarmadaclient.Clientset).Fake.PrependReactor("get", "clusterpropagationpolicies", func(coretesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("unexpected error: encountered a network issue while getting the cluster propagationpolicies") + }) + return nil + }, + verify: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + wantErr: true, + errMsg: "encountered a network issue while getting the cluster propagationpolicies", + }, + { + name: "CreateClusterPropagationPolicy_CreateClusterPropagationPolicy_FailedToCreateClusterPropagationPolicy", + promoteOpts: &CommandPromoteOption{ + PolicyName: "crd-cluster-propagation", + Cluster: "member1", + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(client karmadaclientset.Interface, _ *CommandPromoteOption) error { + client.(*fakekarmadaclient.Clientset).Fake.PrependReactor("get", "clusterpropagationpolicies", func(coretesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("unexpected error: encountered a network issue while creating the cluster propagationpolicies") + }) + return nil + }, + verify: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + gvr: schema.GroupVersionResource{}, + wantErr: true, + errMsg: "encountered a network issue while creating the cluster propagationpolicies", + }, + { + name: "CreateClusterPropagationPolicy_CreateClusterPropagationPolicy_ClusterPropagationPolicyCreated", + promoteOpts: &CommandPromoteOption{ + name: "crd", + PolicyName: "crd-cluster-propagation", + Cluster: "member1", + gvk: schema.GroupVersionKind{ + Kind: "CustomResourceDefinition", + }, + Deps: true, + }, + client: fakekarmadaclient.NewSimpleClientset(), + prep: func(karmadaclientset.Interface, *CommandPromoteOption) error { return nil }, + verify: func(client karmadaclientset.Interface, promoteOpts *CommandPromoteOption) error { + if _, err := client.PolicyV1alpha1().ClusterPropagationPolicies().Get(context.TODO(), promoteOpts.PolicyName, metav1.GetOptions{}); err != nil { + return fmt.Errorf("failed to create propagation policy %s for the legacy cluster %s, got error: %v", promoteOpts.PolicyName, promoteOpts.Cluster, err) + } + return nil + }, + gvr: schema.GroupVersionResource{ + Group: "apiextensions.k8s.io", + Version: "v1", + Resource: "customresourcedefinitions", + }, + wantErr: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := test.prep(test.client, test.promoteOpts); err != nil { + t.Fatalf("failed to prep test environment, got error: %v", err) + } + _, err := test.promoteOpts.createClusterPropagationPolicy(test.client, test.gvr) + if err == nil && test.wantErr { + t.Fatal("expcted an error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error, got: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error()) + } + if err := test.verify(test.client, test.promoteOpts); err != nil { + t.Errorf("failed to verify creating cluster propagation policy, got error: %v", err) + } + }) + } +}