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 })