github.com/oam-dev/kubevela@v1.9.11/test/e2e-test/app_resourcetracker_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 "encoding/json" 22 "fmt" 23 "time" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 "github.com/pkg/errors" 28 29 appsv1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/types" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 37 "sigs.k8s.io/yaml" 38 39 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 40 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 41 "github.com/oam-dev/kubevela/pkg/oam" 42 "github.com/oam-dev/kubevela/pkg/oam/util" 43 ) 44 45 var _ = Describe("Test application cross namespace resource", func() { 46 ctx := context.Background() 47 var namespace, crossNamespace string 48 49 BeforeEach(func() { 50 namespace = randomNamespaceName("app-resource-tracker-e2e") 51 ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} 52 Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 53 54 crossNamespace = randomNamespaceName("cross-namespace") 55 crossNs := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: crossNamespace}} 56 Expect(k8sClient.Create(ctx, &crossNs)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 57 58 Eventually(func() error { 59 ns := new(corev1.Namespace) 60 return k8sClient.Get(ctx, types.NamespacedName{Name: namespace}, ns) 61 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 62 Eventually(func() error { 63 ns := new(corev1.Namespace) 64 return k8sClient.Get(ctx, types.NamespacedName{Name: crossNamespace}, ns) 65 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 66 }) 67 68 AfterEach(func() { 69 By("Clean up resources after a test") 70 k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace)) 71 k8sClient.DeleteAllOf(ctx, &v1beta1.WorkloadDefinition{}, client.InNamespace(namespace)) 72 k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace)) 73 k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace)) 74 75 Expect(k8sClient.Delete(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed()) 76 Expect(k8sClient.Delete(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: crossNamespace}}, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed()) 77 }) 78 79 It("Test application containing cluster-scoped trait", func() { 80 By("Install TraitDefinition") 81 traitDef := &v1beta1.TraitDefinition{} 82 Expect(yaml.Unmarshal([]byte(fmt.Sprintf(clusterScopeTraitDefYAML, namespace)), traitDef)).Should(Succeed()) 83 Expect(k8sClient.Create(ctx, traitDef)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 84 85 By("Create Application") 86 var ( 87 appName = "cluster-scope-trait-app" 88 app = new(v1beta1.Application) 89 componentName = "cluster-scope-trait-comp" 90 ) 91 app = &v1beta1.Application{ 92 ObjectMeta: metav1.ObjectMeta{ 93 Name: appName, 94 Namespace: namespace, 95 }, 96 Spec: v1beta1.ApplicationSpec{ 97 Components: []common.ApplicationComponent{ 98 { 99 Name: componentName, 100 Type: "worker", 101 Properties: &runtime.RawExtension{Raw: []byte(`{"image": "nginx:latest"}`)}, 102 Traits: []common.ApplicationTrait{{ 103 Type: "cluster-scope-trait", 104 Properties: &runtime.RawExtension{Raw: []byte("{}")}, 105 }}, 106 }, 107 }, 108 }, 109 } 110 Eventually(func() error { 111 return k8sClient.Create(ctx, app) 112 }, 20*time.Second, 2*time.Second).Should(Succeed()) 113 114 By("Verify the trait is created") 115 // sample cluster-scoped trait is PersistentVolume 116 pv := &corev1.PersistentVolume{} 117 Eventually(func() error { 118 return k8sClient.Get(ctx, client.ObjectKey{Name: "pv-" + componentName, Namespace: namespace}, pv) 119 }, 20*time.Second, 500*time.Millisecond).Should(Succeed()) 120 121 By("Delete Application") 122 Expect(k8sClient.Delete(ctx, app)).Should(Succeed()) 123 By("Verify cluster-scoped trait is deleted cascadingly") 124 Eventually(func() error { 125 if err := k8sClient.Get(ctx, client.ObjectKey{Name: "pv-" + componentName, Namespace: namespace}, pv); err != nil { 126 if apierrors.IsNotFound(err) { 127 return nil 128 } 129 return errors.Wrap(err, "PersistentVolume has not deleted") 130 } 131 if ctrlutil.ContainsFinalizer(pv, "kubernetes.io/pv-protection") { 132 return nil 133 } 134 return errors.New("PersistentVolume has not deleted") 135 }, 20*time.Second, 500*time.Millisecond).Should(BeNil()) 136 }) 137 138 It("Test application have cross-namespace workload", func() { 139 // install component definition 140 crossCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace))) 141 ccd := new(v1beta1.ComponentDefinition) 142 Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil()) 143 Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 144 var ( 145 appName = "test-app-1" 146 app = new(v1beta1.Application) 147 componentName = "test-app-1-comp" 148 ) 149 app = &v1beta1.Application{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: appName, 152 Namespace: namespace, 153 }, 154 Spec: v1beta1.ApplicationSpec{ 155 Components: []common.ApplicationComponent{ 156 { 157 Name: componentName, 158 Type: "cross-worker", 159 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 160 }, 161 }, 162 }, 163 } 164 Eventually(func() error { 165 return k8sClient.Create(ctx, app) 166 }, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) 167 By("check resource tracker has been created and app status ") 168 resourceTracker := new(v1beta1.ResourceTracker) 169 Eventually(func() error { 170 app := new(v1beta1.Application) 171 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 172 return fmt.Errorf("app not found %v", err) 173 } 174 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil { 175 return err 176 } 177 if app.Status.Phase != common.ApplicationRunning { 178 return fmt.Errorf("application status is not running") 179 } 180 return nil 181 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 182 By("check resource is generated correctly") 183 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 184 var workload appsv1.Deployment 185 Eventually(func() error { 186 checkRt := new(v1beta1.ResourceTracker) 187 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), checkRt); err != nil { 188 return err 189 } 190 depolys := new(appsv1.DeploymentList) 191 opts := []client.ListOption{ 192 client.InNamespace(crossNamespace), 193 client.MatchingLabels{ 194 oam.LabelAppName: appName, 195 }, 196 } 197 err := k8sClient.List(ctx, depolys, opts...) 198 if err != nil || len(depolys.Items) != 1 { 199 return fmt.Errorf("error workload number %v", err) 200 } 201 workload = depolys.Items[0] 202 if len(checkRt.Spec.ManagedResources) != 1 { 203 return fmt.Errorf("resourceTracker status recode trackedResource length missmatch") 204 } 205 if checkRt.Spec.ManagedResources[0].Name != workload.Name { 206 return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", checkRt.Spec.ManagedResources[0].Name, workload.Name) 207 } 208 return nil 209 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 210 211 By("deleting application will remove resourceTracker and related workload will be removed") 212 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 213 Expect(k8sClient.Delete(ctx, app)).Should(BeNil()) 214 Eventually(func() error { 215 err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker) 216 if err == nil { 217 return fmt.Errorf("resourceTracker still exist") 218 } 219 if !apierrors.IsNotFound(err) { 220 return err 221 } 222 err = k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload) 223 if err == nil { 224 return fmt.Errorf("wrokload still exist") 225 } 226 if !apierrors.IsNotFound(err) { 227 return err 228 } 229 return nil 230 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 231 }) 232 233 It("Test application have two different workload", func() { 234 var ( 235 appName = "test-app-4" 236 app = new(v1beta1.Application) 237 component1Name = "test-app-4-comp-1" 238 component2Name = "test-app-4-comp-2" 239 ) 240 By("install component definition") 241 normalCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(normalCompDefYaml, namespace))) 242 ncd := new(v1beta1.ComponentDefinition) 243 Expect(json.Unmarshal(normalCdJson, ncd)).Should(BeNil()) 244 Expect(k8sClient.Create(ctx, ncd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 245 246 crossCdJson, err := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace))) 247 Expect(err).Should(BeNil()) 248 ctd := new(v1beta1.ComponentDefinition) 249 Expect(json.Unmarshal(crossCdJson, ctd)).Should(BeNil()) 250 Expect(k8sClient.Create(ctx, ctd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 251 252 app = &v1beta1.Application{ 253 ObjectMeta: metav1.ObjectMeta{ 254 Name: appName, 255 Namespace: namespace, 256 }, 257 Spec: v1beta1.ApplicationSpec{ 258 Components: []common.ApplicationComponent{ 259 { 260 Name: component1Name, 261 Type: "normal-worker", 262 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 263 }, 264 { 265 Name: component2Name, 266 Type: "cross-worker", 267 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 268 }, 269 }, 270 }, 271 } 272 273 Eventually(func() error { 274 return k8sClient.Create(ctx, app) 275 }, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) 276 resourceTracker := new(v1beta1.ResourceTracker) 277 278 By("create application will generate two workload, and generate resourceTracker") 279 Eventually(func() error { 280 app = new(v1beta1.Application) 281 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 282 return fmt.Errorf("error to get application %v", err) 283 } 284 if app.Status.Phase != common.ApplicationRunning { 285 return fmt.Errorf("application status not running") 286 } 287 err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker) 288 if err != nil { 289 return fmt.Errorf("error to generate resourceTracker %v", err) 290 } 291 sameOpts := []client.ListOption{ 292 client.InNamespace(namespace), 293 client.MatchingLabels{ 294 oam.LabelAppName: appName, 295 }, 296 } 297 crossOpts := []client.ListOption{ 298 client.InNamespace(crossNamespace), 299 client.MatchingLabels{ 300 oam.LabelAppName: appName, 301 }, 302 } 303 same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList) 304 err = k8sClient.List(ctx, same, sameOpts...) 305 if err != nil || len(same.Items) != 1 { 306 return fmt.Errorf("failed generate same namespace workload") 307 } 308 err = k8sClient.List(ctx, cross, crossOpts...) 309 if err != nil || len(cross.Items) != 1 { 310 return fmt.Errorf("failed generate cross namespace trait") 311 } 312 if len(resourceTracker.Spec.ManagedResources) != 2 { 313 return fmt.Errorf("expect track %q resources, but got %q", 2, len(resourceTracker.Spec.ManagedResources)) 314 } 315 return nil 316 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 317 By("update application by delete cross namespace workload") 318 Eventually(func() error { 319 app = new(v1beta1.Application) 320 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 321 app.Spec.Components = app.Spec.Components[:1] // delete a component 322 return k8sClient.Update(ctx, app) 323 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 324 Eventually(func() error { 325 app = new(v1beta1.Application) 326 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 327 return fmt.Errorf("error to get application %v", err) 328 } 329 if app.Status.Phase != common.ApplicationRunning { 330 return fmt.Errorf("application status not running") 331 } 332 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker); err != nil { 333 return err 334 } 335 sameOpts := []client.ListOption{ 336 client.InNamespace(namespace), 337 client.MatchingLabels{ 338 oam.LabelAppName: appName, 339 }, 340 } 341 crossOpts := []client.ListOption{ 342 client.InNamespace(crossNamespace), 343 client.MatchingLabels{ 344 oam.LabelAppName: appName, 345 }, 346 } 347 same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList) 348 err = k8sClient.List(ctx, same, sameOpts...) 349 if err != nil || len(same.Items) != 1 { 350 return fmt.Errorf("failed generate same namespace workload") 351 } 352 err = k8sClient.List(ctx, cross, crossOpts...) 353 if err != nil || len(cross.Items) != 0 { 354 return fmt.Errorf("error : cross namespace workload still exist") 355 } 356 return nil 357 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 358 }) 359 360 It("Update a cross namespace workload of application", func() { 361 // install component definition 362 crossCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace))) 363 ccd := new(v1beta1.ComponentDefinition) 364 Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil()) 365 Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 366 var ( 367 appName = "test-app-5" 368 app = new(v1beta1.Application) 369 componentName = "test-app-5-comp" 370 ) 371 app = &v1beta1.Application{ 372 ObjectMeta: metav1.ObjectMeta{ 373 Name: appName, 374 Namespace: namespace, 375 }, 376 Spec: v1beta1.ApplicationSpec{ 377 Components: []common.ApplicationComponent{ 378 { 379 Name: componentName, 380 Type: "cross-worker", 381 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 382 }, 383 }, 384 }, 385 } 386 Eventually(func() error { 387 return k8sClient.Create(ctx, app) 388 }, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) 389 By("check resource tracker has been created and app status ") 390 resourceTracker := new(v1beta1.ResourceTracker) 391 Eventually(func() error { 392 app := new(v1beta1.Application) 393 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 394 return fmt.Errorf("app not found %v", err) 395 } 396 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil { 397 return err 398 } 399 if app.Status.Phase != common.ApplicationRunning { 400 return fmt.Errorf("application status is not running") 401 } 402 return nil 403 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 404 By("check resource is generated correctly") 405 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 406 var workload appsv1.Deployment 407 Eventually(func() error { 408 depolys := new(appsv1.DeploymentList) 409 opts := []client.ListOption{ 410 client.InNamespace(crossNamespace), 411 client.MatchingLabels{ 412 oam.LabelAppName: appName, 413 }, 414 } 415 err := k8sClient.List(ctx, depolys, opts...) 416 if err != nil || len(depolys.Items) != 1 { 417 return fmt.Errorf("error workload number %v", err) 418 } 419 workload = depolys.Items[0] 420 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil { 421 return err 422 } 423 if workload.Spec.Template.Spec.Containers[0].Image != "busybox" { 424 return fmt.Errorf("container image not match") 425 } 426 if len(resourceTracker.Spec.ManagedResources) != 1 { 427 return fmt.Errorf("expect track %q resources, but got %q", 1, len(resourceTracker.Spec.ManagedResources)) 428 } 429 return nil 430 }, time.Second*50, time.Millisecond*300).Should(BeNil()) 431 432 By("update application and check resource status") 433 Eventually(func() error { 434 checkApp := new(v1beta1.Application) 435 err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, checkApp) 436 if err != nil { 437 return err 438 } 439 checkApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"nginx"}`)} 440 err = k8sClient.Update(ctx, checkApp) 441 if err != nil { 442 return err 443 } 444 return nil 445 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 446 447 Eventually(func() error { 448 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker); err != nil { 449 return err 450 } 451 if len(resourceTracker.Spec.ManagedResources) != 1 { 452 return fmt.Errorf("expect track %q resources, but got %q", 1, len(resourceTracker.Spec.ManagedResources)) 453 } 454 depolys := new(appsv1.DeploymentList) 455 opts := []client.ListOption{ 456 client.InNamespace(crossNamespace), 457 client.MatchingLabels{ 458 oam.LabelAppName: appName, 459 }, 460 } 461 err := k8sClient.List(ctx, depolys, opts...) 462 if err != nil || len(depolys.Items) != 1 { 463 return fmt.Errorf("error workload number %v", err) 464 } 465 workload = depolys.Items[0] 466 if workload.Spec.Template.Spec.Containers[0].Image != "nginx" { 467 return fmt.Errorf("container image not match") 468 } 469 return nil 470 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 471 472 By("deleting application will remove resourceTracker and related workload will be removed") 473 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 474 Expect(k8sClient.Delete(ctx, app)).Should(BeNil()) 475 Eventually(func() error { 476 err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker) 477 if err == nil { 478 return fmt.Errorf("resourceTracker still exist") 479 } 480 if !apierrors.IsNotFound(err) { 481 return err 482 } 483 err = k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload) 484 if err == nil { 485 return fmt.Errorf("wrokload still exist") 486 } 487 if !apierrors.IsNotFound(err) { 488 return err 489 } 490 return nil 491 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 492 }) 493 494 It("Test cross-namespace resource gc logic, delete a cross-ns component", func() { 495 var ( 496 appName = "test-app-6" 497 app = new(v1beta1.Application) 498 component1Name = "test-app-6-comp-1" 499 component2Name = "test-app-6-comp-2" 500 ) 501 By("install related definition") 502 503 crossCdJson, err := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace))) 504 Expect(err).Should(BeNil()) 505 ctd := new(v1beta1.ComponentDefinition) 506 Expect(json.Unmarshal(crossCdJson, ctd)).Should(BeNil()) 507 Expect(k8sClient.Create(ctx, ctd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 508 509 app = &v1beta1.Application{ 510 ObjectMeta: metav1.ObjectMeta{ 511 Name: appName, 512 Namespace: namespace, 513 }, 514 Spec: v1beta1.ApplicationSpec{ 515 Components: []common.ApplicationComponent{ 516 { 517 Name: component1Name, 518 Type: "cross-worker", 519 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 520 }, 521 { 522 Name: component2Name, 523 Type: "cross-worker", 524 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 525 }, 526 }, 527 }, 528 } 529 530 Eventually(func() error { 531 return k8sClient.Create(ctx, app) 532 }, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) 533 resourceTracker := new(v1beta1.ResourceTracker) 534 535 By("create application will generate two workload, and generate resourceTracker") 536 Eventually(func() error { 537 app = new(v1beta1.Application) 538 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 539 return fmt.Errorf("error to get application %v", err) 540 } 541 if app.Status.Phase != common.ApplicationRunning { 542 return fmt.Errorf("application status not running") 543 } 544 err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker) 545 if err != nil { 546 return fmt.Errorf("error to generate resourceTracker %v", err) 547 } 548 crossOpts := []client.ListOption{ 549 client.InNamespace(crossNamespace), 550 client.MatchingLabels{ 551 oam.LabelAppName: appName, 552 }, 553 } 554 //same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList) 555 workloads := new(appsv1.DeploymentList) 556 err = k8sClient.List(ctx, workloads, crossOpts...) 557 if err != nil || len(workloads.Items) != 2 { 558 return fmt.Errorf("failed get workloads") 559 } 560 deploy1 := workloads.Items[0] 561 deploy2 := workloads.Items[1] 562 if len(resourceTracker.Spec.ManagedResources) != 2 { 563 return fmt.Errorf("expect track %q resources, but got %q", 2, len(resourceTracker.Spec.ManagedResources)) 564 } 565 if resourceTracker.Spec.ManagedResources[0].Namespace != crossNamespace || resourceTracker.Spec.ManagedResources[1].Namespace != crossNamespace { 566 return fmt.Errorf("resourceTracker recorde namespace mismatch") 567 } 568 if resourceTracker.Spec.ManagedResources[0].Name != deploy1.Name && resourceTracker.Spec.ManagedResources[1].Name != deploy1.Name { 569 return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Spec.ManagedResources[0].Name, deploy1.Name) 570 } 571 if resourceTracker.Spec.ManagedResources[0].Name != deploy2.Name && resourceTracker.Spec.ManagedResources[1].Name != deploy2.Name { 572 return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Spec.ManagedResources[0].Name, deploy2.Name) 573 } 574 return nil 575 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 576 By("update application by delete a cross namespace workload") 577 Eventually(func() error { 578 app = new(v1beta1.Application) 579 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 580 app.Spec.Components = app.Spec.Components[:1] // delete a component 581 return k8sClient.Update(ctx, app) 582 }, time.Second*5, time.Millisecond*300).Should(BeNil()) 583 Eventually(func() error { 584 app = new(v1beta1.Application) 585 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 586 return fmt.Errorf("error to get application %v", err) 587 } 588 if app.Status.Phase != common.ApplicationRunning { 589 return fmt.Errorf("application status not running") 590 } 591 err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker) 592 if err != nil { 593 return fmt.Errorf("failed to get resourceTracker %v", err) 594 } 595 crossOpts := []client.ListOption{ 596 client.InNamespace(crossNamespace), 597 client.MatchingLabels{ 598 oam.LabelAppName: appName, 599 }, 600 } 601 workloads := new(appsv1.DeploymentList) 602 err = k8sClient.List(ctx, workloads, crossOpts...) 603 if err != nil || len(workloads.Items) != 1 { 604 return fmt.Errorf("failed get cross namespace workload") 605 } 606 checkRt := new(v1beta1.ResourceTracker) 607 err = k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), checkRt) 608 if err != nil { 609 return fmt.Errorf("error get resourceTracker") 610 } 611 if len(checkRt.Spec.ManagedResources) != 1 { 612 return fmt.Errorf("expect track %q resources, but got %q", 1, len(checkRt.Spec.ManagedResources)) 613 } 614 return nil 615 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 616 617 By("deleting application will remove resourceTracker") 618 app = new(v1beta1.Application) 619 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 620 Expect(k8sClient.Delete(ctx, app)).Should(BeNil()) 621 Eventually(func() error { 622 err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker) 623 if err == nil { 624 return fmt.Errorf("resourceTracker still exist") 625 } 626 if !apierrors.IsNotFound(err) { 627 return err 628 } 629 return nil 630 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 631 }) 632 633 It("Test cross-namespace resource gc logic, update a cross-ns workload's namespace", func() { 634 // install related definition 635 crossCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace))) 636 ccd := new(v1beta1.ComponentDefinition) 637 Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil()) 638 Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 639 640 normalCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(normalCompDefYaml, namespace))) 641 ncd := new(v1beta1.ComponentDefinition) 642 Expect(json.Unmarshal(normalCdJson, ncd)).Should(BeNil()) 643 Expect(k8sClient.Create(ctx, ncd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 644 645 var ( 646 appName = "test-app-8" 647 app = new(v1beta1.Application) 648 componentName = "test-app-8-comp" 649 ) 650 app = &v1beta1.Application{ 651 ObjectMeta: metav1.ObjectMeta{ 652 Name: appName, 653 Namespace: namespace, 654 }, 655 Spec: v1beta1.ApplicationSpec{ 656 Components: []common.ApplicationComponent{ 657 { 658 Name: componentName, 659 Type: "cross-worker", 660 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 661 }, 662 }, 663 }, 664 } 665 Eventually(func() error { 666 return k8sClient.Create(ctx, app) 667 }, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) 668 By("check resource tracker has been created and app status ") 669 resourceTracker := new(v1beta1.ResourceTracker) 670 Eventually(func() error { 671 app := new(v1beta1.Application) 672 if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil { 673 return fmt.Errorf("app not found %v", err) 674 } 675 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil { 676 return err 677 } 678 if app.Status.Phase != common.ApplicationRunning { 679 return fmt.Errorf("application status is not running") 680 } 681 return nil 682 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 683 By("check resource is generated correctly") 684 Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil()) 685 var workload appsv1.Deployment 686 Eventually(func() error { 687 checkRt := new(v1beta1.ResourceTracker) 688 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), checkRt); err != nil { 689 return err 690 } 691 depolys := new(appsv1.DeploymentList) 692 opts := []client.ListOption{ 693 client.InNamespace(crossNamespace), 694 client.MatchingLabels{ 695 oam.LabelAppName: appName, 696 }, 697 } 698 err := k8sClient.List(ctx, depolys, opts...) 699 if err != nil || len(depolys.Items) != 1 { 700 return fmt.Errorf("error workload number %v", err) 701 } 702 workload = depolys.Items[0] 703 if len(checkRt.Spec.ManagedResources) != 1 { 704 return fmt.Errorf("resourceTracker status recode trackedResource length missmatch") 705 } 706 if checkRt.Spec.ManagedResources[0].Name != workload.Name { 707 return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", checkRt.Spec.ManagedResources[0].Name, workload.Name) 708 } 709 return nil 710 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 711 712 By("update application modify workload namespace") 713 Eventually(func() error { 714 app = new(v1beta1.Application) 715 err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app) 716 if err != nil { 717 return err 718 } 719 app.Spec.Components[0].Type = "normal-worker" 720 err = k8sClient.Update(ctx, app) 721 if err != nil { 722 return err 723 } 724 return nil 725 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 726 Eventually(func() error { 727 if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker); err != nil { 728 return err 729 } 730 err := k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload) 731 if err == nil { 732 return fmt.Errorf("wrokload still exist") 733 } 734 if !apierrors.IsNotFound(err) { 735 return err 736 } 737 newWorkload := new(appsv1.Deployment) 738 err = k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workload.GetName()}, newWorkload) 739 if err != nil { 740 return fmt.Errorf("generate same namespace workload error") 741 } 742 return nil 743 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 744 }) 745 }) 746 747 func generateResourceTrackerKey(namespace string, appName string, revision int) types.NamespacedName { 748 return types.NamespacedName{Name: fmt.Sprintf("%s-v%d-%s", appName, revision, namespace)} 749 } 750 751 const ( 752 crossCompDefYaml = ` 753 apiVersion: core.oam.dev/v1beta1 754 kind: ComponentDefinition 755 metadata: 756 name: cross-worker 757 namespace: %s 758 annotations: 759 definition.oam.dev/description: "Long-running scalable backend worker without network endpoint" 760 spec: 761 workload: 762 definition: 763 apiVersion: apps/v1 764 kind: Deployment 765 extension: 766 healthPolicy: | 767 isHealth: context.output.status.readyReplicas == context.output.status.replicas 768 template: | 769 output: { 770 apiVersion: "apps/v1" 771 kind: "Deployment" 772 metadata: { 773 namespace: "%s" 774 } 775 spec: { 776 replicas: 0 777 template: { 778 metadata: labels: { 779 "app.oam.dev/component": context.name 780 } 781 782 spec: { 783 containers: [{ 784 name: context.name 785 image: parameter.image 786 787 if parameter["cmd"] != _|_ { 788 command: parameter.cmd 789 } 790 }] 791 } 792 } 793 794 selector: 795 matchLabels: 796 "app.oam.dev/component": context.name 797 } 798 } 799 800 parameter: { 801 // +usage=Which image would you like to use for your service 802 // +short=i 803 image: string 804 805 cmd?: [...string] 806 } 807 ` 808 normalCompDefYaml = ` 809 apiVersion: core.oam.dev/v1beta1 810 kind: ComponentDefinition 811 metadata: 812 name: normal-worker 813 namespace: %s 814 annotations: 815 definition.oam.dev/description: "Long-running scalable backend worker without network endpoint" 816 spec: 817 workload: 818 definition: 819 apiVersion: apps/v1 820 kind: Deployment 821 extension: 822 healthPolicy: | 823 isHealth: context.output.status.readyReplicas == context.output.status.replicas 824 template: | 825 output: { 826 apiVersion: "apps/v1" 827 kind: "Deployment" 828 spec: { 829 replicas: 0 830 template: { 831 metadata: labels: { 832 "app.oam.dev/component": context.name 833 } 834 835 spec: { 836 containers: [{ 837 name: context.name 838 image: parameter.image 839 840 if parameter["cmd"] != _|_ { 841 command: parameter.cmd 842 } 843 }] 844 } 845 } 846 847 selector: 848 matchLabels: 849 "app.oam.dev/component": context.name 850 } 851 } 852 853 parameter: { 854 // +usage=Which image would you like to use for your service 855 // +short=i 856 image: string 857 858 cmd?: [...string] 859 } 860 ` 861 862 clusterScopeTraitDefYAML = ` 863 apiVersion: core.oam.dev/v1beta1 864 kind: TraitDefinition 865 metadata: 866 name: cluster-scope-trait 867 namespace: %s 868 spec: 869 appliesToWorkloads: 870 - deployments.apps 871 extension: 872 template: |- 873 outputs: pv: { 874 apiVersion: "v1" 875 kind: "PersistentVolume" 876 metadata: name: "pv-\(context.name)" 877 spec: { 878 accessModes: ["ReadWriteOnce"] 879 capacity: storage: "5Gi" 880 persistentVolumeReclaimPolicy: "Retain" 881 storageClassName: "test-sc" 882 csi: { 883 driver: "gcs.csi.ofek.dev" 884 volumeHandle: "csi-gcs" 885 nodePublishSecretRef: { 886 name: "bucket-sa-\(context.name)" 887 namespace: context.namespace 888 } 889 } 890 } 891 } 892 ` 893 )