github.com/oam-dev/kubevela@v1.9.11/test/e2e-test/application_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 controllers_test
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math/rand"
    23  	"strconv"
    24  	"time"
    25  
    26  	. "github.com/onsi/ginkgo/v2"
    27  	. "github.com/onsi/gomega"
    28  	v1 "k8s.io/api/apps/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	rbacv1 "k8s.io/api/rbac/v1"
    31  	"k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  
    35  	oamcomm "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    36  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    37  	"github.com/oam-dev/kubevela/pkg/oam"
    38  	"github.com/oam-dev/kubevela/pkg/oam/util"
    39  	"github.com/oam-dev/kubevela/pkg/utils/common"
    40  )
    41  
    42  func createNamespace(ctx context.Context, namespaceName string) corev1.Namespace {
    43  	ns := corev1.Namespace{
    44  		ObjectMeta: metav1.ObjectMeta{
    45  			Name: namespaceName,
    46  		},
    47  	}
    48  	// delete the namespaceName with all its resources
    49  	Eventually(
    50  		func() error {
    51  			return k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))
    52  		},
    53  		time.Second*120, time.Millisecond*500).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
    54  	By("make sure all the resources are removed")
    55  	objectKey := client.ObjectKey{
    56  		Name: namespaceName,
    57  	}
    58  	res := &corev1.Namespace{}
    59  	Eventually(
    60  		func() error {
    61  			return k8sClient.Get(ctx, objectKey, res)
    62  		},
    63  		time.Second*120, time.Millisecond*500).Should(&util.NotFoundMatcher{})
    64  	Eventually(
    65  		func() error {
    66  			return k8sClient.Create(ctx, &ns)
    67  		},
    68  		time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    69  	return ns
    70  }
    71  
    72  func createServiceAccount(ctx context.Context, ns, name string) {
    73  	sa := corev1.ServiceAccount{
    74  		ObjectMeta: metav1.ObjectMeta{
    75  			Namespace: ns,
    76  			Name:      name,
    77  		},
    78  	}
    79  	Eventually(
    80  		func() error {
    81  			return k8sClient.Create(ctx, &sa)
    82  		},
    83  		time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    84  }
    85  
    86  func applyApp(ctx context.Context, namespaceName, source string, app *v1beta1.Application) {
    87  	By("Apply an application")
    88  	var newApp v1beta1.Application
    89  	Expect(common.ReadYamlToObject("testdata/app/"+source, &newApp)).Should(BeNil())
    90  	newApp.Namespace = namespaceName
    91  	Eventually(func() error {
    92  		return k8sClient.Create(ctx, newApp.DeepCopy())
    93  	}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
    94  
    95  	By("Get Application latest status")
    96  	Eventually(
    97  		func() *oamcomm.Revision {
    98  			k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: newApp.Name}, app)
    99  			if app.Status.LatestRevision != nil {
   100  				return app.Status.LatestRevision
   101  			}
   102  			return nil
   103  		},
   104  		time.Second*30, time.Millisecond*500).ShouldNot(BeNil())
   105  }
   106  
   107  func updateApp(ctx context.Context, namespaceName, target string, app *v1beta1.Application) {
   108  	By("Update the application to target spec during rolling")
   109  	var targetApp v1beta1.Application
   110  	Expect(common.ReadYamlToObject("testdata/app/"+target, &targetApp)).Should(BeNil())
   111  
   112  	Eventually(
   113  		func() error {
   114  			k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: app.Name}, app)
   115  			app.Spec = targetApp.Spec
   116  			return k8sClient.Update(ctx, app)
   117  		}, time.Second*5, time.Millisecond*500).Should(Succeed())
   118  }
   119  
   120  func verifyApplicationPhase(ctx context.Context, ns, appName string, expected oamcomm.ApplicationPhase) {
   121  	var testApp v1beta1.Application
   122  	Eventually(func() error {
   123  		err := k8sClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: appName}, &testApp)
   124  		if err != nil {
   125  			return err
   126  		}
   127  		if testApp.Status.Phase != expected {
   128  			return fmt.Errorf("application status wants %s, actually %s", expected, testApp.Status.Phase)
   129  		}
   130  		return nil
   131  	}, 120*time.Second, time.Second).Should(BeNil())
   132  }
   133  
   134  func verifyApplicationDelaySuspendExpected(ctx context.Context, ns, appName, suspendStep, nextStep, duration string) {
   135  	var testApp v1beta1.Application
   136  	Eventually(func() error {
   137  		waitDuration, err := time.ParseDuration(duration)
   138  		if err != nil {
   139  			return err
   140  		}
   141  
   142  		err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: appName}, &testApp)
   143  		if err != nil {
   144  			return err
   145  		}
   146  
   147  		if testApp.Status.Workflow == nil {
   148  			return fmt.Errorf("application wait to start workflow")
   149  		}
   150  
   151  		if testApp.Status.Workflow.Finished {
   152  			var suspendStartTime, nextStepStartTime metav1.Time
   153  			var sFlag, nFlag bool
   154  
   155  			for _, wfStatus := range testApp.Status.Workflow.Steps {
   156  				if wfStatus.Name == suspendStep {
   157  					suspendStartTime = wfStatus.FirstExecuteTime
   158  					sFlag = true
   159  					continue
   160  				}
   161  
   162  				if wfStatus.Name == nextStep {
   163  					nextStepStartTime = wfStatus.FirstExecuteTime
   164  					nFlag = true
   165  				}
   166  			}
   167  
   168  			if !sFlag {
   169  				return fmt.Errorf("application can not find suspend step: %s", suspendStep)
   170  			}
   171  
   172  			if !nFlag {
   173  				return fmt.Errorf("application can not find next step: %s", nextStep)
   174  			}
   175  
   176  			dd := nextStepStartTime.Sub(suspendStartTime.Time)
   177  			if waitDuration > dd {
   178  				return fmt.Errorf("application suspend wait duration wants more than %s, actually %s", duration, dd.String())
   179  			}
   180  
   181  			return nil
   182  		}
   183  		return fmt.Errorf("application status workflow finished wants true, actually false")
   184  	}, 120*time.Second, time.Second).Should(BeNil())
   185  }
   186  
   187  func verifyWorkloadRunningExpected(ctx context.Context, namespaceName, workloadName string, replicas int32, image string) {
   188  	var workload v1.Deployment
   189  	By("Verify Workload running as expected")
   190  	Eventually(
   191  		func() error {
   192  			if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: workloadName}, &workload); err != nil {
   193  				return err
   194  			}
   195  			if workload.Status.ReadyReplicas != replicas {
   196  				return fmt.Errorf("expect replicas %v != real %v", replicas, workload.Status.ReadyReplicas)
   197  			}
   198  			if workload.Spec.Template.Spec.Containers[0].Image != image {
   199  				return fmt.Errorf("expect replicas %v != real %v", image, workload.Spec.Template.Spec.Containers[0].Image)
   200  			}
   201  			return nil
   202  		},
   203  		time.Second*60, time.Millisecond*500).Should(BeNil())
   204  }
   205  
   206  var _ = Describe("Application Normal tests", func() {
   207  	ctx := context.Background()
   208  	var namespaceName string
   209  	var ns corev1.Namespace
   210  	var app *v1beta1.Application
   211  
   212  	BeforeEach(func() {
   213  		By("Start to run a test, clean up previous resources")
   214  		namespaceName = "app-normal-e2e-test" + "-" + strconv.FormatInt(rand.Int63(), 16)
   215  		ns = createNamespace(ctx, namespaceName)
   216  		app = &v1beta1.Application{}
   217  	})
   218  
   219  	AfterEach(func() {
   220  		By("Clean up resources after a test")
   221  		k8sClient.Delete(ctx, app)
   222  		By(fmt.Sprintf("Delete the entire namespaceName %s", ns.Name))
   223  		// delete the namespaceName with all its resources
   224  		Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationBackground))).Should(BeNil())
   225  	})
   226  
   227  	It("Test app created normally", func() {
   228  		applyApp(ctx, namespaceName, "app1.yaml", app)
   229  		By("Apply the application rollout go directly to the target")
   230  		verifyWorkloadRunningExpected(ctx, namespaceName, "myweb", 1, "stefanprodan/podinfo:4.0.3")
   231  
   232  		By("Update app with trait")
   233  		updateApp(ctx, namespaceName, "app2.yaml", app)
   234  		By("Apply the application rollout go directly to the target")
   235  		verifyWorkloadRunningExpected(ctx, namespaceName, "myweb", 2, "stefanprodan/podinfo:4.0.3")
   236  
   237  		By("Update app with trait updated")
   238  		updateApp(ctx, namespaceName, "app3.yaml", app)
   239  		By("Apply the application rollout go directly to the target")
   240  		verifyWorkloadRunningExpected(ctx, namespaceName, "myweb", 3, "stefanprodan/podinfo:4.0.3")
   241  
   242  		By("Update app with trait and workload image updated")
   243  		updateApp(ctx, namespaceName, "app4.yaml", app)
   244  		By("Apply the application rollout go directly to the target")
   245  		verifyWorkloadRunningExpected(ctx, namespaceName, "myweb", 1, "stefanprodan/podinfo:5.0.2")
   246  	})
   247  
   248  	It("Test app have component with multiple same type traits", func() {
   249  		traitDef := new(v1beta1.TraitDefinition)
   250  		Expect(common.ReadYamlToObject("testdata/app/trait_config.yaml", traitDef)).Should(BeNil())
   251  		traitDef.Namespace = namespaceName
   252  		Expect(k8sClient.Create(ctx, traitDef)).Should(BeNil())
   253  
   254  		By("apply application")
   255  		applyApp(ctx, namespaceName, "app7.yaml", app)
   256  		appName := "test-worker"
   257  
   258  		By("check application status")
   259  		testApp := new(v1beta1.Application)
   260  		Eventually(func() error {
   261  			err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: appName}, testApp)
   262  			if err != nil {
   263  				return err
   264  			}
   265  			if len(testApp.Status.Services) != 1 {
   266  				return fmt.Errorf("error ComponentStatus number wants %d, actually %d", 1, len(testApp.Status.Services))
   267  			}
   268  			if len(testApp.Status.Services[0].Traits) != 2 {
   269  				return fmt.Errorf("error TraitStatus number wants %d, actually %d", 2, len(testApp.Status.Services[0].Traits))
   270  			}
   271  			return nil
   272  		}, 5*time.Second).Should(BeNil())
   273  
   274  		By("check trait status")
   275  		Expect(testApp.Status.Services[0].Traits[0].Message).Should(Equal("configMap:app-file-html"))
   276  		Expect(testApp.Status.Services[0].Traits[1].Message).Should(Equal("secret:app-env-config"))
   277  	})
   278  
   279  	It("Test app have components with same name", func() {
   280  		By("Apply an application")
   281  		var newApp v1beta1.Application
   282  		Expect(common.ReadYamlToObject("testdata/app/app8.yaml", &newApp)).Should(BeNil())
   283  		newApp.Namespace = namespaceName
   284  		Expect(k8sClient.Create(ctx, &newApp)).ShouldNot(BeNil())
   285  	})
   286  
   287  	It("Test app failed after retries", func() {
   288  		By("Apply an application")
   289  		var newApp v1beta1.Application
   290  		Expect(common.ReadYamlToObject("testdata/app/app10.yaml", &newApp)).Should(BeNil())
   291  		newApp.Namespace = namespaceName
   292  		Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
   293  
   294  		By("check application status")
   295  		verifyApplicationPhase(ctx, newApp.Namespace, newApp.Name, oamcomm.ApplicationWorkflowFailed)
   296  	})
   297  
   298  	It("Test app with notification and custom if", func() {
   299  		By("Apply an application")
   300  		var newApp v1beta1.Application
   301  		Expect(common.ReadYamlToObject("testdata/app/app12.yaml", &newApp)).Should(BeNil())
   302  		newApp.Namespace = namespaceName
   303  		Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
   304  
   305  		By("check application status")
   306  		verifyWorkloadRunningExpected(ctx, namespaceName, "comp-custom-if", 1, "crccheck/hello-world")
   307  	})
   308  
   309  	It("Test wait suspend", func() {
   310  		By("Apply wait suspend application")
   311  		var newApp v1beta1.Application
   312  		Expect(common.ReadYamlToObject("testdata/app/app_wait_suspend.yaml", &newApp)).Should(BeNil())
   313  		newApp.Namespace = namespaceName
   314  		Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
   315  
   316  		By("check application suspend duration")
   317  		verifyApplicationDelaySuspendExpected(ctx, newApp.Namespace, newApp.Name, "suspend-test", "apply-wait-suspend-comp", "30s")
   318  	})
   319  
   320  	It("Test app with ServiceAccount", func() {
   321  		By("Creating a ServiceAccount")
   322  		const saName = "app-service-account"
   323  		createServiceAccount(ctx, namespaceName, saName)
   324  
   325  		By("Creating Role and RoleBinding")
   326  		const roleName = "worker"
   327  		role := rbacv1.Role{
   328  			ObjectMeta: metav1.ObjectMeta{
   329  				Namespace: namespaceName,
   330  				Name:      roleName,
   331  			},
   332  			Rules: []rbacv1.PolicyRule{
   333  				{
   334  					Verbs:     []string{rbacv1.VerbAll},
   335  					APIGroups: []string{"apps"},
   336  					Resources: []string{"deployments", "controllerrevisions"},
   337  				},
   338  			},
   339  		}
   340  		Expect(k8sClient.Create(ctx, &role)).Should(BeNil())
   341  
   342  		roleBinding := rbacv1.RoleBinding{
   343  			ObjectMeta: metav1.ObjectMeta{
   344  				Namespace: namespaceName,
   345  				Name:      roleName + "-binding",
   346  			},
   347  			Subjects: []rbacv1.Subject{
   348  				{
   349  					Kind:      "ServiceAccount",
   350  					Name:      saName,
   351  					Namespace: namespaceName,
   352  				},
   353  			},
   354  			RoleRef: rbacv1.RoleRef{
   355  				APIGroup: rbacv1.GroupName,
   356  				Kind:     "Role",
   357  				Name:     roleName,
   358  			},
   359  		}
   360  		Expect(k8sClient.Create(ctx, &roleBinding)).Should(BeNil())
   361  
   362  		By("Creating an application")
   363  		var newApp v1beta1.Application
   364  		Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
   365  		newApp.Namespace = namespaceName
   366  		annotations := newApp.GetAnnotations()
   367  		annotations[oam.AnnotationApplicationServiceAccountName] = saName
   368  		newApp.SetAnnotations(annotations)
   369  		Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
   370  
   371  		By("Checking an application status")
   372  		verifyWorkloadRunningExpected(ctx, namespaceName, "myweb", 1, "stefanprodan/podinfo:4.0.3")
   373  
   374  		Expect(k8sClient.Delete(ctx, &newApp)).Should(Succeed())
   375  		Eventually(func(g Gomega) {
   376  			g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&newApp), &newApp)).Should(Satisfy(errors.IsNotFound))
   377  		}, 15*time.Second).Should(Succeed())
   378  	})
   379  
   380  	It("Test app with ServiceAccount which has no permission for the component", func() {
   381  		By("Creating a ServiceAccount")
   382  		const saName = "dummy-service-account"
   383  		createServiceAccount(ctx, namespaceName, saName)
   384  
   385  		By("Creating an application")
   386  		var newApp v1beta1.Application
   387  		Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
   388  		newApp.Namespace = namespaceName
   389  		annotations := newApp.GetAnnotations()
   390  		annotations[oam.AnnotationApplicationServiceAccountName] = saName
   391  		newApp.SetAnnotations(annotations)
   392  		Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
   393  
   394  		By("Checking an application status")
   395  		verifyApplicationPhase(ctx, newApp.Namespace, newApp.Name, oamcomm.ApplicationWorkflowFailed)
   396  	})
   397  
   398  	It("Test app with non-existence ServiceAccount", func() {
   399  		By("Ensuring that given service account doesn't exists")
   400  		const saName = "not-existing-service-account"
   401  		sa := corev1.ServiceAccount{
   402  			ObjectMeta: metav1.ObjectMeta{
   403  				Namespace: namespaceName,
   404  				Name:      saName,
   405  			},
   406  		}
   407  		Eventually(
   408  			func() error {
   409  				return k8sClient.Delete(ctx, &sa)
   410  			},
   411  			time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
   412  
   413  		By("Creating an application")
   414  		var newApp v1beta1.Application
   415  		Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
   416  		newApp.Namespace = namespaceName
   417  		annotations := newApp.GetAnnotations()
   418  		annotations[oam.AnnotationApplicationServiceAccountName] = saName
   419  		newApp.SetAnnotations(annotations)
   420  		Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
   421  
   422  		By("Checking an application status")
   423  		verifyApplicationPhase(ctx, newApp.Namespace, newApp.Name, oamcomm.ApplicationWorkflowFailed)
   424  	})
   425  
   426  	It("Test app with replication policy", func() {
   427  		By("Apply replica-webservice definition")
   428  		var compDef v1beta1.ComponentDefinition
   429  		Expect(common.ReadYamlToObject("testdata/definition/replica-webservice.yaml", &compDef)).Should(BeNil())
   430  		Eventually(func() error {
   431  			return k8sClient.Create(ctx, compDef.DeepCopy())
   432  		}, 10*time.Second, 500*time.Millisecond).Should(SatisfyAny(util.AlreadyExistMatcher{}, BeNil()))
   433  
   434  		By("Creating an application")
   435  		applyApp(ctx, namespaceName, "app_replication.yaml", app)
   436  
   437  		By("Checking the replication & application status")
   438  		verifyWorkloadRunningExpected(ctx, namespaceName, "hello-rep-beijing", 1, "crccheck/hello-world")
   439  		verifyWorkloadRunningExpected(ctx, namespaceName, "hello-rep-hangzhou", 1, "crccheck/hello-world")
   440  		By("Checking the origin component are not be dispatched")
   441  		var workload v1.Deployment
   442  		err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: "hello-rep"}, &workload)
   443  		Expect(err).Should(SatisfyAny(&util.NotFoundMatcher{}))
   444  
   445  		By("Checking the component not replicated & application status")
   446  		verifyWorkloadRunningExpected(ctx, namespaceName, "hello-no-rep", 1, "crccheck/hello-world")
   447  
   448  		var svc corev1.Service
   449  		By("Verify Service running as expected")
   450  		verifySeriveDispatched := func(svcName string) {
   451  			Eventually(
   452  				func() error {
   453  					return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: svcName}, &svc)
   454  				},
   455  				time.Second*120, time.Millisecond*500).Should(BeNil())
   456  		}
   457  		verifySeriveDispatched("hello-rep-beijing")
   458  		verifySeriveDispatched("hello-rep-hangzhou")
   459  
   460  		By("Checking the services not replicated & application status")
   461  		verifyWorkloadRunningExpected(ctx, namespaceName, "hello-no-rep", 1, "crccheck/hello-world")
   462  
   463  	})
   464  })