sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/controllers/clusterresourceset_controller_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes 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 18 19 import ( 20 "crypto/sha1" //nolint: gosec 21 "fmt" 22 "reflect" 23 "testing" 24 "time" 25 26 . "github.com/onsi/gomega" 27 "github.com/pkg/errors" 28 corev1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 "sigs.k8s.io/yaml" 33 34 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 35 addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" 36 "sigs.k8s.io/cluster-api/internal/test/envtest" 37 "sigs.k8s.io/cluster-api/util" 38 "sigs.k8s.io/cluster-api/util/conditions" 39 ) 40 41 const ( 42 timeout = time.Second * 15 43 ) 44 45 func TestClusterResourceSetReconciler(t *testing.T) { 46 var ( 47 clusterResourceSetName string 48 testCluster *clusterv1.Cluster 49 clusterName string 50 labels map[string]string 51 configmapName = "test-configmap" 52 secretName = "test-secret" 53 namespacePrefix = "test-cluster-resource-set" 54 resourceConfigMap1Name = "resource-configmap" 55 resourceConfigMap2Name = "resource-configmap-2" 56 resourceConfigMapsNamespace = "default" 57 ) 58 59 setup := func(t *testing.T, g *WithT) *corev1.Namespace { 60 t.Helper() 61 62 clusterResourceSetName = fmt.Sprintf("clusterresourceset-%s", util.RandomString(6)) 63 labels = map[string]string{clusterResourceSetName: "bar"} 64 65 ns, err := env.CreateNamespace(ctx, namespacePrefix) 66 g.Expect(err).ToNot(HaveOccurred()) 67 68 clusterName = fmt.Sprintf("cluster-%s", util.RandomString(6)) 69 testCluster = &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: ns.Name}} 70 71 t.Log("Creating the Cluster") 72 g.Expect(env.Create(ctx, testCluster)).To(Succeed()) 73 t.Log("Creating the remote Cluster kubeconfig") 74 g.Expect(env.CreateKubeconfigSecret(ctx, testCluster)).To(Succeed()) 75 76 resourceConfigMap1Content := fmt.Sprintf(`metadata: 77 name: %s 78 namespace: %s 79 kind: ConfigMap 80 apiVersion: v1`, resourceConfigMap1Name, resourceConfigMapsNamespace) 81 82 testConfigmap := &corev1.ConfigMap{ 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: configmapName, 85 Namespace: ns.Name, 86 }, 87 Data: map[string]string{ 88 "cm": resourceConfigMap1Content, 89 }, 90 } 91 92 resourceConfigMap2Content := fmt.Sprintf(`metadata: 93 kind: ConfigMap 94 apiVersion: v1 95 metadata: 96 name: %s 97 namespace: %s`, resourceConfigMap2Name, resourceConfigMapsNamespace) 98 testSecret := &corev1.Secret{ 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: secretName, 101 Namespace: ns.Name, 102 }, 103 Type: "addons.cluster.x-k8s.io/resource-set", 104 StringData: map[string]string{ 105 "cm": resourceConfigMap2Content, 106 }, 107 } 108 t.Log("Creating a Secret and a ConfigMap with ConfigMap in their data field") 109 g.Expect(env.Create(ctx, testConfigmap)).To(Succeed()) 110 g.Expect(env.Create(ctx, testSecret)).To(Succeed()) 111 112 return ns 113 } 114 115 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace) { 116 t.Helper() 117 118 t.Log("Deleting the Kubeconfigsecret") 119 secret := &corev1.Secret{ 120 ObjectMeta: metav1.ObjectMeta{ 121 Name: clusterName + "-kubeconfig", 122 Namespace: ns.Name, 123 }, 124 } 125 g.Expect(env.Delete(ctx, secret)).To(Succeed()) 126 127 clusterResourceSetInstance := &addonsv1.ClusterResourceSet{ 128 ObjectMeta: metav1.ObjectMeta{ 129 Name: clusterResourceSetName, 130 Namespace: ns.Name, 131 }, 132 } 133 134 err := env.Get(ctx, client.ObjectKey{Namespace: clusterResourceSetInstance.Namespace, Name: clusterResourceSetInstance.Name}, clusterResourceSetInstance) 135 if err == nil { 136 g.Expect(env.Delete(ctx, clusterResourceSetInstance)).To(Succeed()) 137 } 138 139 g.Eventually(func() bool { 140 crsKey := client.ObjectKey{ 141 Namespace: clusterResourceSetInstance.Namespace, 142 Name: clusterResourceSetInstance.Name, 143 } 144 crs := &addonsv1.ClusterResourceSet{} 145 err := env.Get(ctx, crsKey, crs) 146 return err != nil 147 }, timeout).Should(BeTrue()) 148 149 g.Expect(env.Delete(ctx, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ 150 Name: configmapName, 151 Namespace: ns.Name, 152 }})).To(Succeed()) 153 g.Expect(env.Delete(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ 154 Name: secretName, 155 Namespace: ns.Name, 156 }})).To(Succeed()) 157 158 cm1 := &corev1.ConfigMap{ 159 ObjectMeta: metav1.ObjectMeta{ 160 Name: resourceConfigMap1Name, 161 Namespace: resourceConfigMapsNamespace, 162 }, 163 } 164 if err = env.Get(ctx, client.ObjectKeyFromObject(cm1), cm1); err == nil { 165 g.Expect(env.Delete(ctx, cm1)).To(Succeed()) 166 } 167 cm2 := &corev1.ConfigMap{ 168 ObjectMeta: metav1.ObjectMeta{ 169 Name: resourceConfigMap2Name, 170 Namespace: resourceConfigMapsNamespace, 171 }, 172 } 173 if err = env.Get(ctx, client.ObjectKeyFromObject(cm2), cm2); err == nil { 174 g.Expect(env.Delete(ctx, cm2)).To(Succeed()) 175 } 176 177 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 178 179 clusterKey := client.ObjectKey{Namespace: testCluster.Namespace, Name: testCluster.Name} 180 if err = env.Get(ctx, clusterKey, testCluster); err == nil { 181 g.Expect(env.Delete(ctx, testCluster)).To(Succeed()) 182 } 183 } 184 185 t.Run("Should reconcile a ClusterResourceSet with multiple resources when a cluster with matching label exists", func(t *testing.T) { 186 g := NewWithT(t) 187 ns := setup(t, g) 188 defer teardown(t, g, ns) 189 190 t.Log("Updating the cluster with labels") 191 testCluster.SetLabels(labels) 192 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 193 194 t.Log("Creating a ClusterResourceSet instance that has same labels as selector") 195 clusterResourceSetInstance := &addonsv1.ClusterResourceSet{ 196 ObjectMeta: metav1.ObjectMeta{ 197 Name: clusterResourceSetName, 198 Namespace: ns.Name, 199 }, 200 Spec: addonsv1.ClusterResourceSetSpec{ 201 ClusterSelector: metav1.LabelSelector{ 202 MatchLabels: labels, 203 }, 204 Resources: []addonsv1.ResourceRef{{Name: configmapName, Kind: "ConfigMap"}, {Name: secretName, Kind: "Secret"}}, 205 }, 206 } 207 // Create the ClusterResourceSet. 208 g.Expect(env.Create(ctx, clusterResourceSetInstance)).To(Succeed()) 209 210 t.Log("Verifying ClusterResourceSetBinding is created with cluster owner reference") 211 g.Eventually(clusterResourceSetBindingReady(env, testCluster), timeout).Should(BeTrue()) 212 t.Log("Deleting the Cluster") 213 g.Expect(env.Delete(ctx, testCluster)).To(Succeed()) 214 }) 215 216 t.Run("Should reconcile a cluster when its labels are changed to match a ClusterResourceSet's selector", func(t *testing.T) { 217 g := NewWithT(t) 218 ns := setup(t, g) 219 defer teardown(t, g, ns) 220 221 clusterResourceSetInstance := &addonsv1.ClusterResourceSet{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Name: clusterResourceSetName, 224 Namespace: ns.Name, 225 }, 226 Spec: addonsv1.ClusterResourceSetSpec{ 227 ClusterSelector: metav1.LabelSelector{ 228 MatchLabels: labels, 229 }, 230 }, 231 } 232 // Create the ClusterResourceSet. 233 g.Expect(env.Create(ctx, clusterResourceSetInstance)).To(Succeed()) 234 235 testCluster.SetLabels(labels) 236 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 237 238 t.Log("Verifying ClusterResourceSetBinding is created with cluster name") 239 g.Eventually(func() bool { 240 binding := &addonsv1.ClusterResourceSetBinding{} 241 clusterResourceSetBindingKey := client.ObjectKey{ 242 Namespace: testCluster.Namespace, 243 Name: testCluster.Name, 244 } 245 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 246 if err != nil { 247 return false 248 } 249 250 return binding.Spec.ClusterName == testCluster.Name 251 }, timeout).Should(BeTrue()) 252 253 // Wait until ClusterResourceSetBinding is created for the Cluster 254 clusterResourceSetBindingKey := client.ObjectKey{ 255 Namespace: testCluster.Namespace, 256 Name: testCluster.Name, 257 } 258 g.Eventually(func() bool { 259 binding := &addonsv1.ClusterResourceSetBinding{} 260 261 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 262 return err == nil 263 }, timeout).Should(BeTrue()) 264 265 t.Log("Verifying ClusterResourceSetBinding is deleted when its cluster owner reference is deleted") 266 g.Expect(env.Delete(ctx, testCluster)).To(Succeed()) 267 268 g.Eventually(func() bool { 269 binding := &addonsv1.ClusterResourceSetBinding{} 270 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 271 return apierrors.IsNotFound(err) 272 }, timeout).Should(BeTrue()) 273 }) 274 275 t.Run("Should reconcile a ClusterResourceSet when a ConfigMap resource is created that is part of ClusterResourceSet resources", func(t *testing.T) { 276 g := NewWithT(t) 277 ns := setup(t, g) 278 defer teardown(t, g, ns) 279 280 newCMName := fmt.Sprintf("test-configmap-%s", util.RandomString(6)) 281 282 crsInstance := &addonsv1.ClusterResourceSet{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Name: clusterResourceSetName, 285 Namespace: ns.Name, 286 }, 287 Spec: addonsv1.ClusterResourceSetSpec{ 288 ClusterSelector: metav1.LabelSelector{ 289 MatchLabels: labels, 290 }, 291 Resources: []addonsv1.ResourceRef{{Name: newCMName, Kind: "ConfigMap"}}, 292 }, 293 } 294 // Create the ClusterResourceSet. 295 g.Expect(env.Create(ctx, crsInstance)).To(Succeed()) 296 297 testCluster.SetLabels(labels) 298 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 299 300 t.Log("Verifying ClusterResourceSetBinding is created with cluster owner reference") 301 // Wait until ClusterResourceSetBinding is created for the Cluster 302 clusterResourceSetBindingKey := client.ObjectKey{ 303 Namespace: testCluster.Namespace, 304 Name: testCluster.Name, 305 } 306 g.Eventually(func() bool { 307 binding := &addonsv1.ClusterResourceSetBinding{} 308 309 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 310 return err == nil 311 }, timeout).Should(BeTrue()) 312 313 // Initially ConfigMap is missing, so no resources in the binding. 314 g.Eventually(func() bool { 315 binding := &addonsv1.ClusterResourceSetBinding{} 316 317 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 318 if err == nil { 319 if len(binding.Spec.Bindings) > 0 && len(binding.Spec.Bindings[0].Resources) == 0 { 320 return true 321 } 322 } 323 return false 324 }, timeout).Should(BeTrue()) 325 326 // Must sleep here to make sure resource is created after the previous reconcile. 327 // If the resource is created in between, predicates are not used as intended in this test. 328 time.Sleep(2 * time.Second) 329 330 newConfigmap := &corev1.ConfigMap{ 331 ObjectMeta: metav1.ObjectMeta{ 332 Name: newCMName, 333 Namespace: ns.Name, 334 }, 335 Data: map[string]string{}, 336 } 337 g.Expect(env.Create(ctx, newConfigmap)).To(Succeed()) 338 defer func() { 339 g.Expect(env.Delete(ctx, newConfigmap)).To(Succeed()) 340 }() 341 342 cmKey := client.ObjectKey{ 343 Namespace: ns.Name, 344 Name: newCMName, 345 } 346 g.Eventually(func() bool { 347 m := &corev1.ConfigMap{} 348 err := env.Get(ctx, cmKey, m) 349 return err == nil 350 }, timeout).Should(BeTrue()) 351 352 // When the ConfigMap resource is created, CRS should get reconciled immediately. 353 g.Eventually(func() error { 354 binding := &addonsv1.ClusterResourceSetBinding{} 355 if err := env.Get(ctx, clusterResourceSetBindingKey, binding); err != nil { 356 return err 357 } 358 if len(binding.Spec.Bindings[0].Resources) > 0 && binding.Spec.Bindings[0].Resources[0].Name == newCMName { 359 return nil 360 } 361 return errors.Errorf("ClusterResourceSet binding does not have any resources matching %q: %v", newCMName, binding.Spec.Bindings) 362 }, timeout).Should(Succeed()) 363 364 t.Log("Verifying ClusterResourceSetBinding is deleted when its cluster owner reference is deleted") 365 g.Expect(env.Delete(ctx, testCluster)).To(Succeed()) 366 367 g.Eventually(func() bool { 368 binding := &addonsv1.ClusterResourceSetBinding{} 369 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 370 return apierrors.IsNotFound(err) 371 }, timeout).Should(BeTrue()) 372 }) 373 374 t.Run("Should reconcile a ClusterResourceSet when a Secret resource is created that is part of ClusterResourceSet resources", func(t *testing.T) { 375 g := NewWithT(t) 376 ns := setup(t, g) 377 defer teardown(t, g, ns) 378 379 newSecretName := fmt.Sprintf("test-secret-%s", util.RandomString(6)) 380 381 crsInstance := &addonsv1.ClusterResourceSet{ 382 ObjectMeta: metav1.ObjectMeta{ 383 Name: clusterResourceSetName, 384 Namespace: ns.Name, 385 }, 386 Spec: addonsv1.ClusterResourceSetSpec{ 387 ClusterSelector: metav1.LabelSelector{ 388 MatchLabels: labels, 389 }, 390 Resources: []addonsv1.ResourceRef{{Name: newSecretName, Kind: "Secret"}}, 391 }, 392 } 393 // Create the ClusterResourceSet. 394 g.Expect(env.Create(ctx, crsInstance)).To(Succeed()) 395 396 testCluster.SetLabels(labels) 397 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 398 399 // Must sleep here to make sure resource is created after the previous reconcile. 400 // If the resource is created in between, predicates are not used as intended in this test. 401 time.Sleep(2 * time.Second) 402 403 t.Log("Verifying ClusterResourceSetBinding is created with cluster owner reference") 404 // Wait until ClusterResourceSetBinding is created for the Cluster 405 clusterResourceSetBindingKey := client.ObjectKey{ 406 Namespace: testCluster.Namespace, 407 Name: testCluster.Name, 408 } 409 g.Eventually(func() bool { 410 binding := &addonsv1.ClusterResourceSetBinding{} 411 412 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 413 return err == nil 414 }, timeout).Should(BeTrue()) 415 416 // Initially Secret is missing, so no resources in the binding. 417 g.Eventually(func() bool { 418 binding := &addonsv1.ClusterResourceSetBinding{} 419 420 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 421 if err == nil { 422 if len(binding.Spec.Bindings) > 0 && len(binding.Spec.Bindings[0].Resources) == 0 { 423 return true 424 } 425 } 426 return false 427 }, timeout).Should(BeTrue()) 428 429 newSecret := &corev1.Secret{ 430 ObjectMeta: metav1.ObjectMeta{ 431 Name: newSecretName, 432 Namespace: ns.Name, 433 }, 434 Type: addonsv1.ClusterResourceSetSecretType, 435 Data: map[string][]byte{}, 436 } 437 g.Expect(env.Create(ctx, newSecret)).To(Succeed()) 438 defer func() { 439 g.Expect(env.Delete(ctx, newSecret)).To(Succeed()) 440 }() 441 442 cmKey := client.ObjectKey{ 443 Namespace: ns.Name, 444 Name: newSecretName, 445 } 446 g.Eventually(func() bool { 447 m := &corev1.Secret{} 448 err := env.Get(ctx, cmKey, m) 449 return err == nil 450 }, timeout).Should(BeTrue()) 451 452 // When the Secret resource is created, CRS should get reconciled immediately. 453 g.Eventually(func() error { 454 binding := &addonsv1.ClusterResourceSetBinding{} 455 if err := env.Get(ctx, clusterResourceSetBindingKey, binding); err != nil { 456 return err 457 } 458 if len(binding.Spec.Bindings[0].Resources) > 0 && binding.Spec.Bindings[0].Resources[0].Name == newSecretName { 459 return nil 460 } 461 return errors.Errorf("ClusterResourceSet binding does not have any resources matching %q: %v", newSecretName, binding.Spec.Bindings) 462 }, timeout).Should(Succeed()) 463 464 t.Log("Verifying ClusterResourceSetBinding is deleted when its cluster owner reference is deleted") 465 g.Expect(env.Delete(ctx, testCluster)).To(Succeed()) 466 467 g.Eventually(func() bool { 468 binding := &addonsv1.ClusterResourceSetBinding{} 469 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 470 return apierrors.IsNotFound(err) 471 }, timeout).Should(BeTrue()) 472 }) 473 474 t.Run("Should delete ClusterResourceSet from the bindings list when ClusterResourceSet is deleted", func(t *testing.T) { 475 g := NewWithT(t) 476 ns := setup(t, g) 477 defer teardown(t, g, ns) 478 479 t.Log("Updating the cluster with labels") 480 testCluster.SetLabels(labels) 481 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 482 483 t.Log("Creating a ClusterResourceSet instance that has same labels as selector") 484 clusterResourceSetInstance2 := &addonsv1.ClusterResourceSet{ 485 ObjectMeta: metav1.ObjectMeta{ 486 Name: clusterResourceSetName, 487 Namespace: ns.Name, 488 }, 489 Spec: addonsv1.ClusterResourceSetSpec{ 490 ClusterSelector: metav1.LabelSelector{ 491 MatchLabels: labels, 492 }, 493 Resources: []addonsv1.ResourceRef{{Name: configmapName, Kind: "ConfigMap"}}, 494 }, 495 } 496 // Create the ClusterResourceSet. 497 g.Expect(env.Create(ctx, clusterResourceSetInstance2)).To(Succeed()) 498 499 t.Log("Creating a second ClusterResourceSet instance that has same labels as selector") 500 clusterResourceSetInstance3 := &addonsv1.ClusterResourceSet{ 501 ObjectMeta: metav1.ObjectMeta{ 502 Name: "test-clusterresourceset2", 503 Namespace: ns.Name, 504 }, 505 Spec: addonsv1.ClusterResourceSetSpec{ 506 ClusterSelector: metav1.LabelSelector{ 507 MatchLabels: labels, 508 }, 509 Resources: []addonsv1.ResourceRef{{Name: configmapName, Kind: "ConfigMap"}, {Name: secretName, Kind: "Secret"}}, 510 }, 511 } 512 // Create the ClusterResourceSet. 513 g.Expect(env.Create(ctx, clusterResourceSetInstance3)).To(Succeed()) 514 515 t.Log("Verifying ClusterResourceSetBinding is created with 2 ClusterResourceSets") 516 g.Eventually(func() bool { 517 binding := &addonsv1.ClusterResourceSetBinding{} 518 clusterResourceSetBindingKey := client.ObjectKey{ 519 Namespace: testCluster.Namespace, 520 Name: testCluster.Name, 521 } 522 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 523 if err != nil { 524 return false 525 } 526 return len(binding.Spec.Bindings) == 2 && len(binding.OwnerReferences) == 2 527 }, timeout).Should(BeTrue(), "Expected 2 ClusterResourceSets and 2 OwnerReferences") 528 529 t.Log("Verifying deleted CRS is deleted from ClusterResourceSetBinding") 530 // Delete one of the CRS instances and wait until it is removed from the binding list. 531 g.Expect(env.Delete(ctx, clusterResourceSetInstance2)).To(Succeed()) 532 g.Eventually(func() bool { 533 binding := &addonsv1.ClusterResourceSetBinding{} 534 clusterResourceSetBindingKey := client.ObjectKey{ 535 Namespace: testCluster.Namespace, 536 Name: testCluster.Name, 537 } 538 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 539 if err != nil { 540 return false 541 } 542 return len(binding.Spec.Bindings) == 1 && len(binding.OwnerReferences) == 1 543 }, timeout).Should(BeTrue(), "ClusterResourceSetBinding should have 1 ClusterResourceSet and 1 OwnerReferences") 544 545 t.Log("Verifying ClusterResourceSetBinding is deleted after deleting all matching CRS objects") 546 // Delete one of the CRS instances and wait until it is removed from the binding list. 547 g.Expect(env.Delete(ctx, clusterResourceSetInstance3)).To(Succeed()) 548 g.Eventually(func() bool { 549 binding := &addonsv1.ClusterResourceSetBinding{} 550 clusterResourceSetBindingKey := client.ObjectKey{ 551 Namespace: testCluster.Namespace, 552 Name: testCluster.Name, 553 } 554 return env.Get(ctx, clusterResourceSetBindingKey, binding) != nil 555 }, timeout).Should(BeTrue()) 556 557 t.Log("Deleting the Cluster") 558 g.Expect(env.Delete(ctx, testCluster)).To(Succeed()) 559 }) 560 561 t.Run("Should add finalizer after reconcile", func(t *testing.T) { 562 g := NewWithT(t) 563 ns := setup(t, g) 564 defer teardown(t, g, ns) 565 566 dt := metav1.Now() 567 clusterResourceSetInstance := &addonsv1.ClusterResourceSet{ 568 ObjectMeta: metav1.ObjectMeta{ 569 Name: clusterResourceSetName, 570 Namespace: ns.Name, 571 Finalizers: []string{addonsv1.ClusterResourceSetFinalizer}, 572 DeletionTimestamp: &dt, 573 }, 574 Spec: addonsv1.ClusterResourceSetSpec{ 575 ClusterSelector: metav1.LabelSelector{ 576 MatchLabels: labels, 577 }, 578 }, 579 } 580 // Create the ClusterResourceSet. 581 g.Expect(env.Create(ctx, clusterResourceSetInstance)).To(Succeed()) 582 g.Eventually(func() bool { 583 crsKey := client.ObjectKey{ 584 Namespace: clusterResourceSetInstance.Namespace, 585 Name: clusterResourceSetInstance.Name, 586 } 587 crs := &addonsv1.ClusterResourceSet{} 588 589 err := env.Get(ctx, crsKey, crs) 590 if err == nil { 591 return len(crs.Finalizers) > 0 592 } 593 return false 594 }, timeout).Should(BeTrue()) 595 }) 596 597 t.Run("Should reconcile a ClusterResourceSet with Reconcile strategy after the resources have already been created", func(t *testing.T) { 598 g := NewWithT(t) 599 ns := setup(t, g) 600 defer teardown(t, g, ns) 601 602 t.Log("Updating the cluster with labels") 603 testCluster.SetLabels(labels) 604 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 605 606 t.Log("Creating a ClusterResourceSet instance that has same labels as selector") 607 clusterResourceSet := &addonsv1.ClusterResourceSet{ 608 ObjectMeta: metav1.ObjectMeta{ 609 Name: clusterResourceSetName, 610 Namespace: ns.Name, 611 }, 612 Spec: addonsv1.ClusterResourceSetSpec{ 613 Strategy: string(addonsv1.ClusterResourceSetStrategyReconcile), 614 ClusterSelector: metav1.LabelSelector{ 615 MatchLabels: labels, 616 }, 617 Resources: []addonsv1.ResourceRef{{Name: configmapName, Kind: "ConfigMap"}, {Name: secretName, Kind: "Secret"}}, 618 }, 619 } 620 621 g.Expect(env.Create(ctx, clusterResourceSet)).To(Succeed()) 622 623 t.Log("Verifying ClusterResourceSetBinding is created with cluster owner reference") 624 clusterResourceSetBindingKey := client.ObjectKey{ 625 Namespace: testCluster.Namespace, 626 Name: testCluster.Name, 627 } 628 g.Eventually(clusterResourceSetBindingReady(env, testCluster), timeout).Should(BeTrue()) 629 630 binding := &addonsv1.ClusterResourceSetBinding{} 631 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 632 g.Expect(err).ToNot(HaveOccurred()) 633 resourceHashes := map[string]string{} 634 for _, r := range binding.Spec.Bindings[0].Resources { 635 resourceHashes[r.Name] = r.Hash 636 } 637 638 t.Log("Verifying resource ConfigMap 1 has been created") 639 resourceConfigMap1Key := client.ObjectKey{ 640 Namespace: resourceConfigMapsNamespace, 641 Name: resourceConfigMap1Name, 642 } 643 g.Eventually(func() error { 644 cm := &corev1.ConfigMap{} 645 return env.Get(ctx, resourceConfigMap1Key, cm) 646 }, timeout).Should(Succeed()) 647 648 t.Log("Verifying resource ConfigMap 2 has been created") 649 resourceConfigMap2Key := client.ObjectKey{ 650 Namespace: resourceConfigMapsNamespace, 651 Name: resourceConfigMap2Name, 652 } 653 g.Eventually(func() error { 654 cm := &corev1.ConfigMap{} 655 return env.Get(ctx, resourceConfigMap2Key, cm) 656 }, timeout).Should(Succeed()) 657 658 resourceConfigMap1 := configMap( 659 resourceConfigMap1Name, 660 resourceConfigMapsNamespace, 661 map[string]string{ 662 "my_new_config": "some_value", 663 }, 664 ) 665 666 resourceConfigMap1Content, err := yaml.Marshal(resourceConfigMap1) 667 g.Expect(err).ToNot(HaveOccurred()) 668 669 testConfigmap := configMap( 670 configmapName, 671 ns.Name, 672 map[string]string{ 673 "cm": string(resourceConfigMap1Content), 674 }, 675 ) 676 677 resourceConfigMap2 := configMap( 678 resourceConfigMap2Name, 679 resourceConfigMapsNamespace, 680 map[string]string{ 681 "my_new_secret_config": "some_secret_value", 682 }, 683 ) 684 685 resourceConfigMap2Content, err := yaml.Marshal(resourceConfigMap2) 686 g.Expect(err).ToNot(HaveOccurred()) 687 688 testSecret := &corev1.Secret{ 689 ObjectMeta: metav1.ObjectMeta{ 690 Name: secretName, 691 Namespace: ns.Name, 692 }, 693 Type: "addons.cluster.x-k8s.io/resource-set", 694 StringData: map[string]string{ 695 "cm": string(resourceConfigMap2Content), 696 }, 697 } 698 t.Log("Updating the Secret and a ConfigMap with updated ConfigMaps in their data field") 699 g.Expect(env.Update(ctx, testConfigmap)).To(Succeed()) 700 g.Expect(env.Update(ctx, testSecret)).To(Succeed()) 701 702 t.Log("Verifying ClusterResourceSetBinding has been updated with new hashes") 703 g.Eventually(func() error { 704 binding := &addonsv1.ClusterResourceSetBinding{} 705 if err := env.Get(ctx, clusterResourceSetBindingKey, binding); err != nil { 706 return err 707 } 708 709 for _, r := range binding.Spec.Bindings[0].Resources { 710 if resourceHashes[r.Name] == r.Hash { 711 return errors.Errorf("resource binding for %s hasn't been updated with new hash", r.Name) 712 } 713 } 714 715 return nil 716 }, timeout).Should(Succeed()) 717 718 t.Log("Checking resource ConfigMap 1 has been updated") 719 g.Eventually(configMapHasBeenUpdated(env, resourceConfigMap1Key, resourceConfigMap1), timeout).Should(Succeed()) 720 721 t.Log("Checking resource ConfigMap 2 has been updated") 722 g.Eventually(configMapHasBeenUpdated(env, resourceConfigMap2Key, resourceConfigMap2), timeout).Should(Succeed()) 723 }) 724 725 t.Run("Should reconcile a ClusterResourceSet with ApplyOnce strategy even when one of the resources already exist", func(t *testing.T) { 726 g := NewWithT(t) 727 ns := setup(t, g) 728 defer teardown(t, g, ns) 729 730 t.Log("Updating the cluster with labels") 731 testCluster.SetLabels(labels) 732 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 733 734 t.Log("Creating resource CM before creating CRS") 735 // This CM is defined in the data of "configmapName", which is included in the 736 // CRS we will create in this test 737 resourceConfigMap1 := configMap( 738 resourceConfigMap1Name, 739 resourceConfigMapsNamespace, 740 map[string]string{ 741 "created": "before CRS", 742 }, 743 ) 744 g.Expect(env.Create(ctx, resourceConfigMap1)).To(Succeed()) 745 746 t.Log("Creating a ClusterResourceSet instance that has same labels as selector") 747 clusterResourceSet := &addonsv1.ClusterResourceSet{ 748 ObjectMeta: metav1.ObjectMeta{ 749 Name: clusterResourceSetName, 750 Namespace: ns.Name, 751 }, 752 Spec: addonsv1.ClusterResourceSetSpec{ 753 Strategy: string(addonsv1.ClusterResourceSetStrategyApplyOnce), 754 ClusterSelector: metav1.LabelSelector{ 755 MatchLabels: labels, 756 }, 757 Resources: []addonsv1.ResourceRef{{Name: configmapName, Kind: "ConfigMap"}, {Name: secretName, Kind: "Secret"}}, 758 }, 759 } 760 761 g.Expect(env.Create(ctx, clusterResourceSet)).To(Succeed()) 762 763 t.Log("Checking resource ConfigMap 1 hasn't been updated") 764 resourceConfigMap1Key := client.ObjectKey{ 765 Namespace: resourceConfigMapsNamespace, 766 Name: resourceConfigMap1Name, 767 } 768 g.Eventually(configMapHasBeenUpdated(env, resourceConfigMap1Key, resourceConfigMap1), timeout).Should(Succeed()) 769 770 t.Log("Verifying resource ConfigMap 2 has been created") 771 resourceConfigMap2Key := client.ObjectKey{ 772 Namespace: resourceConfigMapsNamespace, 773 Name: resourceConfigMap2Name, 774 } 775 g.Eventually(func() error { 776 cm := &corev1.ConfigMap{} 777 return env.Get(ctx, resourceConfigMap2Key, cm) 778 }, timeout).Should(Succeed()) 779 }) 780 781 t.Run("Should reconcile a ClusterResourceSet with ApplyOnce strategy even when there is an error, after the error has been corrected", func(t *testing.T) { 782 // To trigger an error in the middle of the reconciliation, we'll define an object in a namespace that doesn't yet exist. 783 // We'll expect the CRS to reconcile all other objects except that one and bubble up the error. 784 // Once that happens, we'll go ahead and create the namespace. Then we'll expect the CRS to, eventually, create that remaining object. 785 786 g := NewWithT(t) 787 ns := setup(t, g) 788 defer teardown(t, g, ns) 789 790 t.Log("Updating the cluster with labels") 791 testCluster.SetLabels(labels) 792 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 793 794 t.Log("Updating the test config map with the missing namespace resource") 795 missingNamespace := randomNamespaceForTest(t) 796 797 resourceConfigMap1 := configMap( 798 resourceConfigMap1Name, 799 resourceConfigMapsNamespace, 800 map[string]string{ 801 "my_new_config": "some_value", 802 }, 803 ) 804 805 resourceConfigMap1Content, err := yaml.Marshal(resourceConfigMap1) 806 g.Expect(err).ToNot(HaveOccurred()) 807 808 resourceConfigMapWithMissingNamespace := configMap( 809 "cm-missing-namespace", 810 missingNamespace, 811 map[string]string{ 812 "my_new_config": "this is all new", 813 }, 814 ) 815 816 resourceConfigMapMissingNamespaceContent, err := yaml.Marshal(resourceConfigMapWithMissingNamespace) 817 g.Expect(err).ToNot(HaveOccurred()) 818 819 testConfigmap := configMap( 820 configmapName, 821 ns.Name, 822 map[string]string{ 823 "cm": string(resourceConfigMap1Content), 824 "problematic_cm": string(resourceConfigMapMissingNamespaceContent), 825 }, 826 ) 827 828 g.Expect(env.Update(ctx, testConfigmap)).To(Succeed()) 829 830 t.Log("Creating a ClusterResourceSet instance that has same labels as selector") 831 clusterResourceSet := &addonsv1.ClusterResourceSet{ 832 ObjectMeta: metav1.ObjectMeta{ 833 Name: clusterResourceSetName, 834 Namespace: ns.Name, 835 }, 836 Spec: addonsv1.ClusterResourceSetSpec{ 837 Strategy: string(addonsv1.ClusterResourceSetStrategyApplyOnce), 838 ClusterSelector: metav1.LabelSelector{ 839 MatchLabels: labels, 840 }, 841 Resources: []addonsv1.ResourceRef{{Name: testConfigmap.Name, Kind: "ConfigMap"}, {Name: secretName, Kind: "Secret"}}, 842 }, 843 } 844 845 g.Expect(env.Create(ctx, clusterResourceSet)).To(Succeed()) 846 847 t.Log("Verifying resource ConfigMap 1 has been created") 848 resourceConfigMap1Key := client.ObjectKeyFromObject(resourceConfigMap1) 849 g.Eventually(configMapHasBeenUpdated(env, resourceConfigMap1Key, resourceConfigMap1), timeout).Should(Succeed()) 850 851 t.Log("Verifying resource ConfigMap 2 has been created") 852 resourceConfigMap2Key := client.ObjectKey{ 853 Namespace: resourceConfigMapsNamespace, 854 Name: resourceConfigMap2Name, 855 } 856 g.Eventually(func() error { 857 cm := &corev1.ConfigMap{} 858 return env.Get(ctx, resourceConfigMap2Key, cm) 859 }, timeout).Should(Succeed()) 860 861 t.Log("Verifying CRS Binding failed marked the resource as not applied") 862 g.Eventually(func(g Gomega) { 863 clusterResourceSetBindingKey := client.ObjectKey{ 864 Namespace: testCluster.Namespace, 865 Name: testCluster.Name, 866 } 867 binding := &addonsv1.ClusterResourceSetBinding{} 868 g.Expect(env.Get(ctx, clusterResourceSetBindingKey, binding)).To(Succeed()) 869 870 g.Expect(binding.Spec.Bindings).To(HaveLen(1)) 871 g.Expect(binding.Spec.Bindings[0].Resources).To(HaveLen(2)) 872 873 for _, r := range binding.Spec.Bindings[0].Resources { 874 switch r.ResourceRef.Name { 875 case testConfigmap.Name: 876 g.Expect(r.Applied).To(BeFalse(), "test-configmap should be not applied bc of missing namespace") 877 case secretName: 878 g.Expect(r.Applied).To(BeTrue(), "test-secret should be applied") 879 } 880 } 881 }, timeout).Should(Succeed()) 882 883 t.Log("Verifying CRS has a false ResourcesApplied condition") 884 g.Eventually(func(g Gomega) { 885 clusterResourceSetKey := client.ObjectKeyFromObject(clusterResourceSet) 886 crs := &addonsv1.ClusterResourceSet{} 887 g.Expect(env.Get(ctx, clusterResourceSetKey, crs)).To(Succeed()) 888 889 appliedCondition := conditions.Get(crs, addonsv1.ResourcesAppliedCondition) 890 g.Expect(appliedCondition).NotTo(BeNil()) 891 g.Expect(appliedCondition.Status).To(Equal(corev1.ConditionFalse)) 892 g.Expect(appliedCondition.Reason).To(Equal(addonsv1.ApplyFailedReason)) 893 g.Expect(appliedCondition.Message).To(ContainSubstring("creating object /v1, Kind=ConfigMap %s/cm-missing-namespace", missingNamespace)) 894 }, timeout).Should(Succeed()) 895 896 t.Log("Creating missing namespace") 897 missingNs := &corev1.Namespace{ 898 ObjectMeta: metav1.ObjectMeta{ 899 Name: missingNamespace, 900 }, 901 } 902 g.Expect(env.Create(ctx, missingNs)).To(Succeed()) 903 904 t.Log("Verifying CRS Binding has all resources applied") 905 g.Eventually(clusterResourceSetBindingReady(env, testCluster), timeout).Should(BeTrue()) 906 907 t.Log("Verifying resource ConfigMap with previously missing namespace has been created") 908 g.Eventually(configMapHasBeenUpdated(env, client.ObjectKeyFromObject(resourceConfigMapWithMissingNamespace), resourceConfigMapWithMissingNamespace), timeout).Should(Succeed()) 909 910 g.Expect(env.Delete(ctx, resourceConfigMapWithMissingNamespace)).To(Succeed()) 911 g.Expect(env.Delete(ctx, missingNs)).To(Succeed()) 912 }) 913 914 t.Run("Should only create ClusterResourceSetBinding after the remote cluster's Kubernetes API Server Service has been created", func(t *testing.T) { 915 g := NewWithT(t) 916 ns := setup(t, g) 917 defer teardown(t, g, ns) 918 919 fakeService := &corev1.Service{ 920 TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"}, 921 ObjectMeta: metav1.ObjectMeta{ 922 Name: "fake", 923 Namespace: metav1.NamespaceDefault, 924 }, 925 Spec: corev1.ServiceSpec{ 926 Ports: []corev1.ServicePort{ 927 { 928 Name: "https", 929 Port: 443, 930 }, 931 }, 932 Type: "ClusterIP", 933 }, 934 } 935 936 kubernetesAPIServerService := &corev1.Service{} 937 t.Log("Verifying Kubernetes API Server Service has been created") 938 g.Expect(env.Get(ctx, client.ObjectKey{Name: "kubernetes", Namespace: metav1.NamespaceDefault}, kubernetesAPIServerService)).To(Succeed()) 939 940 fakeService.Spec.ClusterIP = kubernetesAPIServerService.Spec.ClusterIP 941 942 t.Log("Let Kubernetes API Server Service fail to create by occupying its IP") 943 g.Eventually(func() error { 944 err := env.Delete(ctx, kubernetesAPIServerService) 945 if err != nil { 946 return err 947 } 948 err = env.Create(ctx, fakeService) 949 if err != nil { 950 return err 951 } 952 return nil 953 }, timeout).Should(Succeed()) 954 g.Expect(apierrors.IsNotFound(env.Get(ctx, client.ObjectKeyFromObject(kubernetesAPIServerService), &corev1.Service{}))).To(BeTrue()) 955 956 clusterResourceSetInstance := &addonsv1.ClusterResourceSet{ 957 ObjectMeta: metav1.ObjectMeta{ 958 Name: clusterResourceSetName, 959 Namespace: ns.Name, 960 }, 961 Spec: addonsv1.ClusterResourceSetSpec{ 962 ClusterSelector: metav1.LabelSelector{ 963 MatchLabels: labels, 964 }, 965 }, 966 } 967 // Create the ClusterResourceSet. 968 g.Expect(env.Create(ctx, clusterResourceSetInstance)).To(Succeed()) 969 970 testCluster.SetLabels(labels) 971 g.Expect(env.Update(ctx, testCluster)).To(Succeed()) 972 973 // ClusterResourceSetBinding for the Cluster is not created because the Kubernetes API Server Service doesn't exist. 974 clusterResourceSetBindingKey := client.ObjectKey{Namespace: testCluster.Namespace, Name: testCluster.Name} 975 g.Consistently(func() bool { 976 binding := &addonsv1.ClusterResourceSetBinding{} 977 978 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 979 return apierrors.IsNotFound(err) 980 }, timeout).Should(BeTrue()) 981 982 t.Log("Create Kubernetes API Server Service") 983 g.Expect(env.Delete(ctx, fakeService)).Should(Succeed()) 984 kubernetesAPIServerService.ResourceVersion = "" 985 g.Expect(env.Create(ctx, kubernetesAPIServerService)).Should(Succeed()) 986 987 // Label the CRS to trigger reconciliation. 988 labels["new"] = "" 989 clusterResourceSetInstance.SetLabels(labels) 990 g.Expect(env.Patch(ctx, clusterResourceSetInstance, client.MergeFrom(clusterResourceSetInstance.DeepCopy()))).To(Succeed()) 991 992 // Wait until ClusterResourceSetBinding is created for the Cluster 993 g.Eventually(func() bool { 994 binding := &addonsv1.ClusterResourceSetBinding{} 995 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 996 return err == nil 997 }, timeout).Should(BeTrue()) 998 }) 999 } 1000 1001 func clusterResourceSetBindingReady(env *envtest.Environment, cluster *clusterv1.Cluster) func() bool { 1002 return func() bool { 1003 clusterResourceSetBindingKey := client.ObjectKey{ 1004 Namespace: cluster.Namespace, 1005 Name: cluster.Name, 1006 } 1007 binding := &addonsv1.ClusterResourceSetBinding{} 1008 err := env.Get(ctx, clusterResourceSetBindingKey, binding) 1009 if err != nil { 1010 return false 1011 } 1012 1013 if len(binding.Spec.Bindings) != 1 { 1014 return false 1015 } 1016 if len(binding.Spec.Bindings[0].Resources) != 2 { 1017 return false 1018 } 1019 1020 if !binding.Spec.Bindings[0].Resources[0].Applied || !binding.Spec.Bindings[0].Resources[1].Applied { 1021 return false 1022 } 1023 1024 return binding.Spec.ClusterName == cluster.Name 1025 } 1026 } 1027 1028 func configMapHasBeenUpdated(env *envtest.Environment, key client.ObjectKey, newState *corev1.ConfigMap) func() error { 1029 return func() error { 1030 cm := &corev1.ConfigMap{} 1031 if err := env.Get(ctx, key, cm); err != nil { 1032 return err 1033 } 1034 1035 if !reflect.DeepEqual(cm.Data, newState.Data) { 1036 return errors.Errorf("configMap %s hasn't been updated yet", key.Name) 1037 } 1038 1039 return nil 1040 } 1041 } 1042 1043 func configMap(name, namespace string, data map[string]string) *corev1.ConfigMap { 1044 return &corev1.ConfigMap{ 1045 TypeMeta: metav1.TypeMeta{ 1046 APIVersion: "v1", 1047 Kind: "ConfigMap", 1048 }, 1049 ObjectMeta: metav1.ObjectMeta{ 1050 Name: name, 1051 Namespace: namespace, 1052 }, 1053 Data: data, 1054 } 1055 } 1056 1057 func randomNamespaceForTest(tb testing.TB) string { 1058 tb.Helper() 1059 // This is just to get a short form of the test name 1060 // sha1 is totally fine 1061 h := sha1.New() //nolint: gosec 1062 h.Write([]byte(tb.Name())) 1063 testNameHash := fmt.Sprintf("%x", h.Sum(nil)) 1064 return "ns-" + testNameHash[:7] + "-" + util.RandomString(6) 1065 }