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 }