github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/apply_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 application 18 19 import ( 20 "context" 21 "strconv" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/oam-dev/kubevela/pkg/oam/testutil" 27 28 terraformtypes "github.com/oam-dev/terraform-controller/api/types" 29 terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2" 30 . "github.com/onsi/ginkgo/v2" 31 . "github.com/onsi/gomega" 32 appsv1 "k8s.io/api/apps/v1" 33 corev1 "k8s.io/api/core/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/types" 38 "k8s.io/utils/pointer" 39 "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 "sigs.k8s.io/yaml" 41 42 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 43 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 44 "github.com/oam-dev/kubevela/pkg/oam/util" 45 ) 46 47 const workloadDefinition = ` 48 apiVersion: core.oam.dev/v1beta1 49 kind: WorkloadDefinition 50 metadata: 51 name: test-worker 52 annotations: 53 definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic." 54 spec: 55 workload: 56 definition: 57 apiVersion: apps/v1 58 kind: Deployment 59 schematic: 60 cue: 61 template: | 62 output: { 63 apiVersion: "apps/v1" 64 kind: "Deployment" 65 spec: { 66 selector: matchLabels: { 67 "app.oam.dev/component": context.name 68 } 69 template: { 70 metadata: labels: { 71 "app.oam.dev/component": context.name 72 } 73 spec: { 74 containers: [{ 75 name: context.name 76 image: parameter.image 77 78 if parameter["cmd"] != _|_ { 79 command: parameter.cmd 80 } 81 }] 82 } 83 } 84 } 85 } 86 parameter: { 87 image: string 88 cmd?: [...string] 89 } 90 ` 91 92 var _ = Describe("Test Application apply", func() { 93 var app *v1beta1.Application 94 var namespaceName string 95 var ns corev1.Namespace 96 97 BeforeEach(func() { 98 ctx := context.TODO() 99 namespaceName = "apply-test-" + strconv.Itoa(time.Now().Second()) + "-" + strconv.Itoa(time.Now().Nanosecond()) 100 ns = corev1.Namespace{ 101 ObjectMeta: metav1.ObjectMeta{ 102 Name: namespaceName, 103 }, 104 } 105 app = &v1beta1.Application{ 106 TypeMeta: metav1.TypeMeta{ 107 Kind: "Application", 108 APIVersion: "core.oam.dev/v1beta1", 109 }, 110 } 111 app.Namespace = namespaceName 112 app.Spec = v1beta1.ApplicationSpec{ 113 Components: []common.ApplicationComponent{{ 114 Type: "test-worker", 115 Name: "test-app", 116 Properties: &runtime.RawExtension{ 117 Raw: []byte(`{"image": "oamdev/testapp:v1", "cmd": ["node", "server.js"]}`), 118 }, 119 }}, 120 } 121 By("Create the Namespace for test") 122 Expect(k8sClient.Create(ctx, &ns)).Should(Succeed()) 123 }) 124 125 AfterEach(func() { 126 By("[TEST] Clean up resources after an integration test") 127 Expect(k8sClient.Delete(context.TODO(), &ns)).Should(Succeed()) 128 }) 129 130 It("Test update or create app revision", func() { 131 ctx := context.TODO() 132 By("[TEST] Create a workload definition") 133 var deployDef v1beta1.WorkloadDefinition 134 Expect(yaml.Unmarshal([]byte(workloadDefinition), &deployDef)).Should(BeNil()) 135 deployDef.Namespace = app.Namespace 136 Expect(k8sClient.Create(ctx, &deployDef)).Should(SatisfyAny(BeNil())) 137 138 By("[TEST] Create a application") 139 app.Name = "poc" 140 err := k8sClient.Create(ctx, app) 141 Expect(err).Should(BeNil()) 142 143 By("[TEST] get a application") 144 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: types.NamespacedName{Name: app.Name, Namespace: app.Namespace}}) 145 testapp := v1beta1.Application{} 146 err = k8sClient.Get(ctx, types.NamespacedName{Name: app.Name, Namespace: app.Namespace}, &testapp) 147 Expect(err).Should(BeNil()) 148 Expect(testapp.Status.LatestRevision != nil).Should(BeTrue()) 149 150 By("[TEST] get a application revision") 151 appRevName := testapp.Status.LatestRevision.Name 152 apprev := &v1beta1.ApplicationRevision{} 153 err = k8sClient.Get(ctx, types.NamespacedName{Name: appRevName, Namespace: app.Namespace}, apprev) 154 Expect(err).Should(BeNil()) 155 156 By("[TEST] verify that the revision is exist and set correctly") 157 applabel, exist := apprev.Labels["app.oam.dev/name"] 158 Expect(exist).Should(BeTrue()) 159 Expect(strings.Compare(applabel, app.Name) == 0).Should(BeTrue()) 160 }) 161 }) 162 163 var _ = Describe("Test deleter resource", func() { 164 It("Test delete resource will remove ref from reference", func() { 165 deployName := "test-del-resource-workload" 166 namespace := "test-del-resource-namespace" 167 ctx := context.Background() 168 Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}})).Should(BeNil()) 169 deploy := appsv1.Deployment{ 170 TypeMeta: metav1.TypeMeta{ 171 APIVersion: "apps/v1", 172 Kind: "Deployment", 173 }, 174 ObjectMeta: metav1.ObjectMeta{ 175 Namespace: namespace, 176 Name: deployName, 177 }, 178 Spec: appsv1.DeploymentSpec{ 179 Replicas: pointer.Int32(3), 180 Selector: &metav1.LabelSelector{ 181 MatchLabels: map[string]string{ 182 "app": "test", 183 }, 184 }, 185 Template: corev1.PodTemplateSpec{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Labels: map[string]string{ 188 "app": "test", 189 }, 190 }, 191 Spec: corev1.PodSpec{ 192 Containers: []corev1.Container{ 193 { 194 Name: "test-container", 195 Image: "test-image", 196 }, 197 }, 198 }, 199 }, 200 }, 201 } 202 Expect(k8sClient.Create(ctx, &deploy)).Should(BeNil()) 203 u := unstructured.Unstructured{} 204 u.SetAPIVersion("apps/v1") 205 u.SetKind("Deployment") 206 Expect(k8sClient.Get(ctx, types.NamespacedName{Name: deployName, Namespace: namespace}, &u)).Should(BeNil()) 207 appliedRsc := []common.ClusterObjectReference{ 208 { 209 Creator: common.WorkflowResourceCreator, 210 ObjectReference: corev1.ObjectReference{ 211 Kind: u.GetKind(), 212 APIVersion: u.GetAPIVersion(), 213 Namespace: u.GetNamespace(), 214 Name: deployName, 215 }, 216 }, 217 { 218 Creator: common.WorkflowResourceCreator, 219 ObjectReference: corev1.ObjectReference{ 220 Kind: "StatefulSet", 221 APIVersion: "apps/v1", 222 Namespace: "test-namespace", 223 Name: "test-sts", 224 }, 225 }, 226 } 227 h, err := NewAppHandler(ctx, reconciler, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "default"}}) 228 Expect(err).Should(Succeed()) 229 h.appliedResources = appliedRsc 230 Expect(h.Delete(ctx, "", common.WorkflowResourceCreator, &u)) 231 checkDeploy := unstructured.Unstructured{} 232 checkDeploy.SetAPIVersion("apps/v1") 233 checkDeploy.SetKind("Deployment") 234 Expect(k8sClient.Get(ctx, types.NamespacedName{Name: deployName, Namespace: namespace}, &u)).Should(SatisfyAny(util.NotFoundMatcher{})) 235 Expect(len(h.appliedResources)).Should(BeEquivalentTo(1)) 236 Expect(h.appliedResources[0].Kind).Should(BeEquivalentTo("StatefulSet")) 237 Expect(h.appliedResources[0].Name).Should(BeEquivalentTo("test-sts")) 238 }) 239 }) 240 241 func TestDeleteAppliedResourceFunc(t *testing.T) { 242 h := AppHandler{appliedResources: []common.ClusterObjectReference{ 243 { 244 ObjectReference: corev1.ObjectReference{ 245 Name: "wl-1", 246 Kind: "Deployment", 247 }, 248 }, 249 { 250 ObjectReference: corev1.ObjectReference{ 251 Name: "wl-2", 252 Kind: "Deployment", 253 }, 254 }, 255 { 256 ObjectReference: corev1.ObjectReference{ 257 Name: "wl-1", 258 Kind: "StatefulSet", 259 }, 260 }, 261 { 262 Cluster: "runtime-cluster", 263 ObjectReference: corev1.ObjectReference{ 264 Name: "wl-1", 265 Kind: "StatefulSet", 266 }, 267 }, 268 }} 269 deleteResc_1 := common.ClusterObjectReference{ObjectReference: corev1.ObjectReference{Name: "wl-1", Kind: "StatefulSet"}, Cluster: "runtime-cluster"} 270 deleteResc_2 := common.ClusterObjectReference{ObjectReference: corev1.ObjectReference{Name: "wl-2", Kind: "Deployment"}} 271 h.deleteAppliedResource(deleteResc_1) 272 h.deleteAppliedResource(deleteResc_2) 273 if len(h.appliedResources) != 2 { 274 t.Errorf("applied length error acctually %d", len(h.appliedResources)) 275 } 276 if h.appliedResources[0].Name != "wl-1" || h.appliedResources[0].Kind != "Deployment" { 277 t.Errorf("resource missmatch") 278 } 279 if h.appliedResources[1].Name != "wl-1" || h.appliedResources[1].Kind != "StatefulSet" { 280 t.Errorf("resource missmatch") 281 } 282 283 preDelResc := common.ClusterObjectReference{ObjectReference: corev1.ObjectReference{Name: "wl-3", Kind: "StatefulSet"}, Cluster: "runtime-cluster"} 284 h.deleteAppliedResource(preDelResc) 285 h.addAppliedResource(true, preDelResc) 286 if len(h.appliedResources) != 2 { 287 t.Errorf("applied length error acctually %d", len(h.appliedResources)) 288 } 289 } 290 291 var _ = Describe("Test Application health check", func() { 292 const ( 293 timeout = time.Second * 10 294 duration = time.Second * 10 295 interval = time.Millisecond * 500 296 ) 297 298 var ( 299 ctx context.Context 300 ns corev1.Namespace 301 ) 302 303 BeforeEach(func() { 304 ctx = context.TODO() 305 namespaceName := "health-check-test-" + strconv.Itoa(time.Now().Second()) + "-" + strconv.Itoa(time.Now().Nanosecond()) 306 ns = corev1.Namespace{ 307 ObjectMeta: metav1.ObjectMeta{ 308 Name: namespaceName, 309 }, 310 } 311 By("By create the Namespace for test") 312 Expect(k8sClient.Create(ctx, &ns)).Should(Succeed()) 313 }) 314 315 AfterEach(func() { 316 By("By clean up resources after an integration test") 317 Expect(k8sClient.Delete(ctx, &ns)).Should(Succeed()) 318 }) 319 320 Context("Terraform components", func() { 321 var ( 322 componentNamespace = "vela-system" 323 componentName string 324 comp v1beta1.ComponentDefinition 325 326 applicationName = "test-tf-health-app" 327 ) 328 329 BeforeEach(func() { 330 componentName = "demo-hello-tf" + strconv.Itoa(time.Now().Second()) + "-" + strconv.Itoa(time.Now().Nanosecond()) 331 comp = v1beta1.ComponentDefinition{ 332 TypeMeta: metav1.TypeMeta{ 333 APIVersion: v1beta1.ComponentDefinitionKindAPIVersion, 334 Kind: v1beta1.ComponentDefinitionKind, 335 }, 336 ObjectMeta: metav1.ObjectMeta{ 337 Name: componentName, 338 Namespace: componentNamespace, 339 Labels: map[string]string{ 340 "type": "terraform", 341 }, 342 }, 343 Spec: v1beta1.ComponentDefinitionSpec{ 344 Workload: common.WorkloadTypeDescriptor{ 345 Definition: common.WorkloadGVK{ 346 APIVersion: "terraform.core.oam.dev/v1beta2", 347 Kind: "Configuration", 348 }, 349 }, 350 Schematic: &common.Schematic{ 351 Terraform: &common.Terraform{ 352 Configuration: ` 353 terraform {} 354 355 variable "name" { 356 type = string 357 description = "your name" 358 } 359 360 output "message" { 361 value = "hello, ${var.name}" 362 }`, 363 }, 364 }, 365 }, 366 } 367 By("By create terraform component") 368 Expect(k8sClient.Create(ctx, &comp)).Should(Succeed()) 369 }) 370 371 AfterEach(func() { 372 By("By clean terraform component") 373 Expect(k8sClient.Delete(ctx, &comp)).Should(Succeed()) 374 }) 375 376 It("Should terraform components stand ready and the latest when a new application is running", func() { 377 By("By creating a new app") 378 tfCompName := applicationName + "-comp" 379 app := v1beta1.Application{ 380 TypeMeta: metav1.TypeMeta{ 381 APIVersion: v1beta1.ApplicationKindAPIVersion, 382 Kind: v1beta1.ApplicationKind, 383 }, 384 ObjectMeta: metav1.ObjectMeta{ 385 Name: applicationName, 386 Namespace: ns.Name, 387 }, 388 Spec: v1beta1.ApplicationSpec{ 389 Components: []common.ApplicationComponent{ 390 { 391 Name: tfCompName, 392 Type: componentName, 393 Properties: &runtime.RawExtension{ 394 Raw: []byte(`{"name": "vela!"}`), 395 }, 396 }, 397 }, 398 }, 399 } 400 Expect(k8sClient.Create(ctx, &app)).Should(Succeed()) 401 402 applicationLoopupKey := types.NamespacedName{Name: applicationName, Namespace: ns.Name} 403 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: applicationLoopupKey}) 404 405 configurationLookupKey := types.NamespacedName{Name: tfCompName, Namespace: ns.Name} 406 configuration := &terraformapi.Configuration{} 407 408 Eventually(func() error { 409 return k8sClient.Get(ctx, configurationLookupKey, configuration) 410 }, timeout, interval).Should(Succeed()) 411 412 By("By checking the Application status is not running") 413 Consistently(func() (common.ApplicationPhase, error) { 414 err := k8sClient.Get(ctx, applicationLoopupKey, &app) 415 if err != nil { 416 return "", err 417 } 418 return app.Status.Phase, nil 419 }, duration, interval).ShouldNot(Equal(common.ApplicationRunning)) 420 421 By("By configuration is ready") 422 configuration.Status = terraformapi.ConfigurationStatus{ 423 ObservedGeneration: configuration.Generation, 424 Apply: terraformapi.ConfigurationApplyStatus{ 425 State: terraformtypes.Available, 426 }, 427 } 428 Expect(k8sClient.Status().Update(ctx, configuration)).Should(Succeed()) 429 430 By("By checking that the Application status is running") 431 Eventually(func() common.ApplicationPhase { 432 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: applicationLoopupKey}) 433 err := k8sClient.Get(ctx, applicationLoopupKey, &app) 434 if err != nil { 435 return "" 436 } 437 return app.Status.Phase 438 }, timeout, interval).Should(Equal(common.ApplicationRunning)) 439 440 By("By update the Application and check that the status is not running") 441 app.Spec.Components[0].Properties.Raw = []byte(`{"name": "terraform!"}`) 442 Expect(k8sClient.Update(ctx, &app)).Should(Succeed()) 443 444 Consistently(func() (common.ApplicationPhase, error) { 445 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: applicationLoopupKey}) 446 if err := k8sClient.Get(ctx, applicationLoopupKey, &app); err != nil { 447 return "", err 448 } 449 return app.Status.Phase, nil 450 }, duration, interval).ShouldNot(Equal(common.ApplicationRunning)) 451 }) 452 }) 453 })