github.com/kubevela/workflow@v0.6.0/pkg/providers/kube/handle_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 kube
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"testing"
    24  	"time"
    25  
    26  	. "github.com/onsi/ginkgo"
    27  	. "github.com/onsi/gomega"
    28  
    29  	corev1 "k8s.io/api/core/v1"
    30  	crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    31  	"k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    37  	"k8s.io/client-go/rest"
    38  	"k8s.io/utils/pointer"
    39  	"sigs.k8s.io/controller-runtime/pkg/client"
    40  	"sigs.k8s.io/controller-runtime/pkg/envtest"
    41  	"sigs.k8s.io/yaml"
    42  
    43  	monitorContext "github.com/kubevela/pkg/monitor/context"
    44  
    45  	wfContext "github.com/kubevela/workflow/pkg/context"
    46  	"github.com/kubevela/workflow/pkg/cue/model/value"
    47  	"github.com/kubevela/workflow/pkg/cue/packages"
    48  )
    49  
    50  // These tests use Ginkgo (BDD-style Go testing framework). Refer to
    51  // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
    52  
    53  var cfg *rest.Config
    54  var k8sClient client.Client
    55  var testEnv *envtest.Environment
    56  var scheme = runtime.NewScheme()
    57  var pd *packages.PackageDiscover
    58  var p *provider
    59  
    60  func TestProvider(t *testing.T) {
    61  	RegisterFailHandler(Fail)
    62  
    63  	RunSpecs(t, "Test Definition Suite")
    64  }
    65  
    66  var _ = BeforeSuite(func(done Done) {
    67  	By("Bootstrapping test environment")
    68  	testEnv = &envtest.Environment{
    69  		ControlPlaneStartTimeout: time.Minute,
    70  		ControlPlaneStopTimeout:  time.Minute,
    71  		UseExistingCluster:       pointer.BoolPtr(false),
    72  	}
    73  	var err error
    74  	cfg, err = testEnv.Start()
    75  	Expect(err).ToNot(HaveOccurred())
    76  	Expect(cfg).ToNot(BeNil())
    77  	Expect(clientgoscheme.AddToScheme(scheme)).Should(BeNil())
    78  	Expect(crdv1.AddToScheme(scheme)).Should(BeNil())
    79  	// +kubebuilder:scaffold:scheme
    80  	By("Create the k8s client")
    81  	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
    82  	Expect(err).ToNot(HaveOccurred())
    83  	Expect(k8sClient).ToNot(BeNil())
    84  	pd, err = packages.NewPackageDiscover(cfg)
    85  	Expect(err).ToNot(HaveOccurred())
    86  
    87  	d := &dispatcher{
    88  		cli: k8sClient,
    89  	}
    90  	p = &provider{
    91  		cli: k8sClient,
    92  		handlers: Handlers{
    93  			Apply:  d.apply,
    94  			Delete: d.delete,
    95  		},
    96  		labels: map[string]string{
    97  			"hello": "world",
    98  		},
    99  	}
   100  	close(done)
   101  }, 120)
   102  
   103  var _ = AfterSuite(func() {
   104  	By("Tearing down the test environment")
   105  	err := testEnv.Stop()
   106  	Expect(err).ToNot(HaveOccurred())
   107  })
   108  
   109  var _ = Describe("Test Workflow Provider Kube", func() {
   110  	It("apply and read", func() {
   111  		ctx, err := newWorkflowContextForTest()
   112  		Expect(err).ToNot(HaveOccurred())
   113  
   114  		v, err := value.NewValue(fmt.Sprintf(`
   115  value:{
   116  	%s
   117  	metadata: name: "app"
   118  	metadata: labels: {
   119  		"test": "test"
   120  	}
   121  }
   122  cluster: ""
   123  `, componentStr), nil, "")
   124  		Expect(err).ToNot(HaveOccurred())
   125  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   126  		err = p.Apply(mCtx, ctx, v, nil)
   127  		Expect(err).ToNot(HaveOccurred())
   128  		// test patch
   129  		v, err = value.NewValue(fmt.Sprintf(`
   130  		value:{
   131  			%s
   132  			metadata: name: "app"
   133  		}
   134  		cluster: ""
   135  		`, componentStr), nil, "")
   136  		Expect(err).ToNot(HaveOccurred())
   137  		err = p.Apply(mCtx, ctx, v, nil)
   138  		Expect(err).ToNot(HaveOccurred())
   139  		workload := &corev1.Pod{}
   140  		Eventually(func() error {
   141  			return k8sClient.Get(context.Background(), client.ObjectKey{
   142  				Namespace: "default",
   143  				Name:      "app",
   144  			}, workload)
   145  		}, time.Second*2, time.Millisecond*300).Should(BeNil())
   146  		Expect(len(workload.GetLabels())).To(Equal(2))
   147  
   148  		v, err = value.NewValue(fmt.Sprintf(`
   149  value: {
   150  %s
   151  metadata: name: "app"
   152  }
   153  cluster: ""
   154  `, componentStr), nil, "")
   155  		Expect(err).ToNot(HaveOccurred())
   156  		err = p.Read(mCtx, ctx, v, nil)
   157  		Expect(err).ToNot(HaveOccurred())
   158  		result, err := v.LookupValue("value")
   159  		Expect(err).ToNot(HaveOccurred())
   160  
   161  		expected := new(unstructured.Unstructured)
   162  		ev, err := result.MakeValue(expectedCue)
   163  		Expect(err).ToNot(HaveOccurred())
   164  		err = ev.UnmarshalTo(expected)
   165  		Expect(err).ToNot(HaveOccurred())
   166  
   167  		err = result.FillObject(expected.Object)
   168  		Expect(err).ToNot(HaveOccurred())
   169  	})
   170  
   171  	It("patch & apply", func() {
   172  		ctx, err := newWorkflowContextForTest()
   173  		Expect(err).ToNot(HaveOccurred())
   174  
   175  		v, err := value.NewValue(fmt.Sprintf(`
   176  value:{
   177  	%s
   178  	metadata: name: "test-app-1"
   179  	metadata: labels: {
   180  		"test": "test"
   181  	}
   182  }
   183  cluster: ""
   184  `, componentStr), nil, "")
   185  		Expect(err).ToNot(HaveOccurred())
   186  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   187  		err = p.Apply(mCtx, ctx, v, nil)
   188  		Expect(err).ToNot(HaveOccurred())
   189  
   190  		v, err = value.NewValue(`
   191  value: {
   192  	apiVersion: "v1"
   193  	kind:       "Pod"
   194  	metadata: name: "test-app-1"
   195  }
   196  cluster: ""
   197  patch: {
   198  	metadata: name: "test-app-1"
   199  	spec: {
   200  		containers: [{
   201  			// +patchStrategy=retainKeys
   202  			image: "nginx:notfound"
   203  		}]
   204  	}
   205  }`, nil, "")
   206  		Expect(err).ToNot(HaveOccurred())
   207  		err = p.Patch(mCtx, ctx, v, nil)
   208  		Expect(err).ToNot(HaveOccurred())
   209  
   210  		pod := &corev1.Pod{}
   211  		Expect(err).ToNot(HaveOccurred())
   212  		Eventually(func() error {
   213  			return k8sClient.Get(context.Background(), client.ObjectKey{
   214  				Namespace: "default",
   215  				Name:      "test-app-1",
   216  			}, pod)
   217  		}, time.Second*2, time.Millisecond*300).Should(BeNil())
   218  		Expect(pod.Name).To(Equal("test-app-1"))
   219  		Expect(pod.Spec.Containers[0].Image).To(Equal("nginx:notfound"))
   220  	})
   221  
   222  	It("list", func() {
   223  		ctx := context.Background()
   224  		for i := 2; i >= 0; i-- {
   225  			err := k8sClient.Create(ctx, &corev1.Pod{
   226  				ObjectMeta: metav1.ObjectMeta{
   227  					Name:      fmt.Sprintf("test-%v", i),
   228  					Namespace: "default",
   229  					Labels: map[string]string{
   230  						"test":  "test",
   231  						"index": fmt.Sprintf("test-%v", i),
   232  					},
   233  				},
   234  				Spec: corev1.PodSpec{
   235  					Containers: []corev1.Container{
   236  						{
   237  							Name:  fmt.Sprintf("test-%v", i),
   238  							Image: "busybox",
   239  						},
   240  					},
   241  				},
   242  			})
   243  			Expect(err).ToNot(HaveOccurred())
   244  		}
   245  
   246  		By("List pods with labels test=test")
   247  		v, err := value.NewValue(`
   248  resource: {
   249  apiVersion: "v1"
   250  kind: "Pod"
   251  }
   252  filter: {
   253  namespace: "default"
   254  matchingLabels: {
   255  test: "test"
   256  }
   257  }
   258  cluster: ""
   259  `, nil, "")
   260  		Expect(err).ToNot(HaveOccurred())
   261  		wfCtx, err := newWorkflowContextForTest()
   262  		Expect(err).ToNot(HaveOccurred())
   263  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   264  		err = p.List(mCtx, wfCtx, v, nil)
   265  		Expect(err).ToNot(HaveOccurred())
   266  		result, err := v.LookupValue("list")
   267  		Expect(err).ToNot(HaveOccurred())
   268  		expected := &metav1.PartialObjectMetadataList{}
   269  		err = result.UnmarshalTo(expected)
   270  		Expect(err).ToNot(HaveOccurred())
   271  		Expect(len(expected.Items)).Should(Equal(4))
   272  
   273  		By("List pods with labels index=test-1")
   274  		v, err = value.NewValue(`
   275  resource: {
   276  apiVersion: "v1"
   277  kind: "Pod"
   278  }
   279  filter: {
   280  matchingLabels: {
   281  index: "test-1"
   282  }
   283  }
   284  cluster: ""
   285  `, nil, "")
   286  		Expect(err).ToNot(HaveOccurred())
   287  		err = p.List(mCtx, wfCtx, v, nil)
   288  		Expect(err).ToNot(HaveOccurred())
   289  		result, err = v.LookupValue("list")
   290  		Expect(err).ToNot(HaveOccurred())
   291  		expected = &metav1.PartialObjectMetadataList{}
   292  		err = result.UnmarshalTo(expected)
   293  		Expect(err).ToNot(HaveOccurred())
   294  		Expect(len(expected.Items)).Should(Equal(1))
   295  	})
   296  
   297  	It("delete", func() {
   298  		ctx := context.Background()
   299  		err := k8sClient.Create(ctx, &corev1.Pod{
   300  			ObjectMeta: metav1.ObjectMeta{
   301  				Name:      "test",
   302  				Namespace: "default",
   303  			},
   304  			Spec: corev1.PodSpec{
   305  				Containers: []corev1.Container{
   306  					{
   307  						Name:  "test",
   308  						Image: "busybox",
   309  					},
   310  				},
   311  			},
   312  		})
   313  		Expect(err).ToNot(HaveOccurred())
   314  		err = k8sClient.Get(ctx, types.NamespacedName{
   315  			Name:      "test",
   316  			Namespace: "default",
   317  		}, &corev1.Pod{})
   318  		Expect(err).ToNot(HaveOccurred())
   319  
   320  		v, err := value.NewValue(`
   321  value: {
   322  apiVersion: "v1"
   323  kind: "Pod"
   324  metadata: {
   325  name: "test"
   326  namespace: "default"
   327  }
   328  }
   329  cluster: ""
   330  `, nil, "")
   331  		Expect(err).ToNot(HaveOccurred())
   332  		wfCtx, err := newWorkflowContextForTest()
   333  		Expect(err).ToNot(HaveOccurred())
   334  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   335  		err = p.Delete(mCtx, wfCtx, v, nil)
   336  		Expect(err).ToNot(HaveOccurred())
   337  		err = k8sClient.Get(ctx, types.NamespacedName{
   338  			Name:      "test",
   339  			Namespace: "default",
   340  		}, &corev1.Pod{})
   341  		Expect(err).To(HaveOccurred())
   342  		Expect(errors.IsNotFound(err)).Should(Equal(true))
   343  	})
   344  
   345  	It("delete with labels", func() {
   346  		ctx := context.Background()
   347  		err := k8sClient.Create(ctx, &corev1.Pod{
   348  			ObjectMeta: metav1.ObjectMeta{
   349  				Name:      "test",
   350  				Namespace: "default",
   351  				Labels: map[string]string{
   352  					"test.oam.dev": "true",
   353  				},
   354  			},
   355  			Spec: corev1.PodSpec{
   356  				Containers: []corev1.Container{
   357  					{
   358  						Name:  "test",
   359  						Image: "busybox",
   360  					},
   361  				},
   362  			},
   363  		})
   364  		Expect(err).ToNot(HaveOccurred())
   365  		err = k8sClient.Get(ctx, types.NamespacedName{
   366  			Name:      "test",
   367  			Namespace: "default",
   368  		}, &corev1.Pod{})
   369  		Expect(err).ToNot(HaveOccurred())
   370  
   371  		v, err := value.NewValue(`
   372  value: {
   373  apiVersion: "v1"
   374  kind: "Pod"
   375  metadata: {
   376    namespace: "default"
   377  }
   378  }
   379  filter: {
   380    namespace: "default"
   381    matchingLabels: {
   382        "test.oam.dev": "true"
   383    }
   384  }
   385  cluster: ""
   386  `, nil, "")
   387  		Expect(err).ToNot(HaveOccurred())
   388  		wfCtx, err := newWorkflowContextForTest()
   389  		Expect(err).ToNot(HaveOccurred())
   390  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   391  		err = p.Delete(mCtx, wfCtx, v, nil)
   392  		Expect(err).ToNot(HaveOccurred())
   393  		err = k8sClient.Get(ctx, types.NamespacedName{
   394  			Name:      "test",
   395  			Namespace: "default",
   396  		}, &corev1.Pod{})
   397  		Expect(err).To(HaveOccurred())
   398  		Expect(errors.IsNotFound(err)).Should(Equal(true))
   399  	})
   400  
   401  	It("apply parallel", func() {
   402  		ctx, err := newWorkflowContextForTest()
   403  		Expect(err).ToNot(HaveOccurred())
   404  
   405  		v, err := value.NewValue(fmt.Sprintf(`
   406  value:[
   407    {
   408  		%s
   409  		metadata: name: "app1"
   410  	},
   411  	{
   412  		%s
   413  		metadata: name: "app1"
   414  	}
   415  ]
   416  cluster: ""
   417  `, componentStr, componentStr), nil, "")
   418  		Expect(err).ToNot(HaveOccurred())
   419  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   420  		err = p.ApplyInParallel(mCtx, ctx, v, nil)
   421  		Expect(err).ToNot(HaveOccurred())
   422  	})
   423  
   424  	It("test error case", func() {
   425  		ctx, err := newWorkflowContextForTest()
   426  		Expect(err).ToNot(HaveOccurred())
   427  
   428  		v, err := value.NewValue(`
   429  value: {
   430    kind: "Pod"
   431    apiVersion: "v1"
   432    spec: close({kind: 12})	
   433  }`, nil, "")
   434  		Expect(err).ToNot(HaveOccurred())
   435  		mCtx := monitorContext.NewTraceContext(context.Background(), "")
   436  		err = p.Apply(mCtx, ctx, v, nil)
   437  		Expect(err).To(HaveOccurred())
   438  
   439  		v, _ = value.NewValue(`
   440  value: {
   441    kind: "Pod"
   442    apiVersion: "v1"
   443  }
   444  patch: _|_
   445  `, nil, "")
   446  		err = p.Apply(mCtx, ctx, v, nil)
   447  		Expect(err).To(HaveOccurred())
   448  
   449  		v, err = value.NewValue(`
   450  value: {
   451    metadata: {
   452       name: "app-xx"
   453       namespace: "default"
   454    }
   455    kind: "Pod"
   456    apiVersion: "v1"
   457  }
   458  cluster: "test"
   459  `, nil, "")
   460  		Expect(err).ToNot(HaveOccurred())
   461  		err = p.Read(mCtx, ctx, v, nil)
   462  		Expect(err).ToNot(HaveOccurred())
   463  		errV, err := v.Field("err")
   464  		Expect(err).ToNot(HaveOccurred())
   465  		Expect(errV.Exists()).Should(BeTrue())
   466  
   467  		v, err = value.NewValue(`
   468  val: {
   469    metadata: {
   470       name: "app-xx"
   471       namespace: "default"
   472    }
   473    kind: "Pod"
   474    apiVersion: "v1"
   475  }
   476  `, nil, "")
   477  		Expect(err).ToNot(HaveOccurred())
   478  		err = p.Read(mCtx, ctx, v, nil)
   479  		Expect(err).To(HaveOccurred())
   480  		err = p.Apply(mCtx, ctx, v, nil)
   481  		Expect(err).To(HaveOccurred())
   482  	})
   483  })
   484  
   485  func newWorkflowContextForTest() (wfContext.Context, error) {
   486  	cm := corev1.ConfigMap{}
   487  
   488  	testCaseJson, err := yaml.YAMLToJSON([]byte(testCaseYaml))
   489  	if err != nil {
   490  		return nil, err
   491  	}
   492  	err = json.Unmarshal(testCaseJson, &cm)
   493  	if err != nil {
   494  		return nil, err
   495  	}
   496  
   497  	wfCtx := new(wfContext.WorkflowContext)
   498  	err = wfCtx.LoadFromConfigMap(cm)
   499  	return wfCtx, err
   500  }
   501  
   502  var (
   503  	componentStr = `apiVersion: "v1"
   504  kind:       "Pod"
   505  metadata: {
   506  	labels: {
   507  		app: "nginx"
   508  	}
   509  }
   510  spec: {
   511  	containers: [{
   512  		env: [{
   513  			name:  "APP"
   514  			value: "nginx"
   515  		}]
   516  		image:           "nginx:1.14.2"
   517  		imagePullPolicy: "IfNotPresent"
   518  		name:            "main"
   519  		ports: [{
   520  			containerPort: 8080
   521  			protocol:      "TCP"
   522  		}]
   523  	}]
   524  }`
   525  	testCaseYaml = `apiVersion: v1
   526  data:
   527    test: ""
   528  kind: ConfigMap
   529  metadata:
   530    name: app-v1
   531  `
   532  	expectedCue = `status: {
   533  	phase:    "Pending"
   534  	qosClass: "BestEffort"
   535  }
   536  apiVersion: "v1"
   537  kind:       "Pod"
   538  metadata: {
   539  	name: "app"
   540  	labels: {
   541  		app: "nginx"
   542  	}
   543  	namespace:         "default"
   544  }
   545  spec: {
   546  	containers: [{
   547  		name: "main"
   548  		env: [{
   549  			name:  "APP"
   550  			value: "nginx"
   551  		}]
   552  		image:           "nginx:1.14.2"
   553  		imagePullPolicy: "IfNotPresent"
   554  		ports: [{
   555  			containerPort: 8080
   556  			protocol:      "TCP"
   557  		}]
   558  		resources: {}
   559  		terminationMessagePath:   "/dev/termination-log"
   560  		terminationMessagePolicy: "File"
   561  	}]
   562  	dnsPolicy:          "ClusterFirst"
   563  	enableServiceLinks: true
   564  	preemptionPolicy:   "PreemptLowerPriority"
   565  	priority:           0
   566  	restartPolicy:      "Always"
   567  	schedulerName:      "default-scheduler"
   568  	securityContext: {}
   569  	terminationGracePeriodSeconds: 30
   570  	tolerations: [{
   571  		effect:            "NoExecute"
   572  		key:               "node.kubernetes.io/not-ready"
   573  		operator:          "Exists"
   574  		tolerationSeconds: 300
   575  	}, {
   576  		effect:            "NoExecute"
   577  		key:               "node.kubernetes.io/unreachable"
   578  		operator:          "Exists"
   579  		tolerationSeconds: 300
   580  	}]
   581  }`
   582  )