github.com/oam-dev/kubevela@v1.9.11/e2e/application/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 e2e
    18  
    19  import (
    20  	context2 "context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"regexp"
    24  	"strings"
    25  	"time"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  
    29  	"github.com/Netflix/go-expect"
    30  	"github.com/crossplane/crossplane-runtime/pkg/meta"
    31  	"github.com/onsi/ginkgo/v2"
    32  	"github.com/onsi/gomega"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    36  	"github.com/oam-dev/kubevela/e2e"
    37  	"github.com/oam-dev/kubevela/pkg/oam/util"
    38  	"github.com/oam-dev/kubevela/pkg/utils/common"
    39  )
    40  
    41  var (
    42  	envName                     = "env-application"
    43  	workloadType                = "webservice"
    44  	applicationName             = "app-basic"
    45  	traitAlias                  = "scaler"
    46  	appNameForInit              = "initmyapp"
    47  	jsonAppFile                 = `{"name":"nginx-vela","services":{"nginx":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
    48  	testDeleteJsonAppFile       = `{"name":"test-vela-delete","services":{"nginx-test":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
    49  	appbasicJsonAppFile         = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
    50  	appbasicAddTraitJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}],"scaler":{"replicas":2}}}}`
    51  	velaQL                      = "test-component-pod-view{appNs=default,appName=nginx-vela,name=nginx}"
    52  
    53  	waitAppfileToSuccess = `{"name":"app-wait-success","services":{"app-basic1":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
    54  	waitAppfileToFail    = `{"name":"app-wait-fail","services":{"app-basic2":{"type":"webservice","image":"nginx:fail","ports":[{port: 80, expose: true}]}}}`
    55  )
    56  
    57  var _ = ginkgo.Describe("Test Vela Application", ginkgo.Ordered, func() {
    58  	e2e.JsonAppFileContext("json appfile apply", jsonAppFile)
    59  	e2e.EnvSetContext("env set default", "default")
    60  	e2e.DeleteEnvFunc("env delete", envName)
    61  	e2e.EnvInitContext("env init env-application", envName)
    62  	e2e.EnvSetContext("env set", envName)
    63  	e2e.JsonAppFileContext("deploy app-basic", appbasicJsonAppFile)
    64  	ApplicationExecContext("exec -- COMMAND", applicationName)
    65  	ApplicationPortForwardContext("port-forward", applicationName)
    66  	e2e.JsonAppFileContext("update app-basic, add scaler trait with replicas 2", appbasicAddTraitJsonAppFile)
    67  	e2e.ComponentListContext("ls", applicationName, workloadType, traitAlias)
    68  	ApplicationStatusContext("status", applicationName, workloadType)
    69  	ApplicationStatusDeeplyContext("status", applicationName, workloadType, envName)
    70  	e2e.WorkloadDeleteContext("delete", applicationName)
    71  
    72  	ApplicationInitIntercativeCliContext("test vela init app", appNameForInit, workloadType)
    73  	e2e.WorkloadDeleteContext("delete", appNameForInit)
    74  
    75  	e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
    76  	ApplicationDeleteWithWaitOptions("test delete with wait option", "test-vela-delete")
    77  
    78  	e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
    79  	ApplicationDeleteWithForceOptions("test delete with force option", "test-vela-delete")
    80  
    81  	VelaQLPodListContext("ql", velaQL)
    82  
    83  	e2e.JsonAppFileContextWithWait("json appfile apply with wait", waitAppfileToSuccess)
    84  	e2e.JsonAppFileContextWithTimeout("json appfile apply with wait but timeout", waitAppfileToFail, "3s")
    85  })
    86  
    87  var ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
    88  	return ginkgo.It(context+": should get status for the application", func() {
    89  		cli := fmt.Sprintf("vela status %s", applicationName)
    90  		output, err := e2e.Exec(cli)
    91  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
    92  		gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
    93  		// TODO(roywang) add more assertion to check health status
    94  	})
    95  }
    96  
    97  var ApplicationStatusDeeplyContext = func(context string, applicationName, workloadType, envName string) bool {
    98  	return ginkgo.It(context+": should get status of the service", func() {
    99  		ginkgo.By("init new k8s client")
   100  		k8sclient, err := common.NewK8sClient()
   101  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   102  
   103  		ginkgo.By("check Application reconciled ready")
   104  		app := &v1beta1.Application{}
   105  		gomega.Eventually(func() bool {
   106  			_ = k8sclient.Get(context2.Background(), client.ObjectKey{Name: applicationName, Namespace: "default"}, app)
   107  			return app.Status.LatestRevision != nil
   108  		}, 180*time.Second, 1*time.Second).Should(gomega.BeTrue())
   109  
   110  		cli := fmt.Sprintf("vela status %s", applicationName)
   111  		output, err := e2e.LongTimeExec(cli, 120*time.Second)
   112  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   113  		gomega.Expect(strings.ToLower(output)).To(gomega.ContainSubstring("healthy"))
   114  		// TODO(zzxwill) need to check workloadType after app status is refined
   115  	})
   116  }
   117  
   118  var ApplicationExecContext = func(context string, appName string) bool {
   119  	return ginkgo.It(context+": should get output of exec /bin/ls", func() {
   120  		gomega.Eventually(func() string {
   121  			cli := fmt.Sprintf("vela exec %s -- /bin/ls ", appName)
   122  			output, err := e2e.Exec(cli)
   123  			gomega.Expect(err).NotTo(gomega.HaveOccurred())
   124  			return output
   125  		}, 90*time.Second, 5*time.Second).Should(gomega.ContainSubstring("bin"))
   126  	})
   127  }
   128  
   129  var ApplicationPortForwardContext = func(context string, appName string) bool {
   130  	return ginkgo.It(context+": should get output of port-forward successfully", func() {
   131  		cli := fmt.Sprintf("vela port-forward %s 8080:80 ", appName)
   132  		output, err := e2e.ExecAndTerminate(cli)
   133  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   134  		gomega.Expect(output).To(gomega.ContainSubstring("Forward successfully"))
   135  	})
   136  }
   137  
   138  var ApplicationInitIntercativeCliContext = func(context string, appName string, workloadType string) bool {
   139  	return ginkgo.It(context+": should init app through interactive questions", func() {
   140  		cli := "vela init"
   141  		output, err := e2e.InteractiveExec(cli, func(c *expect.Console) {
   142  			data := []struct {
   143  				q, a string
   144  			}{
   145  				{
   146  					q: "What would you like to name your application (required): ",
   147  					a: appName,
   148  				},
   149  				{
   150  					q: "webservice",
   151  					a: workloadType,
   152  				},
   153  				{
   154  					q: "What would you like to name this webservice (required): ",
   155  					a: "mysvc",
   156  				},
   157  				{
   158  					q: "Which image would you like to use for your service ",
   159  					a: "nginx:latest",
   160  				},
   161  				{
   162  					q: "Specify image pull policy for your service ",
   163  					a: "Always",
   164  				},
   165  				{
   166  					q: "Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core) (optional):",
   167  					a: "0.5",
   168  				},
   169  				{
   170  					q: "Specifies the attributes of the memory resource required for the container. (optional):",
   171  					a: "200M",
   172  				},
   173  			}
   174  			for _, qa := range data {
   175  				_, err := c.ExpectString(qa.q)
   176  				gomega.Expect(err).NotTo(gomega.HaveOccurred())
   177  				_, err = c.SendLine(qa.a)
   178  				gomega.Expect(err).NotTo(gomega.HaveOccurred())
   179  			}
   180  			c.ExpectEOF()
   181  		})
   182  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   183  		gomega.Expect(output).To(gomega.ContainSubstring("Application Deployed"))
   184  	})
   185  }
   186  
   187  var ApplicationDeleteWithWaitOptions = func(context string, appName string) bool {
   188  	return ginkgo.It(context+": should print successful deletion information", func() {
   189  		cli := fmt.Sprintf("vela delete %s --wait -y", appName)
   190  		output, err := e2e.ExecAndTerminate(cli)
   191  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   192  		gomega.Expect(output).To(gomega.ContainSubstring("succeeded"))
   193  	})
   194  }
   195  
   196  var ApplicationDeleteWithForceOptions = func(context string, appName string) bool {
   197  	return ginkgo.It(context+": should print successful deletion information", func() {
   198  		args := common.Args{
   199  			Schema: common.Scheme,
   200  		}
   201  		ctx := context2.Background()
   202  
   203  		k8sClient, err := args.GetClient()
   204  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   205  
   206  		app := new(v1beta1.Application)
   207  		gomega.Eventually(func() error {
   208  			if err := k8sClient.Get(ctx, client.ObjectKey{Name: appName, Namespace: "default"}, app); err != nil {
   209  				return err
   210  			}
   211  			meta.AddFinalizer(app, "test")
   212  			return k8sClient.Update(ctx, app)
   213  		}, time.Second*3, time.Millisecond*300).Should(gomega.BeNil())
   214  
   215  		cli := fmt.Sprintf("vela delete %s --force -y", appName)
   216  		output, err := e2e.LongTimeExec(cli, 3*time.Minute)
   217  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   218  		gomega.Expect(output).To(gomega.ContainSubstring("timed out"))
   219  
   220  		app = new(v1beta1.Application)
   221  		gomega.Eventually(func(g gomega.Gomega) {
   222  			g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: appName, Namespace: "default"}, app)).Should(gomega.Succeed())
   223  			meta.RemoveFinalizer(app, "test")
   224  			g.Expect(k8sClient.Update(ctx, app)).Should(gomega.Succeed())
   225  		}, time.Second*5, time.Millisecond*300).Should(gomega.Succeed())
   226  
   227  		cli = fmt.Sprintf("vela delete %s --force -y", appName)
   228  		output, err = e2e.ExecAndTerminate(cli)
   229  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   230  		gomega.Expect(output).To(gomega.ContainSubstring("deleted"))
   231  	})
   232  }
   233  
   234  type PodList struct {
   235  	PodList []Pod `form:"podList" json:"podList"`
   236  }
   237  
   238  type Pod struct {
   239  	Status   Status   `form:"status" json:"status"`
   240  	Cluster  string   `form:"cluster" json:"cluster"`
   241  	Metadata Metadata `form:"metadata" json:"metadata"`
   242  	Workload Workload `form:"workload" json:"workload"`
   243  }
   244  
   245  type Status struct {
   246  	Phase    string `form:"phase" json:"phase"`
   247  	NodeName string `form:"nodeName" json:"nodeName"`
   248  }
   249  
   250  type Metadata struct {
   251  	Namespace string `form:"namespace" json:"namespace"`
   252  }
   253  
   254  type Workload struct {
   255  	ApiVersion string `form:"apiVersion" json:"apiVersion"`
   256  	Kind       string `form:"kind" json:"kind"`
   257  }
   258  
   259  var VelaQLPodListContext = func(context string, velaQL string) bool {
   260  	return ginkgo.It(context+": should get successful result for executing vela ql", func() {
   261  		args := common.Args{
   262  			Schema: common.Scheme,
   263  		}
   264  		ctx := context2.Background()
   265  
   266  		k8sClient, err := args.GetClient()
   267  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   268  
   269  		componentView := new(corev1.ConfigMap)
   270  		gomega.Eventually(func(g gomega.Gomega) {
   271  			g.Expect(common.ReadYamlToObject("./component-pod-view.yaml", componentView)).Should(gomega.BeNil())
   272  			g.Expect(k8sClient.Create(ctx, componentView)).Should(gomega.SatisfyAny(gomega.Succeed(), util.AlreadyExistMatcher{}))
   273  		}, time.Second*3, time.Millisecond*300).Should(gomega.Succeed())
   274  
   275  		cli := fmt.Sprintf("vela ql %s", velaQL)
   276  		output, err := e2e.Exec(cli)
   277  
   278  		// remove warning like: W0406 14:07:49.832144 2443978 tree.go:958] ignore list resources: EndpointSlice as no matches for kind "EndpointSlice" in version "discovery.k8s.io/v1beta1"
   279  		re := regexp.MustCompile(`W\d{4}.*`)
   280  		output = re.ReplaceAllString(output, "")
   281  
   282  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   283  		var list PodList
   284  		err = json.Unmarshal([]byte(output), &list)
   285  		gomega.Expect(err).NotTo(gomega.HaveOccurred())
   286  		for _, v := range list.PodList {
   287  			if v.Cluster != "" {
   288  				gomega.Expect(v.Cluster).To(gomega.ContainSubstring("local"))
   289  			}
   290  			if v.Status.Phase != "" {
   291  				gomega.Expect(v.Status.Phase).To(gomega.ContainSubstring("Running"))
   292  			}
   293  			if v.Metadata.Namespace != "" {
   294  				gomega.Expect(v.Metadata.Namespace).To(gomega.ContainSubstring("default"))
   295  			}
   296  			if v.Workload.ApiVersion != "" {
   297  				gomega.Expect(v.Workload.ApiVersion).To(gomega.ContainSubstring("apps/v1"))
   298  			}
   299  			if v.Workload.Kind != "" {
   300  				gomega.Expect(v.Workload.Kind).To(gomega.ContainSubstring("ReplicaSet"))
   301  			}
   302  		}
   303  	})
   304  }