github.com/oam-dev/kubevela@v1.9.11/pkg/workflow/providers/multicluster/multicluster_test.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package multicluster
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/require"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    29  
    30  	"github.com/kubevela/workflow/pkg/cue/model/value"
    31  	"github.com/kubevela/workflow/pkg/mock"
    32  	clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
    33  	clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
    34  
    35  	apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    36  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    37  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    38  	"github.com/oam-dev/kubevela/apis/types"
    39  	"github.com/oam-dev/kubevela/pkg/multicluster"
    40  	"github.com/oam-dev/kubevela/pkg/utils/common"
    41  )
    42  
    43  func TestMakePlacementDecisions(t *testing.T) {
    44  	multicluster.ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS
    45  	testCases := []struct {
    46  		InputVal        map[string]interface{}
    47  		OldCluster      string
    48  		OldNamespace    string
    49  		ExpectError     string
    50  		ExpectCluster   string
    51  		ExpectNamespace string
    52  		PreAddCluster   string
    53  	}{{
    54  		InputVal:    map[string]interface{}{},
    55  		ExpectError: "var(path=inputs.policyName) not exist",
    56  	}, {
    57  		InputVal: map[string]interface{}{
    58  			"policyName": "example-policy",
    59  		},
    60  		ExpectError: "var(path=inputs.envName) not exist",
    61  	}, {
    62  		InputVal: map[string]interface{}{
    63  			"policyName": "example-policy",
    64  			"envName":    "example-env",
    65  		},
    66  		ExpectError: "var(path=inputs.placement) not exist",
    67  	}, {
    68  		InputVal: map[string]interface{}{
    69  			"policyName": "example-policy",
    70  			"envName":    "example-env",
    71  			"placement":  "example-placement",
    72  		},
    73  		ExpectError: "failed to parse placement while making placement decision",
    74  	}, {
    75  		InputVal: map[string]interface{}{
    76  			"policyName": "example-policy",
    77  			"envName":    "example-env",
    78  			"placement": map[string]interface{}{
    79  				"namespaceSelector": map[string]interface{}{
    80  					"labels": map[string]string{"key": "value"},
    81  				},
    82  			},
    83  		},
    84  		ExpectError: "namespace selector in cluster-gateway does not support label selector for now",
    85  	}, {
    86  		InputVal: map[string]interface{}{
    87  			"policyName": "example-policy",
    88  			"envName":    "example-env",
    89  			"placement": map[string]interface{}{
    90  				"clusterSelector": map[string]interface{}{
    91  					"labels": map[string]string{"key": "value"},
    92  				},
    93  			},
    94  		},
    95  		ExpectError: "cluster selector does not support label selector for now",
    96  	}, {
    97  		InputVal: map[string]interface{}{
    98  			"policyName": "example-policy",
    99  			"envName":    "example-env",
   100  			"placement":  map[string]interface{}{},
   101  		},
   102  		ExpectError:     "",
   103  		ExpectCluster:   "local",
   104  		ExpectNamespace: "",
   105  	}, {
   106  		InputVal: map[string]interface{}{
   107  			"policyName": "example-policy",
   108  			"envName":    "example-env",
   109  			"placement": map[string]interface{}{
   110  				"clusterSelector": map[string]interface{}{
   111  					"name": "example-cluster",
   112  				},
   113  				"namespaceSelector": map[string]interface{}{
   114  					"name": "example-namespace",
   115  				},
   116  			},
   117  		},
   118  		ExpectError: "failed to get cluster",
   119  	}, {
   120  		InputVal: map[string]interface{}{
   121  			"policyName": "example-policy",
   122  			"envName":    "example-env",
   123  			"placement": map[string]interface{}{
   124  				"clusterSelector": map[string]interface{}{
   125  					"name": "example-cluster",
   126  				},
   127  				"namespaceSelector": map[string]interface{}{
   128  					"name": "example-namespace",
   129  				},
   130  			},
   131  		},
   132  		ExpectError:     "",
   133  		ExpectCluster:   "example-cluster",
   134  		ExpectNamespace: "example-namespace",
   135  		PreAddCluster:   "example-cluster",
   136  	}, {
   137  		InputVal: map[string]interface{}{
   138  			"policyName": "example-policy",
   139  			"envName":    "example-env",
   140  			"placement": map[string]interface{}{
   141  				"clusterSelector": map[string]interface{}{
   142  					"name": "example-cluster",
   143  				},
   144  				"namespaceSelector": map[string]interface{}{
   145  					"name": "example-namespace",
   146  				},
   147  			},
   148  		},
   149  		OldCluster:      "old-cluster",
   150  		OldNamespace:    "old-namespace",
   151  		ExpectError:     "",
   152  		ExpectCluster:   "example-cluster",
   153  		ExpectNamespace: "example-namespace",
   154  		PreAddCluster:   "example-cluster",
   155  	}, {
   156  		InputVal: map[string]interface{}{
   157  			"policyName": "example-policy",
   158  			"envName":    "example-env",
   159  			"placement": map[string]interface{}{
   160  				"clusterSelector": map[string]interface{}{
   161  					"name": "example-cluster",
   162  				},
   163  				"namespaceSelector": map[string]interface{}{
   164  					"name": "example-namespace",
   165  				},
   166  			},
   167  		},
   168  		ExpectError:     "",
   169  		ExpectCluster:   "example-cluster",
   170  		ExpectNamespace: "example-namespace",
   171  		PreAddCluster:   "example-cluster",
   172  	}}
   173  
   174  	r := require.New(t)
   175  	for _, testCase := range testCases {
   176  		cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
   177  		app := &v1beta1.Application{}
   178  		p := &provider{
   179  			Client: cli,
   180  			app:    app,
   181  		}
   182  		act := &mock.Action{}
   183  		v, err := value.NewValue("", nil, "")
   184  		r.NoError(err)
   185  		r.NoError(v.FillObject(testCase.InputVal, "inputs"))
   186  		if testCase.PreAddCluster != "" {
   187  			r.NoError(cli.Create(context.Background(), &corev1.Secret{
   188  				ObjectMeta: metav1.ObjectMeta{
   189  					Namespace: multicluster.ClusterGatewaySecretNamespace,
   190  					Name:      testCase.PreAddCluster,
   191  					Labels:    map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)},
   192  				},
   193  			}))
   194  		}
   195  		if testCase.OldNamespace != "" || testCase.OldCluster != "" {
   196  			pd := v1alpha1.PlacementDecision{
   197  				Cluster:   testCase.OldNamespace,
   198  				Namespace: testCase.OldCluster,
   199  			}
   200  			bs, err := json.Marshal(&v1alpha1.EnvBindingStatus{
   201  				Envs: []v1alpha1.EnvStatus{{
   202  					Env:        "example-env",
   203  					Placements: []v1alpha1.PlacementDecision{pd},
   204  				}},
   205  			})
   206  			r.NoError(err)
   207  			app.Status.PolicyStatus = []apicommon.PolicyStatus{{
   208  				Name:   "example-policy",
   209  				Type:   v1alpha1.EnvBindingPolicyType,
   210  				Status: &runtime.RawExtension{Raw: bs},
   211  			}}
   212  		}
   213  		err = p.MakePlacementDecisions(nil, nil, v, act)
   214  		if testCase.ExpectError == "" {
   215  			r.NoError(err)
   216  		} else {
   217  			r.Contains(err.Error(), testCase.ExpectError)
   218  			continue
   219  		}
   220  		outputs, err := v.LookupValue("outputs")
   221  		r.NoError(err)
   222  		md := map[string][]v1alpha1.PlacementDecision{}
   223  		r.NoError(outputs.UnmarshalTo(&md))
   224  		r.Equal(1, len(md["decisions"]))
   225  		r.Equal(testCase.ExpectCluster, md["decisions"][0].Cluster)
   226  		r.Equal(testCase.ExpectNamespace, md["decisions"][0].Namespace)
   227  		r.Equal(1, len(app.Status.PolicyStatus))
   228  		r.Equal(testCase.InputVal["policyName"], app.Status.PolicyStatus[0].Name)
   229  		r.Equal(v1alpha1.EnvBindingPolicyType, app.Status.PolicyStatus[0].Type)
   230  		status := &v1alpha1.EnvBindingStatus{}
   231  		r.NoError(json.Unmarshal(app.Status.PolicyStatus[0].Status.Raw, status))
   232  		r.Equal(1, len(status.Envs))
   233  		r.Equal(testCase.InputVal["envName"], status.Envs[0].Env)
   234  		r.Equal(1, len(status.Envs[0].Placements))
   235  		r.Equal(testCase.ExpectNamespace, status.Envs[0].Placements[0].Namespace)
   236  		r.Equal(testCase.ExpectCluster, status.Envs[0].Placements[0].Cluster)
   237  	}
   238  }
   239  
   240  func TestPatchApplication(t *testing.T) {
   241  	baseApp := &v1beta1.Application{Spec: v1beta1.ApplicationSpec{
   242  		Components: []apicommon.ApplicationComponent{{
   243  			Name:       "comp-1",
   244  			Type:       "webservice",
   245  			Properties: &runtime.RawExtension{Raw: []byte(`{"image":"base"}`)},
   246  		}, {
   247  			Name:       "comp-3",
   248  			Type:       "webservice",
   249  			Properties: &runtime.RawExtension{Raw: []byte(`{"image":"ext"}`)},
   250  			Traits: []apicommon.ApplicationTrait{{
   251  				Type:       "scaler",
   252  				Properties: &runtime.RawExtension{Raw: []byte(`{"replicas":3}`)},
   253  			}, {
   254  				Type:       "env",
   255  				Properties: &runtime.RawExtension{Raw: []byte(`{"env":{"key":"value"}}`)},
   256  			}, {
   257  				Type:       "labels",
   258  				Properties: &runtime.RawExtension{Raw: []byte(`{"lKey":"lVal"}`)},
   259  			}},
   260  		}},
   261  	}}
   262  	testCases := []struct {
   263  		InputVal         map[string]interface{}
   264  		ExpectError      string
   265  		ExpectComponents []apicommon.ApplicationComponent
   266  	}{{
   267  		InputVal:    map[string]interface{}{},
   268  		ExpectError: "var(path=inputs.envName) not exist",
   269  	}, {
   270  		InputVal: map[string]interface{}{
   271  			"envName": "example-env",
   272  		},
   273  		ExpectComponents: baseApp.Spec.Components,
   274  	}, {
   275  		InputVal: map[string]interface{}{
   276  			"envName": "example-env",
   277  			"patch":   "bad patch",
   278  		},
   279  		ExpectError: "failed to unmarshal patch for env",
   280  	}, {
   281  		InputVal: map[string]interface{}{
   282  			"envName":  "example-env",
   283  			"selector": "bad selector",
   284  		},
   285  		ExpectError: "failed to unmarshal selector for env",
   286  	}, {
   287  		InputVal: map[string]interface{}{
   288  			"envName": "example-env",
   289  			"patch": map[string]interface{}{
   290  				"components": []map[string]interface{}{{
   291  					"name": "comp-0",
   292  					"type": "webservice",
   293  				}, {
   294  					"name": "comp-1",
   295  					"type": "worker",
   296  					"properties": map[string]interface{}{
   297  						"image": "patch",
   298  						"port":  8080,
   299  					},
   300  				}, {
   301  					"name": "comp-3",
   302  					"type": "webservice",
   303  					"properties": map[string]interface{}{
   304  						"image": "patch",
   305  						"port":  8090,
   306  					},
   307  					"traits": []map[string]interface{}{{
   308  						"type":       "scaler",
   309  						"properties": map[string]interface{}{"replicas": 5},
   310  					}, {
   311  						"type":       "env",
   312  						"properties": map[string]interface{}{"env": map[string]string{"Key": "Value"}},
   313  					}, {
   314  						"type":       "annotations",
   315  						"properties": map[string]interface{}{"aKey": "aVal"}},
   316  					},
   317  				}, {
   318  					"name": "comp-4",
   319  					"type": "webservice",
   320  				}},
   321  			},
   322  			"selector": map[string]interface{}{
   323  				"components": []string{"comp-2", "comp-1", "comp-3", "comp-0"},
   324  			},
   325  		},
   326  		ExpectComponents: []apicommon.ApplicationComponent{{
   327  			Name:       "comp-1",
   328  			Type:       "worker",
   329  			Properties: &runtime.RawExtension{Raw: []byte(`{"image":"patch","port":8080}`)},
   330  		}, {
   331  			Name:       "comp-3",
   332  			Type:       "webservice",
   333  			Properties: &runtime.RawExtension{Raw: []byte(`{"image":"patch","port":8090}`)},
   334  			Traits: []apicommon.ApplicationTrait{{
   335  				Type:       "scaler",
   336  				Properties: &runtime.RawExtension{Raw: []byte(`{"replicas":5}`)},
   337  			}, {
   338  				Type:       "env",
   339  				Properties: &runtime.RawExtension{Raw: []byte(`{"env":{"Key":"Value","key":"value"}}`)},
   340  			}, {
   341  				Type:       "labels",
   342  				Properties: &runtime.RawExtension{Raw: []byte(`{"lKey":"lVal"}`)},
   343  			}, {
   344  				Type:       "annotations",
   345  				Properties: &runtime.RawExtension{Raw: []byte(`{"aKey":"aVal"}`)},
   346  			}},
   347  		}, {
   348  			Name: "comp-0",
   349  			Type: "webservice",
   350  		}},
   351  	}}
   352  	r := require.New(t)
   353  	for _, testCase := range testCases {
   354  		cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
   355  		p := &provider{
   356  			Client: cli,
   357  			app:    baseApp,
   358  		}
   359  		act := &mock.Action{}
   360  		v, err := value.NewValue("", nil, "")
   361  		r.NoError(err)
   362  		r.NoError(v.FillObject(testCase.InputVal, "inputs"))
   363  		err = p.PatchApplication(nil, nil, v, act)
   364  		if testCase.ExpectError == "" {
   365  			r.NoError(err)
   366  		} else {
   367  			r.Contains(err.Error(), testCase.ExpectError)
   368  			continue
   369  		}
   370  		outputs, err := v.LookupValue("outputs")
   371  		r.NoError(err)
   372  		patchApp := &v1beta1.Application{}
   373  		r.NoError(outputs.UnmarshalTo(patchApp))
   374  		r.Equal(len(testCase.ExpectComponents), len(patchApp.Spec.Components))
   375  		for idx, comp := range testCase.ExpectComponents {
   376  			_comp := patchApp.Spec.Components[idx]
   377  			r.Equal(comp.Name, _comp.Name)
   378  			r.Equal(comp.Type, _comp.Type)
   379  			if comp.Properties == nil {
   380  				r.Equal(comp.Properties, _comp.Properties)
   381  			} else {
   382  				r.Equal(string(comp.Properties.Raw), string(_comp.Properties.Raw))
   383  			}
   384  			r.Equal(len(comp.Traits), len(_comp.Traits))
   385  			for _idx, trait := range comp.Traits {
   386  				_trait := _comp.Traits[_idx]
   387  				r.Equal(trait.Type, _trait.Type)
   388  				if trait.Properties == nil {
   389  					r.Equal(trait.Properties, _trait.Properties)
   390  				} else {
   391  					r.Equal(string(trait.Properties.Raw), string(_trait.Properties.Raw))
   392  				}
   393  			}
   394  		}
   395  	}
   396  }
   397  
   398  func TestListClusters(t *testing.T) {
   399  	multicluster.ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS
   400  	r := require.New(t)
   401  	cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
   402  	clusterNames := []string{"cluster-a", "cluster-b"}
   403  	for _, secretName := range clusterNames {
   404  		secret := &corev1.Secret{}
   405  		secret.Name = secretName
   406  		secret.Namespace = multicluster.ClusterGatewaySecretNamespace
   407  		secret.Labels = map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)}
   408  		r.NoError(cli.Create(context.Background(), secret))
   409  	}
   410  	app := &v1beta1.Application{}
   411  	p := &provider{
   412  		Client: cli,
   413  		app:    app,
   414  	}
   415  	act := &mock.Action{}
   416  	v, err := value.NewValue("", nil, "")
   417  	r.NoError(err)
   418  	r.NoError(p.ListClusters(nil, nil, v, act))
   419  	outputs, err := v.LookupValue("outputs")
   420  	r.NoError(err)
   421  	obj := struct {
   422  		Clusters []string `json:"clusters"`
   423  	}{}
   424  	r.NoError(outputs.UnmarshalTo(&obj))
   425  	r.Equal(clusterNames, obj.Clusters)
   426  }