sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/objectgraph_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 cluster 18 19 import ( 20 "context" 21 "fmt" 22 "sort" 23 "testing" 24 25 . "github.com/onsi/gomega" 26 "github.com/pkg/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apimachinery/pkg/util/sets" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" 34 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" 35 "sigs.k8s.io/cluster-api/internal/test/builder" 36 ) 37 38 func TestObjectGraph_getDiscoveryTypeMetaList(t *testing.T) { 39 type fields struct { 40 proxy Proxy 41 } 42 tests := []struct { 43 name string 44 fields fields 45 want map[string]*discoveryTypeInfo 46 wantErr bool 47 }{ 48 { 49 name: "Return CRDs + ConfigMap & Secrets", 50 fields: fields{ 51 proxy: test.NewFakeProxy(). 52 WithObjs( 53 test.FakeNamespacedCustomResourceDefinition("foo", "Bar", "v2", "v1"), // NB. foo/v1 Bar is not a storage version, so it should be ignored 54 test.FakeNamespacedCustomResourceDefinition("foo", "Baz", "v1"), 55 ), 56 }, 57 want: map[string]*discoveryTypeInfo{ 58 "bars.foo": { 59 typeMeta: metav1.TypeMeta{Kind: "Bar", APIVersion: "foo/v2"}, 60 forceMove: false, 61 forceMoveHierarchy: false, 62 scope: "Namespaced", 63 }, 64 "bazs.foo": { 65 typeMeta: metav1.TypeMeta{Kind: "Baz", APIVersion: "foo/v1"}, 66 forceMove: false, 67 forceMoveHierarchy: false, 68 scope: "Namespaced", 69 }, 70 "secrets.v1": { 71 typeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}, 72 forceMove: false, 73 forceMoveHierarchy: false, 74 scope: "", 75 }, 76 "configmaps.v1": { 77 typeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, 78 forceMove: false, 79 forceMoveHierarchy: false, 80 scope: "", 81 }, 82 }, 83 wantErr: false, 84 }, 85 { 86 name: "Enforce force move for Cluster and ClusterResourceSet", 87 fields: fields{ 88 proxy: test.NewFakeProxy(). 89 WithObjs( 90 test.FakeNamespacedCustomResourceDefinition("cluster.x-k8s.io", "Cluster", "v1beta1"), 91 test.FakeNamespacedCustomResourceDefinition("addons.cluster.x-k8s.io", "ClusterResourceSet", "v1beta1"), 92 ), 93 }, 94 want: map[string]*discoveryTypeInfo{ 95 "clusters.cluster.x-k8s.io": { 96 typeMeta: metav1.TypeMeta{Kind: "Cluster", APIVersion: "cluster.x-k8s.io/v1beta1"}, 97 forceMove: true, 98 forceMoveHierarchy: true, 99 scope: "Namespaced", 100 }, 101 "clusterresourcesets.addons.cluster.x-k8s.io": { 102 typeMeta: metav1.TypeMeta{Kind: "ClusterResourceSet", APIVersion: "addons.cluster.x-k8s.io/v1beta1"}, 103 forceMove: true, 104 forceMoveHierarchy: true, 105 scope: "Namespaced", 106 }, 107 "secrets.v1": { 108 typeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}, 109 forceMove: false, 110 forceMoveHierarchy: false, 111 scope: "", 112 }, 113 "configmaps.v1": { 114 typeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, 115 forceMove: false, 116 forceMoveHierarchy: false, 117 scope: "", 118 }, 119 }, 120 wantErr: false, 121 }, 122 { 123 name: "Identified Cluster scoped types", 124 fields: fields{ 125 proxy: test.NewFakeProxy(). 126 WithObjs( 127 test.FakeClusterCustomResourceDefinition("infrastructure.cluster.x-k8s.io", "GenericClusterInfrastructureIdentity", "v1beta1"), 128 ), 129 }, 130 want: map[string]*discoveryTypeInfo{ 131 "genericclusterinfrastructureidentitys.infrastructure.cluster.x-k8s.io": { 132 typeMeta: metav1.TypeMeta{Kind: "GenericClusterInfrastructureIdentity", APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1"}, 133 forceMove: false, 134 forceMoveHierarchy: false, 135 scope: "Cluster", 136 }, 137 "secrets.v1": { 138 typeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}, 139 forceMove: false, 140 forceMoveHierarchy: false, 141 scope: "", 142 }, 143 "configmaps.v1": { 144 typeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, 145 forceMove: false, 146 forceMoveHierarchy: false, 147 scope: "", 148 }, 149 }, 150 wantErr: false, 151 }, 152 { 153 name: "Identified force move label", 154 fields: fields{ 155 proxy: test.NewFakeProxy(). 156 WithObjs( 157 func() client.Object { 158 crd := test.FakeNamespacedCustomResourceDefinition("foo", "Bar", "v1") 159 crd.Labels[clusterctlv1.ClusterctlMoveLabel] = "" 160 return crd 161 }(), 162 ), 163 }, 164 want: map[string]*discoveryTypeInfo{ 165 "bars.foo": { 166 typeMeta: metav1.TypeMeta{Kind: "Bar", APIVersion: "foo/v1"}, 167 forceMove: true, 168 forceMoveHierarchy: false, 169 scope: "Namespaced", 170 }, 171 "secrets.v1": { 172 typeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}, 173 forceMove: false, 174 forceMoveHierarchy: false, 175 scope: "", 176 }, 177 "configmaps.v1": { 178 typeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, 179 forceMove: false, 180 forceMoveHierarchy: false, 181 scope: "", 182 }, 183 }, 184 wantErr: false, 185 }, 186 { 187 name: "Identified force move hierarchy label", 188 fields: fields{ 189 proxy: test.NewFakeProxy(). 190 WithObjs( 191 func() client.Object { 192 crd := test.FakeNamespacedCustomResourceDefinition("foo", "Bar", "v1") 193 crd.Labels[clusterctlv1.ClusterctlMoveHierarchyLabel] = "" 194 return crd 195 }(), 196 ), 197 }, 198 want: map[string]*discoveryTypeInfo{ 199 "bars.foo": { 200 typeMeta: metav1.TypeMeta{Kind: "Bar", APIVersion: "foo/v1"}, 201 forceMove: true, // force move is implicit when there is forceMoveHierarchy 202 forceMoveHierarchy: true, 203 scope: "Namespaced", 204 }, 205 "secrets.v1": { 206 typeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}, 207 forceMove: false, 208 forceMoveHierarchy: false, 209 scope: "", 210 }, 211 "configmaps.v1": { 212 typeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, 213 forceMove: false, 214 forceMoveHierarchy: false, 215 scope: "", 216 }, 217 }, 218 wantErr: false, 219 }, 220 } 221 for _, tt := range tests { 222 t.Run(tt.name, func(t *testing.T) { 223 g := NewWithT(t) 224 225 ctx := context.Background() 226 227 graph := newObjectGraph(tt.fields.proxy, nil) 228 err := graph.getDiscoveryTypes(ctx) 229 if tt.wantErr { 230 g.Expect(err).To(HaveOccurred()) 231 return 232 } 233 234 g.Expect(err).ToNot(HaveOccurred()) 235 g.Expect(graph.types).To(Equal(tt.want)) 236 }) 237 } 238 } 239 240 type wantGraphItem struct { 241 virtual bool 242 isGlobal bool 243 forceMove bool 244 forceMoveHierarchy bool 245 owners []string 246 softOwners []string 247 } 248 249 type wantGraph struct { 250 nodes map[string]wantGraphItem 251 } 252 253 func assertGraph(t *testing.T, got *objectGraph, want wantGraph) { 254 t.Helper() 255 256 g := NewWithT(t) 257 258 g.Expect(got.uidToNode).To(HaveLen(len(want.nodes)), "the number of nodes in the objectGraph doesn't match the number of expected nodes - got: %d expected: %d", len(got.uidToNode), len(want.nodes)) 259 260 for uid, wantNode := range want.nodes { 261 gotNode, ok := got.uidToNode[types.UID(uid)] 262 g.Expect(ok).To(BeTrue(), "node %q not found", uid) 263 g.Expect(gotNode.virtual).To(Equal(wantNode.virtual), "node %q.virtual does not have the expected value", uid) 264 g.Expect(gotNode.isGlobal).To(Equal(wantNode.isGlobal), "node %q.isGlobal does not have the expected value", uid) 265 g.Expect(gotNode.forceMove).To(Equal(wantNode.forceMove), "node %q.forceMove does not have the expected value", uid) 266 g.Expect(gotNode.forceMoveHierarchy).To(Equal(wantNode.forceMoveHierarchy), "node %q.forceMoveHierarchy does not have the expected value", uid) 267 g.Expect(gotNode.owners).To(HaveLen(len(wantNode.owners)), "node %q.owner does not have the expected length", uid) 268 269 for _, wantOwner := range wantNode.owners { 270 found := false 271 for k := range gotNode.owners { 272 if k.identity.UID == types.UID(wantOwner) { 273 found = true 274 break 275 } 276 } 277 g.Expect(found).To(BeTrue(), "node %q.owners does not contain %q", uid, wantOwner) 278 } 279 280 g.Expect(gotNode.softOwners).To(HaveLen(len(wantNode.softOwners)), "node %q.softOwners does not have the expected length", uid) 281 282 for _, wantOwner := range wantNode.softOwners { 283 found := false 284 for k := range gotNode.softOwners { 285 if k.identity.UID == types.UID(wantOwner) { 286 found = true 287 break 288 } 289 } 290 g.Expect(found).To(BeTrue(), "node %q.softOwners does not contain %q", uid, wantOwner) 291 } 292 } 293 } 294 295 func TestObjectGraph_addObj(t *testing.T) { 296 type args struct { 297 objs []*unstructured.Unstructured 298 } 299 300 tests := []struct { 301 name string 302 args args 303 want wantGraph 304 }{ 305 { 306 name: "Add a single object", 307 args: args{ 308 objs: []*unstructured.Unstructured{ 309 { 310 Object: map[string]interface{}{ 311 "apiVersion": "a/v1", 312 "kind": "A", 313 "metadata": map[string]interface{}{ 314 "namespace": "ns", 315 "name": "foo", 316 "uid": "1", 317 }, 318 }, 319 }, 320 }, 321 }, 322 want: wantGraph{ 323 nodes: map[string]wantGraphItem{ 324 "1": { // the object: not virtual (observed), without owner ref 325 virtual: false, 326 owners: nil, 327 }, 328 }, 329 }, 330 }, 331 { 332 name: "Add a single object with an owner ref", 333 args: args{ 334 objs: []*unstructured.Unstructured{ 335 { 336 Object: map[string]interface{}{ 337 "apiVersion": "a/v1", 338 "kind": "A", 339 "metadata": map[string]interface{}{ 340 "namespace": "ns", 341 "name": "foo", 342 "uid": "1", 343 "ownerReferences": []interface{}{ 344 map[string]interface{}{ 345 "apiVersion": "b/v1", 346 "kind": "B", 347 "name": "bar", 348 "uid": "2", 349 }, 350 }, 351 }, 352 }, 353 }, 354 }, 355 }, 356 want: wantGraph{ 357 nodes: map[string]wantGraphItem{ 358 "1": { // the object: not virtual (observed), with 1 owner refs 359 virtual: false, 360 owners: []string{"2"}, 361 }, 362 "2": { // the object owner: virtual (not yet observed), without owner refs 363 virtual: true, 364 owners: nil, 365 }, 366 }, 367 }, 368 }, 369 { 370 name: "Add an object with an owner ref and its owner", 371 args: args{ 372 objs: []*unstructured.Unstructured{ 373 { 374 Object: map[string]interface{}{ 375 "apiVersion": "a/v1", 376 "kind": "A", 377 "metadata": map[string]interface{}{ 378 "namespace": "ns", 379 "name": "foo", 380 "uid": "1", 381 "ownerReferences": []interface{}{ 382 map[string]interface{}{ 383 "apiVersion": "b/v1", 384 "kind": "B", 385 "name": "bar", 386 "uid": "2", 387 }, 388 }, 389 }, 390 }, 391 }, 392 { 393 Object: map[string]interface{}{ 394 "apiVersion": "b/v1", 395 "kind": "B", 396 "metadata": map[string]interface{}{ 397 "namespace": "ns", 398 "name": "bar", 399 "uid": "2", 400 }, 401 }, 402 }, 403 }, 404 }, 405 want: wantGraph{ 406 nodes: map[string]wantGraphItem{ 407 "1": { // the object: not virtual (observed), with 1 owner refs 408 virtual: false, 409 owners: []string{"2"}, 410 }, 411 "2": { // the object owner: not virtual (observed), without owner refs 412 virtual: false, 413 owners: nil, 414 }, 415 }, 416 }, 417 }, 418 { 419 name: "Add an object with an owner ref and its owner (reverse discovery order)", 420 args: args{ 421 objs: []*unstructured.Unstructured{ 422 { 423 Object: map[string]interface{}{ 424 "apiVersion": "b/v1", 425 "kind": "B", 426 "metadata": map[string]interface{}{ 427 "namespace": "ns", 428 "name": "bar", 429 "uid": "2", 430 }, 431 }, 432 }, 433 { 434 Object: map[string]interface{}{ 435 "apiVersion": "a/v1", 436 "kind": "A", 437 "metadata": map[string]interface{}{ 438 "namespace": "ns", 439 "name": "foo", 440 "uid": "1", 441 "ownerReferences": []interface{}{ 442 map[string]interface{}{ 443 "apiVersion": "b/v1", 444 "kind": "B", 445 "name": "bar", 446 "uid": "2", 447 }, 448 }, 449 }, 450 }, 451 }, 452 }, 453 }, 454 want: wantGraph{ 455 nodes: map[string]wantGraphItem{ 456 "1": { // the object: not virtual (observed), with 1 owner refs 457 virtual: false, 458 owners: []string{"2"}, 459 }, 460 "2": { // the object owner: not virtual (observed), without owner refs 461 virtual: false, 462 owners: nil, 463 }, 464 }, 465 }, 466 }, 467 } 468 for _, tt := range tests { 469 t.Run(tt.name, func(t *testing.T) { 470 graph := newObjectGraph(nil, nil) 471 for _, o := range tt.args.objs { 472 if err := graph.addObj(o); err != nil { 473 panic(fmt.Sprintf("failed when adding object to graph: %v", o)) 474 } 475 } 476 477 assertGraph(t, graph, tt.want) 478 }) 479 } 480 } 481 482 type objectGraphTestArgs struct { 483 objs []client.Object 484 } 485 486 var objectGraphsTests = []struct { 487 name string 488 args objectGraphTestArgs 489 want wantGraph 490 wantErr bool 491 }{ 492 { 493 name: "Cluster", 494 args: objectGraphTestArgs{ 495 objs: test.NewFakeCluster("ns1", "cluster1").Objs(), 496 }, 497 want: wantGraph{ 498 nodes: map[string]wantGraphItem{ 499 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 500 forceMove: true, 501 forceMoveHierarchy: true, 502 }, 503 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 504 owners: []string{ 505 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 506 }, 507 }, 508 "/v1, Kind=Secret, ns1/cluster1-ca": { 509 softOwners: []string{ 510 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 511 }, 512 }, 513 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 514 owners: []string{ 515 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 516 }, 517 }, 518 }, 519 }, 520 }, 521 { 522 name: "Cluster with cloud config secret with the force move label", 523 args: objectGraphTestArgs{ 524 objs: test.NewFakeCluster("ns1", "cluster1"). 525 WithCloudConfigSecret().Objs(), 526 }, 527 want: wantGraph{ 528 nodes: map[string]wantGraphItem{ 529 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 530 forceMove: true, 531 forceMoveHierarchy: true, 532 }, 533 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 534 owners: []string{ 535 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 536 }, 537 }, 538 "/v1, Kind=Secret, ns1/cluster1-ca": { 539 softOwners: []string{ 540 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 541 }, 542 }, 543 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 544 owners: []string{ 545 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 546 }, 547 }, 548 "/v1, Kind=Secret, ns1/cluster1-cloud-config": { 549 forceMove: true, 550 }, 551 }, 552 }, 553 }, 554 { 555 name: "Two clusters", 556 args: objectGraphTestArgs{ 557 objs: func() []client.Object { 558 objs := []client.Object{} 559 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 560 objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) 561 return objs 562 }(), 563 }, 564 want: wantGraph{ 565 nodes: map[string]wantGraphItem{ 566 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 567 forceMove: true, 568 forceMoveHierarchy: true, 569 }, 570 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 571 owners: []string{ 572 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 573 }, 574 }, 575 "/v1, Kind=Secret, ns1/cluster1-ca": { 576 softOwners: []string{ 577 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 578 }, 579 }, 580 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 581 owners: []string{ 582 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 583 }, 584 }, 585 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2": { 586 forceMove: true, 587 forceMoveHierarchy: true, 588 }, 589 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster2": { 590 owners: []string{ 591 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 592 }, 593 }, 594 "/v1, Kind=Secret, ns1/cluster2-ca": { 595 softOwners: []string{ 596 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", // NB. this secret is not linked to the cluster through owner ref 597 }, 598 }, 599 "/v1, Kind=Secret, ns1/cluster2-kubeconfig": { 600 owners: []string{ 601 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 602 }, 603 }, 604 }, 605 }, 606 }, 607 { 608 name: "Cluster with machine", 609 args: objectGraphTestArgs{ 610 objs: test.NewFakeCluster("ns1", "cluster1"). 611 WithMachines( 612 test.NewFakeMachine("m1"), 613 ).Objs(), 614 }, 615 want: wantGraph{ 616 nodes: map[string]wantGraphItem{ 617 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 618 forceMove: true, 619 forceMoveHierarchy: true, 620 }, 621 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 622 owners: []string{ 623 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 624 }, 625 }, 626 "/v1, Kind=Secret, ns1/cluster1-ca": { 627 softOwners: []string{ 628 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 629 }, 630 }, 631 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 632 owners: []string{ 633 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 634 }, 635 }, 636 637 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1": { 638 owners: []string{ 639 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 640 }, 641 }, 642 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1": { 643 owners: []string{ 644 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 645 }, 646 }, 647 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1": { 648 owners: []string{ 649 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 650 }, 651 }, 652 "/v1, Kind=Secret, ns1/m1": { 653 owners: []string{ 654 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1", 655 }, 656 }, 657 "/v1, Kind=Secret, ns1/cluster1-sa": { 658 owners: []string{ 659 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1", 660 }, 661 }, 662 }, 663 }, 664 }, 665 { 666 name: "Cluster with MachineSet", 667 args: objectGraphTestArgs{ 668 objs: test.NewFakeCluster("ns1", "cluster1"). 669 WithMachineSets( 670 test.NewFakeMachineSet("ms1"). 671 WithMachines(test.NewFakeMachine("m1")), 672 ).Objs(), 673 }, 674 want: wantGraph{ 675 nodes: map[string]wantGraphItem{ 676 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 677 forceMove: true, 678 forceMoveHierarchy: true, 679 }, 680 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 681 owners: []string{ 682 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 683 }, 684 }, 685 "/v1, Kind=Secret, ns1/cluster1-ca": { 686 softOwners: []string{ 687 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 688 }, 689 }, 690 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 691 owners: []string{ 692 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 693 }, 694 }, 695 696 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/ms1": { 697 owners: []string{ 698 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 699 }, 700 }, 701 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/ms1": { 702 owners: []string{ 703 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 704 }, 705 }, 706 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/ms1": { 707 owners: []string{ 708 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 709 }, 710 }, 711 712 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1": { 713 owners: []string{ 714 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/ms1", 715 }, 716 }, 717 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1": { 718 owners: []string{ 719 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 720 }, 721 }, 722 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1": { 723 owners: []string{ 724 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 725 }, 726 }, 727 "/v1, Kind=Secret, ns1/m1": { 728 owners: []string{ 729 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1", 730 }, 731 }, 732 }, 733 }, 734 }, 735 { 736 name: "Cluster with MachineDeployment", 737 args: objectGraphTestArgs{ 738 objs: test.NewFakeCluster("ns1", "cluster1"). 739 WithMachineDeployments( 740 test.NewFakeMachineDeployment("md1"). 741 WithMachineSets( 742 test.NewFakeMachineSet("ms1"). 743 WithMachines( 744 test.NewFakeMachine("m1"), 745 ), 746 ), 747 ).Objs(), 748 }, 749 want: wantGraph{ 750 nodes: map[string]wantGraphItem{ 751 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 752 forceMove: true, 753 forceMoveHierarchy: true, 754 }, 755 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 756 owners: []string{ 757 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 758 }, 759 }, 760 "/v1, Kind=Secret, ns1/cluster1-ca": { 761 softOwners: []string{ 762 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 763 }, 764 }, 765 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 766 owners: []string{ 767 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 768 }, 769 }, 770 771 "cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": { 772 owners: []string{ 773 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 774 }, 775 }, 776 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": { 777 owners: []string{ 778 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 779 }, 780 }, 781 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": { 782 owners: []string{ 783 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 784 }, 785 }, 786 787 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/ms1": { 788 owners: []string{ 789 "cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1", 790 }, 791 }, 792 793 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1": { 794 owners: []string{ 795 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/ms1", 796 }, 797 }, 798 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1": { 799 owners: []string{ 800 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 801 }, 802 }, 803 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1": { 804 owners: []string{ 805 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 806 }, 807 }, 808 "/v1, Kind=Secret, ns1/m1": { 809 owners: []string{ 810 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1", 811 }, 812 }, 813 }, 814 }, 815 }, 816 { 817 name: "Cluster with MachineDeployment without a BootstrapConfigRef", 818 args: objectGraphTestArgs{ 819 objs: test.NewFakeCluster("ns1", "cluster1"). 820 WithMachineDeployments( 821 test.NewFakeMachineDeployment("md1"). 822 WithStaticBootstrapConfig(). 823 WithMachineSets( 824 test.NewFakeMachineSet("ms1"). 825 WithMachines( 826 test.NewFakeMachine("m1"), 827 ), 828 ), 829 ).Objs(), 830 }, 831 want: wantGraph{ 832 nodes: map[string]wantGraphItem{ 833 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 834 forceMove: true, 835 forceMoveHierarchy: true, 836 }, 837 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 838 owners: []string{ 839 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 840 }, 841 }, 842 "/v1, Kind=Secret, ns1/cluster1-ca": { 843 softOwners: []string{ 844 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 845 }, 846 }, 847 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 848 owners: []string{ 849 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 850 }, 851 }, 852 853 "cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": { 854 owners: []string{ 855 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 856 }, 857 }, 858 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": { 859 owners: []string{ 860 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 861 }, 862 }, 863 864 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/ms1": { 865 owners: []string{ 866 "cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1", 867 }, 868 }, 869 870 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1": { 871 owners: []string{ 872 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/ms1", 873 }, 874 }, 875 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1": { 876 owners: []string{ 877 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 878 }, 879 }, 880 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1": { 881 owners: []string{ 882 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 883 }, 884 }, 885 "/v1, Kind=Secret, ns1/m1": { 886 owners: []string{ 887 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1", 888 }, 889 }, 890 }, 891 }, 892 }, 893 { 894 name: "Cluster with Control Plane", 895 args: objectGraphTestArgs{ 896 objs: test.NewFakeCluster("ns1", "cluster1"). 897 WithControlPlane( 898 test.NewFakeControlPlane("cp1"). 899 WithMachines( 900 test.NewFakeMachine("m1"), 901 ), 902 ).Objs(), 903 }, 904 want: wantGraph{ 905 nodes: map[string]wantGraphItem{ 906 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 907 forceMove: true, 908 forceMoveHierarchy: true, 909 }, 910 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 911 owners: []string{ 912 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 913 }, 914 }, 915 "/v1, Kind=Secret, ns1/cluster1-ca": { 916 softOwners: []string{ 917 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 918 }, 919 }, 920 921 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp1": { 922 owners: []string{ 923 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 924 }, 925 }, 926 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": { 927 owners: []string{ 928 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 929 }, 930 }, 931 "/v1, Kind=Secret, ns1/cluster1-sa": { 932 owners: []string{ 933 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp1", 934 }, 935 }, 936 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 937 owners: []string{ 938 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp1", 939 }, 940 }, 941 942 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1": { 943 owners: []string{ 944 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp1", 945 }, 946 }, 947 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1": { 948 owners: []string{ 949 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 950 }, 951 }, 952 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1": { 953 owners: []string{ 954 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1", 955 }, 956 }, 957 "/v1, Kind=Secret, ns1/m1": { 958 owners: []string{ 959 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1", 960 }, 961 }, 962 }, 963 }, 964 }, 965 { 966 name: "Cluster with MachinePool", 967 args: objectGraphTestArgs{ 968 objs: test.NewFakeCluster("ns1", "cluster1"). 969 WithMachinePools( 970 test.NewFakeMachinePool("mp1"), 971 ).Objs(), 972 }, 973 want: wantGraph{ 974 nodes: map[string]wantGraphItem{ 975 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 976 forceMove: true, 977 forceMoveHierarchy: true, 978 }, 979 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 980 owners: []string{ 981 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 982 }, 983 }, 984 "/v1, Kind=Secret, ns1/cluster1-ca": { 985 softOwners: []string{ 986 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 987 }, 988 }, 989 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 990 owners: []string{ 991 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 992 }, 993 }, 994 995 "cluster.x-k8s.io/v1beta1, Kind=MachinePool, ns1/mp1": { 996 owners: []string{ 997 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 998 }, 999 }, 1000 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/mp1": { 1001 owners: []string{ 1002 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1003 }, 1004 }, 1005 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/mp1": { 1006 owners: []string{ 1007 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1008 }, 1009 }, 1010 }, 1011 }, 1012 }, 1013 { 1014 name: "Two clusters with shared objects", 1015 args: objectGraphTestArgs{ 1016 objs: func() []client.Object { 1017 sharedInfrastructureTemplate := test.NewFakeInfrastructureTemplate("shared") 1018 1019 objs := []client.Object{ 1020 sharedInfrastructureTemplate, 1021 } 1022 1023 objs = append(objs, test.NewFakeCluster("ns1", "cluster1"). 1024 WithMachineSets( 1025 test.NewFakeMachineSet("cluster1-ms1"). 1026 WithInfrastructureTemplate(sharedInfrastructureTemplate). 1027 WithMachines( 1028 test.NewFakeMachine("cluster1-m1"), 1029 ), 1030 ).Objs()...) 1031 1032 objs = append(objs, test.NewFakeCluster("ns1", "cluster2"). 1033 WithMachineSets( 1034 test.NewFakeMachineSet("cluster2-ms1"). 1035 WithInfrastructureTemplate(sharedInfrastructureTemplate). 1036 WithMachines( 1037 test.NewFakeMachine("cluster2-m1"), 1038 ), 1039 ).Objs()...) 1040 1041 return objs 1042 }(), 1043 }, 1044 want: wantGraph{ 1045 nodes: map[string]wantGraphItem{ 1046 1047 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/shared": { 1048 owners: []string{ 1049 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1050 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1051 }, 1052 }, 1053 1054 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1055 forceMove: true, 1056 forceMoveHierarchy: true, 1057 }, 1058 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1059 owners: []string{ 1060 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1061 }, 1062 }, 1063 "/v1, Kind=Secret, ns1/cluster1-ca": { 1064 softOwners: []string{ 1065 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 1066 }, 1067 }, 1068 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 1069 owners: []string{ 1070 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1071 }, 1072 }, 1073 1074 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/cluster1-ms1": { 1075 owners: []string{ 1076 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1077 }, 1078 }, 1079 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/cluster1-ms1": { 1080 owners: []string{ 1081 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1082 }, 1083 }, 1084 1085 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster1-m1": { 1086 owners: []string{ 1087 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/cluster1-ms1", 1088 }, 1089 }, 1090 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cluster1-m1": { 1091 owners: []string{ 1092 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster1-m1", 1093 }, 1094 }, 1095 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cluster1-m1": { 1096 owners: []string{ 1097 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster1-m1", 1098 }, 1099 }, 1100 "/v1, Kind=Secret, ns1/cluster1-m1": { 1101 owners: []string{ 1102 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cluster1-m1", 1103 }, 1104 }, 1105 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2": { 1106 forceMove: true, 1107 forceMoveHierarchy: true, 1108 }, 1109 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster2": { 1110 owners: []string{ 1111 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1112 }, 1113 }, 1114 "/v1, Kind=Secret, ns1/cluster2-ca": { 1115 softOwners: []string{ 1116 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", // NB. this secret is not linked to the cluster through owner ref 1117 }, 1118 }, 1119 "/v1, Kind=Secret, ns1/cluster2-kubeconfig": { 1120 owners: []string{ 1121 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1122 }, 1123 }, 1124 1125 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/cluster2-ms1": { 1126 owners: []string{ 1127 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1128 }, 1129 }, 1130 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/cluster2-ms1": { 1131 owners: []string{ 1132 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1133 }, 1134 }, 1135 1136 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster2-m1": { 1137 owners: []string{ 1138 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/cluster2-ms1", 1139 }, 1140 }, 1141 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cluster2-m1": { 1142 owners: []string{ 1143 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster2-m1", 1144 }, 1145 }, 1146 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cluster2-m1": { 1147 owners: []string{ 1148 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster2-m1", 1149 }, 1150 }, 1151 "/v1, Kind=Secret, ns1/cluster2-m1": { 1152 owners: []string{ 1153 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cluster2-m1", 1154 }, 1155 }, 1156 }, 1157 }, 1158 }, 1159 { 1160 name: "A ClusterResourceSet applied to a cluster", 1161 args: objectGraphTestArgs{ 1162 objs: func() []client.Object { 1163 objs := []client.Object{} 1164 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 1165 1166 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 1167 WithSecret("resource-s1"). 1168 WithConfigMap("resource-c1"). 1169 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 1170 Objs()...) 1171 1172 return objs 1173 }(), 1174 }, 1175 want: wantGraph{ 1176 nodes: map[string]wantGraphItem{ 1177 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1178 forceMove: true, 1179 forceMoveHierarchy: true, 1180 }, 1181 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1182 owners: []string{ 1183 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1184 }, 1185 }, 1186 "/v1, Kind=Secret, ns1/cluster1-ca": { 1187 softOwners: []string{ 1188 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 1189 }, 1190 }, 1191 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 1192 owners: []string{ 1193 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1194 }, 1195 }, 1196 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": { 1197 forceMove: true, 1198 forceMoveHierarchy: true, 1199 }, 1200 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1": { 1201 owners: []string{ 1202 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1203 }, 1204 softOwners: []string{ 1205 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1206 }, 1207 }, 1208 "/v1, Kind=Secret, ns1/resource-s1": { 1209 owners: []string{ 1210 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1211 }, 1212 }, 1213 "/v1, Kind=ConfigMap, ns1/resource-c1": { 1214 owners: []string{ 1215 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1216 }, 1217 }, 1218 }, 1219 }, 1220 }, 1221 { 1222 name: "A ClusterResourceSet applied to two clusters", 1223 args: objectGraphTestArgs{ 1224 objs: func() []client.Object { 1225 objs := []client.Object{} 1226 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 1227 objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) 1228 1229 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 1230 WithSecret("resource-s1"). 1231 WithConfigMap("resource-c1"). 1232 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 1233 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster2")). 1234 Objs()...) 1235 1236 return objs 1237 }(), 1238 }, 1239 want: wantGraph{ 1240 nodes: map[string]wantGraphItem{ 1241 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1242 forceMove: true, 1243 forceMoveHierarchy: true, 1244 }, 1245 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1246 owners: []string{ 1247 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1248 }, 1249 }, 1250 "/v1, Kind=Secret, ns1/cluster1-ca": { 1251 softOwners: []string{ 1252 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 1253 }, 1254 }, 1255 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 1256 owners: []string{ 1257 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1258 }, 1259 }, 1260 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2": { 1261 forceMove: true, 1262 forceMoveHierarchy: true, 1263 }, 1264 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster2": { 1265 owners: []string{ 1266 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1267 }, 1268 }, 1269 "/v1, Kind=Secret, ns1/cluster2-ca": { 1270 softOwners: []string{ 1271 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", // NB. this secret is not linked to the cluster through owner ref 1272 }, 1273 }, 1274 "/v1, Kind=Secret, ns1/cluster2-kubeconfig": { 1275 owners: []string{ 1276 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1277 }, 1278 }, 1279 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": { 1280 forceMove: true, 1281 forceMoveHierarchy: true, 1282 }, 1283 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1": { 1284 owners: []string{ 1285 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1286 }, 1287 softOwners: []string{ 1288 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1289 }, 1290 }, 1291 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster2": { 1292 owners: []string{ 1293 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1294 }, 1295 softOwners: []string{ 1296 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", 1297 }, 1298 }, 1299 "/v1, Kind=Secret, ns1/resource-s1": { 1300 owners: []string{ 1301 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1302 }, 1303 }, 1304 "/v1, Kind=ConfigMap, ns1/resource-c1": { 1305 owners: []string{ 1306 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 1307 }, 1308 }, 1309 }, 1310 }, 1311 }, 1312 { 1313 // NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims. 1314 name: "Namespaced External Objects with force move label", 1315 args: objectGraphTestArgs{ 1316 objs: test.NewFakeExternalObject("ns1", "externalObject1").Objs(), 1317 }, 1318 want: wantGraph{ 1319 nodes: map[string]wantGraphItem{ 1320 "external.cluster.x-k8s.io/v1beta1, Kind=GenericExternalObject, ns1/externalObject1": { 1321 forceMove: true, 1322 }, 1323 }, 1324 }, 1325 }, 1326 { 1327 // NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims. 1328 name: "Global External Objects with force move label", 1329 args: objectGraphTestArgs{ 1330 objs: test.NewFakeClusterExternalObject("externalObject1").Objs(), 1331 }, 1332 want: wantGraph{ 1333 nodes: map[string]wantGraphItem{ 1334 "external.cluster.x-k8s.io/v1beta1, Kind=GenericClusterExternalObject, externalObject1": { 1335 forceMove: true, 1336 isGlobal: true, 1337 }, 1338 }, 1339 }, 1340 }, 1341 { 1342 // NOTE: Infrastructure providers global credentials are going to be stored in Secrets in the provider's namespaces. 1343 name: "Secrets from provider's namespace", 1344 args: objectGraphTestArgs{ 1345 objs: []client.Object{ 1346 test.NewSecret("infra-system", "credentials"), 1347 }, 1348 }, 1349 want: wantGraph{ 1350 nodes: map[string]wantGraphItem{ 1351 "/v1, Kind=Secret, infra-system/credentials": {}, 1352 }, 1353 }, 1354 }, 1355 { 1356 name: "Cluster owning a secret with infrastructure credentials", 1357 args: objectGraphTestArgs{ 1358 objs: test.NewFakeCluster("ns1", "cluster1"). 1359 WithCredentialSecret().Objs(), 1360 }, 1361 want: wantGraph{ 1362 nodes: map[string]wantGraphItem{ 1363 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1364 forceMove: true, 1365 forceMoveHierarchy: true, 1366 }, 1367 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1368 owners: []string{ 1369 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1370 }, 1371 }, 1372 "/v1, Kind=Secret, ns1/cluster1-ca": { 1373 softOwners: []string{ 1374 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 1375 }, 1376 }, 1377 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 1378 owners: []string{ 1379 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1380 }, 1381 }, 1382 "/v1, Kind=Secret, ns1/cluster1-credentials": { 1383 owners: []string{ 1384 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1385 }, 1386 }, 1387 }, 1388 }, 1389 }, 1390 { 1391 name: "A global identity for an infrastructure provider owning a Secret with credentials in the provider's namespace", 1392 args: objectGraphTestArgs{ 1393 objs: test.NewFakeClusterInfrastructureIdentity("infra1-identity"). 1394 WithSecretIn("infra1-system"). // a secret in infra1-system namespace, where an infrastructure provider is installed 1395 Objs(), 1396 }, 1397 want: wantGraph{ 1398 nodes: map[string]wantGraphItem{ 1399 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericClusterInfrastructureIdentity, infra1-identity": { 1400 isGlobal: true, 1401 forceMove: true, 1402 forceMoveHierarchy: true, 1403 }, 1404 "/v1, Kind=Secret, infra1-system/infra1-identity-credentials": { 1405 owners: []string{ 1406 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericClusterInfrastructureIdentity, infra1-identity", 1407 }, 1408 }, 1409 }, 1410 }, 1411 }, 1412 { 1413 name: "ClusterClass", 1414 args: objectGraphTestArgs{ 1415 objs: test.NewFakeClusterClass("ns1", "clusterclass1").Objs(), 1416 }, 1417 want: wantGraph{ 1418 nodes: map[string]wantGraphItem{ 1419 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1": { 1420 forceMove: true, 1421 forceMoveHierarchy: true, 1422 }, 1423 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass1": { 1424 owners: []string{ 1425 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1426 }, 1427 }, 1428 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/clusterclass1": { 1429 owners: []string{ 1430 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1431 }, 1432 }, 1433 }, 1434 }, 1435 }, 1436 { 1437 name: "ClusterClass with control plane infrastructure template", 1438 args: objectGraphTestArgs{ 1439 objs: func() []client.Object { 1440 infrastructureMachineTemplate := builder.InfrastructureMachineTemplate("ns1", "inframachinetemplate1").Build() 1441 return test.NewFakeClusterClass("ns1", "clusterclass1"). 1442 WithControlPlaneInfrastructureTemplate(infrastructureMachineTemplate). 1443 Objs() 1444 }(), 1445 }, 1446 want: wantGraph{ 1447 nodes: map[string]wantGraphItem{ 1448 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1": { 1449 forceMove: true, 1450 forceMoveHierarchy: true, 1451 }, 1452 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass1": { 1453 owners: []string{ 1454 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1455 }, 1456 }, 1457 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/clusterclass1": { 1458 owners: []string{ 1459 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1460 }, 1461 }, 1462 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/inframachinetemplate1": { 1463 owners: []string{ 1464 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1465 }, 1466 }, 1467 }, 1468 }, 1469 }, 1470 { 1471 name: "ClusterClass with worker machine deployment class", 1472 args: objectGraphTestArgs{ 1473 objs: func() []client.Object { 1474 mdClass := test.NewFakeMachineDeploymentClass("ns1", "mdclass1") 1475 return test.NewFakeClusterClass("ns1", "clusterclass1"). 1476 WithWorkerMachineDeploymentClasses([]*test.FakeMachineDeploymentClass{mdClass}). 1477 Objs() 1478 }(), 1479 }, 1480 want: wantGraph{ 1481 nodes: map[string]wantGraphItem{ 1482 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1": { 1483 forceMove: true, 1484 forceMoveHierarchy: true, 1485 }, 1486 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass1": { 1487 owners: []string{ 1488 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1489 }, 1490 }, 1491 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/clusterclass1": { 1492 owners: []string{ 1493 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1494 }, 1495 }, 1496 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/mdclass1": { 1497 owners: []string{ 1498 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1499 }, 1500 }, 1501 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/mdclass1": { 1502 owners: []string{ 1503 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1504 }, 1505 }, 1506 }, 1507 }, 1508 }, 1509 { 1510 name: "ClusterClass with 2 worker machine deployment classes with shared templates", 1511 args: objectGraphTestArgs{ 1512 objs: func() []client.Object { 1513 infraMachineTemplate := builder.InfrastructureMachineTemplate("ns1", "infamachinetemplate1").Build() 1514 bootstrapTemplate := builder.BootstrapTemplate("ns1", "bootstraptemplate1").Build() 1515 mdClass1 := test.NewFakeMachineDeploymentClass("ns1", "mdclass1"). 1516 WithInfrastructureMachineTemplate(infraMachineTemplate). 1517 WithBootstrapTemplate(bootstrapTemplate) 1518 mdClass2 := test.NewFakeMachineDeploymentClass("ns1", "mdclass2"). 1519 WithInfrastructureMachineTemplate(infraMachineTemplate). 1520 WithBootstrapTemplate(bootstrapTemplate) 1521 return test.NewFakeClusterClass("ns1", "clusterclass1"). 1522 WithWorkerMachineDeploymentClasses([]*test.FakeMachineDeploymentClass{mdClass1, mdClass2}). 1523 Objs() 1524 }(), 1525 }, 1526 want: wantGraph{ 1527 nodes: map[string]wantGraphItem{ 1528 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1": { 1529 forceMove: true, 1530 forceMoveHierarchy: true, 1531 }, 1532 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass1": { 1533 owners: []string{ 1534 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1535 }, 1536 }, 1537 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/clusterclass1": { 1538 owners: []string{ 1539 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1540 }, 1541 }, 1542 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/infamachinetemplate1": { 1543 owners: []string{ 1544 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1545 }, 1546 }, 1547 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/bootstraptemplate1": { 1548 owners: []string{ 1549 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1550 }, 1551 }, 1552 }, 1553 }, 1554 }, 1555 { 1556 name: "Two ClusterClasses", 1557 args: objectGraphTestArgs{ 1558 objs: func() []client.Object { 1559 objs := []client.Object{} 1560 objs = append(objs, test.NewFakeClusterClass("ns1", "clusterclass1").Objs()...) 1561 objs = append(objs, test.NewFakeClusterClass("ns1", "clusterclass2").Objs()...) 1562 return objs 1563 }(), 1564 }, 1565 want: wantGraph{ 1566 nodes: map[string]wantGraphItem{ 1567 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1": { 1568 forceMove: true, 1569 forceMoveHierarchy: true, 1570 }, 1571 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass1": { 1572 owners: []string{ 1573 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1574 }, 1575 }, 1576 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/clusterclass1": { 1577 owners: []string{ 1578 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1579 }, 1580 }, 1581 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2": { 1582 forceMove: true, 1583 forceMoveHierarchy: true, 1584 }, 1585 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass2": { 1586 owners: []string{ 1587 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1588 }, 1589 }, 1590 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/clusterclass2": { 1591 owners: []string{ 1592 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1593 }, 1594 }, 1595 }, 1596 }, 1597 }, 1598 { 1599 name: "Two ClusterClasses with shared templates", 1600 args: objectGraphTestArgs{ 1601 objs: func() []client.Object { 1602 objs := []client.Object{} 1603 1604 infraMachineTemplate1 := builder.InfrastructureMachineTemplate("ns1", "infamachinetemplate1").Build() 1605 bootstrapTemplate1 := builder.BootstrapTemplate("ns1", "bootstraptemplate1").Build() 1606 1607 infraMachineTemplate2 := builder.InfrastructureMachineTemplate("ns1", "infamachinetemplate2").Build() 1608 bootstrapTemplate2 := builder.BootstrapTemplate("ns1", "bootstraptemplate2").Build() 1609 1610 controlPlaneTemplate := builder.ControlPlaneTemplate("ns1", "controlplanetemplate1").Build() 1611 1612 // mdClass1 and mdClass2 share templates. 1613 // mdClass3 does not share templates with any. 1614 mdClass1 := test.NewFakeMachineDeploymentClass("ns1", "mdclass1"). 1615 WithInfrastructureMachineTemplate(infraMachineTemplate1). 1616 WithBootstrapTemplate(bootstrapTemplate1) 1617 mdClass2 := test.NewFakeMachineDeploymentClass("ns1", "mdclass2"). 1618 WithInfrastructureMachineTemplate(infraMachineTemplate1). 1619 WithBootstrapTemplate(bootstrapTemplate1) 1620 mdClass3 := test.NewFakeMachineDeploymentClass("ns1", "mdclass2"). 1621 WithInfrastructureMachineTemplate(infraMachineTemplate2). 1622 WithBootstrapTemplate(bootstrapTemplate2) 1623 1624 // clusterclass1 and clusterclass2 share the same control plane template but have different 1625 // infrastructure cluster templates. 1626 // clusterclass1 and clusterclass2 share the templates defined in mdclass1. 1627 // clusterclass1 and clusterclass2 do not share the templates in mdclass3. 1628 objs = append(objs, test.NewFakeClusterClass("ns1", "clusterclass1"). 1629 WithControlPlaneTemplate(controlPlaneTemplate). 1630 WithWorkerMachineDeploymentClasses([]*test.FakeMachineDeploymentClass{mdClass1, mdClass2}). 1631 Objs()...) 1632 1633 objs = append(objs, test.NewFakeClusterClass("ns1", "clusterclass2"). 1634 WithControlPlaneTemplate(controlPlaneTemplate). 1635 WithWorkerMachineDeploymentClasses([]*test.FakeMachineDeploymentClass{mdClass1, mdClass3}). 1636 Objs()...) 1637 1638 // We need to deduplicate objects here as the clusterclasses share objects and 1639 // setting up the test server panics if we try to create it with duplicate objects. 1640 return deduplicateObjects(objs) 1641 1642 }(), 1643 }, 1644 want: wantGraph{ 1645 nodes: map[string]wantGraphItem{ 1646 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1": { 1647 forceMove: true, 1648 forceMoveHierarchy: true, 1649 }, 1650 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2": { 1651 forceMove: true, 1652 forceMoveHierarchy: true, 1653 }, 1654 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass1": { 1655 owners: []string{ 1656 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1657 }, 1658 }, 1659 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/clusterclass2": { 1660 owners: []string{ 1661 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1662 }, 1663 }, 1664 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/controlplanetemplate1": { 1665 owners: []string{ 1666 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1667 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1668 }, 1669 }, 1670 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/infamachinetemplate1": { 1671 owners: []string{ 1672 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1673 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1674 }, 1675 }, 1676 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/bootstraptemplate1": { 1677 owners: []string{ 1678 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass1", 1679 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1680 }, 1681 }, 1682 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/infamachinetemplate2": { 1683 owners: []string{ 1684 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1685 }, 1686 }, 1687 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/bootstraptemplate2": { 1688 owners: []string{ 1689 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/clusterclass2", 1690 }, 1691 }, 1692 }, 1693 }, 1694 }, 1695 } 1696 1697 func getDetachedObjectGraphWihObjs(objs []client.Object) (*objectGraph, error) { 1698 graph := newObjectGraph(nil, nil) // detached from any cluster 1699 for _, o := range objs { 1700 u := &unstructured.Unstructured{} 1701 if err := test.FakeScheme.Convert(o, u, nil); err != nil { 1702 return nil, errors.Wrap(err, "failed to convert object in unstructured") 1703 } 1704 if err := graph.addObj(u); err != nil { 1705 return nil, err 1706 } 1707 } 1708 1709 // given that we are not relying on discovery while testing in "detached mode (without a fake client)" it is required to: 1710 for _, node := range graph.getNodes() { 1711 // enforce forceMoveHierarchy for Clusters, ClusterResourceSets, GenericClusterInfrastructureIdentity 1712 if node.identity.Kind == "Cluster" || node.identity.Kind == "ClusterClass" || node.identity.Kind == "ClusterResourceSet" || node.identity.Kind == "GenericClusterInfrastructureIdentity" { 1713 node.forceMove = true 1714 node.forceMoveHierarchy = true 1715 } 1716 // enforce forceMove for GenericExternalObject, GenericClusterExternalObject 1717 if node.identity.Kind == "GenericExternalObject" || node.identity.Kind == "GenericClusterExternalObject" { 1718 node.forceMove = true 1719 } 1720 // enforce isGlobal for GenericClusterInfrastructureIdentity and GenericClusterExternalObject 1721 if node.identity.Kind == "GenericClusterInfrastructureIdentity" || node.identity.Kind == "GenericClusterExternalObject" { 1722 node.isGlobal = true 1723 } 1724 } 1725 1726 return graph, nil 1727 } 1728 1729 func TestObjectGraph_addObj_WithFakeObjects(t *testing.T) { 1730 // NB. we are testing the graph is properly built starting from objects (this test) or from the same objects read from the cluster (TestGraphBuilder_Discovery) 1731 for _, tt := range objectGraphsTests { 1732 t.Run(tt.name, func(t *testing.T) { 1733 g := NewWithT(t) 1734 1735 graph, err := getDetachedObjectGraphWihObjs(tt.args.objs) 1736 g.Expect(err).ToNot(HaveOccurred()) 1737 1738 // call setSoftOwnership so there is functional parity with discovery 1739 graph.setSoftOwnership() 1740 1741 assertGraph(t, graph, tt.want) 1742 }) 1743 } 1744 } 1745 1746 func getObjectGraphWithObjs(objs []client.Object) *objectGraph { 1747 fromProxy := getFakeProxyWithCRDs() 1748 1749 for _, o := range objs { 1750 fromProxy.WithObjs(o) 1751 } 1752 1753 fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1-system") 1754 inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter) 1755 1756 return newObjectGraph(fromProxy, inventory) 1757 } 1758 1759 func getObjectGraph() *objectGraph { 1760 // build object graph from file 1761 fromProxy := getFakeProxyWithCRDs() 1762 1763 fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1-system") 1764 inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter) 1765 1766 return newObjectGraph(fromProxy, inventory) 1767 } 1768 1769 func getFakeProxyWithCRDs() *test.FakeProxy { 1770 proxy := test.NewFakeProxy() 1771 for _, o := range test.FakeCRDList() { 1772 proxy.WithObjs(o) 1773 } 1774 return proxy 1775 } 1776 1777 func TestObjectGraph_Discovery(t *testing.T) { 1778 // NB. we are testing the graph is properly built starting from objects (TestGraphBuilder_addObj_WithFakeObjects) or from the same objects read from the cluster (this test). 1779 for _, tt := range objectGraphsTests { 1780 t.Run(tt.name, func(t *testing.T) { 1781 g := NewWithT(t) 1782 1783 ctx := context.Background() 1784 1785 // Create an objectGraph bound to a source cluster with all the CRDs for the types involved in the test. 1786 graph := getObjectGraphWithObjs(tt.args.objs) 1787 1788 // Get all the types to be considered for discovery 1789 err := graph.getDiscoveryTypes(ctx) 1790 g.Expect(err).ToNot(HaveOccurred()) 1791 1792 // finally test discovery 1793 err = graph.Discovery(ctx, "") 1794 if tt.wantErr { 1795 g.Expect(err).To(HaveOccurred()) 1796 return 1797 } 1798 1799 g.Expect(err).ToNot(HaveOccurred()) 1800 assertGraph(t, graph, tt.want) 1801 }) 1802 } 1803 } 1804 1805 func TestObjectGraph_DiscoveryByNamespace(t *testing.T) { 1806 type args struct { 1807 namespace string 1808 objs []client.Object 1809 } 1810 var tests = []struct { 1811 name string 1812 args args 1813 want wantGraph 1814 wantErr bool 1815 }{ 1816 { 1817 name: "two clusters, in different namespaces, read both", 1818 args: args{ 1819 namespace: "", // read all the namespaces 1820 objs: func() []client.Object { 1821 objs := []client.Object{} 1822 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 1823 objs = append(objs, test.NewFakeCluster("ns2", "cluster1").Objs()...) 1824 return objs 1825 }(), 1826 }, 1827 want: wantGraph{ 1828 nodes: map[string]wantGraphItem{ 1829 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1830 forceMove: true, 1831 forceMoveHierarchy: true, 1832 }, 1833 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1834 owners: []string{ 1835 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1836 }, 1837 }, 1838 "/v1, Kind=Secret, ns1/cluster1-ca": { 1839 softOwners: []string{ 1840 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 1841 }, 1842 }, 1843 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 1844 owners: []string{ 1845 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1846 }, 1847 }, 1848 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns2/cluster1": { 1849 forceMove: true, 1850 forceMoveHierarchy: true, 1851 }, 1852 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns2/cluster1": { 1853 owners: []string{ 1854 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns2/cluster1", 1855 }, 1856 }, 1857 "/v1, Kind=Secret, ns2/cluster1-ca": { 1858 softOwners: []string{ 1859 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns2/cluster1", // NB. this secret is not linked to the cluster through owner ref 1860 }, 1861 }, 1862 "/v1, Kind=Secret, ns2/cluster1-kubeconfig": { 1863 owners: []string{ 1864 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns2/cluster1", 1865 }, 1866 }, 1867 }, 1868 }, 1869 }, 1870 { 1871 name: "two clusters, in different namespaces, read only 1", 1872 args: args{ 1873 namespace: "ns1", // read only from ns1 1874 objs: func() []client.Object { 1875 objs := []client.Object{} 1876 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 1877 objs = append(objs, test.NewFakeCluster("ns2", "cluster1").Objs()...) 1878 return objs 1879 }(), 1880 }, 1881 want: wantGraph{ 1882 nodes: map[string]wantGraphItem{ 1883 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1884 forceMove: true, 1885 forceMoveHierarchy: true, 1886 }, 1887 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1888 owners: []string{ 1889 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1890 }, 1891 }, 1892 "/v1, Kind=Secret, ns1/cluster1-ca": { 1893 softOwners: []string{ 1894 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref 1895 }, 1896 }, 1897 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 1898 owners: []string{ 1899 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1900 }, 1901 }, 1902 }, 1903 }, 1904 }, 1905 { 1906 // NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims. 1907 name: "Namespaced External Objects with force move label", 1908 args: args{ 1909 namespace: "ns1", // read only from ns1 1910 objs: test.NewFakeExternalObject("ns1", "externalObject1").Objs(), // Fake external object with 1911 }, 1912 want: wantGraph{ 1913 nodes: map[string]wantGraphItem{ 1914 "external.cluster.x-k8s.io/v1beta1, Kind=GenericExternalObject, ns1/externalObject1": { 1915 forceMove: true, 1916 }, 1917 }, 1918 }, 1919 }, 1920 { 1921 // NOTE: Infrastructure providers global credentials are going to be stored in Secrets in the provider's namespaces. 1922 name: "Secrets from provider's namespace (e.g. credentials) should always be read", 1923 args: args{ 1924 namespace: "ns1", // read only from ns1 1925 objs: []client.Object{ 1926 test.NewSecret("infra1-system", "infra1-credentials"), // a secret in infra1-system namespace, where an infrastructure provider is installed 1927 }, 1928 }, 1929 want: wantGraph{ 1930 nodes: map[string]wantGraphItem{ 1931 "/v1, Kind=Secret, infra1-system/infra1-credentials": {}, 1932 }, 1933 }, 1934 }, 1935 } 1936 1937 for _, tt := range tests { 1938 t.Run(tt.name, func(t *testing.T) { 1939 g := NewWithT(t) 1940 1941 ctx := context.Background() 1942 1943 // Create an objectGraph bound to a source cluster with all the CRDs for the types involved in the test. 1944 graph := getObjectGraphWithObjs(tt.args.objs) 1945 1946 // Get all the types to be considered for discovery 1947 err := graph.getDiscoveryTypes(ctx) 1948 g.Expect(err).ToNot(HaveOccurred()) 1949 1950 // finally test discovery 1951 err = graph.Discovery(ctx, tt.args.namespace) 1952 if tt.wantErr { 1953 g.Expect(err).To(HaveOccurred()) 1954 return 1955 } 1956 1957 g.Expect(err).ToNot(HaveOccurred()) 1958 assertGraph(t, graph, tt.want) 1959 }) 1960 } 1961 } 1962 1963 func Test_objectGraph_setSoftOwnership(t *testing.T) { 1964 type fields struct { 1965 objs []client.Object 1966 } 1967 tests := []struct { 1968 name string 1969 fields fields 1970 want wantGraph 1971 }{ 1972 { 1973 name: "A cluster with a soft owned secret", 1974 fields: fields{ 1975 objs: test.NewFakeCluster("ns1", "cluster1").Objs(), 1976 }, 1977 want: wantGraph{ 1978 nodes: map[string]wantGraphItem{ 1979 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 1980 forceMove: true, 1981 forceMoveHierarchy: true, 1982 }, 1983 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 1984 owners: []string{ 1985 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1986 }, 1987 }, 1988 "/v1, Kind=Secret, ns1/cluster1-ca": { // the ca secret has no explicit OwnerRef to the cluster, so it should be identified as a soft ownership 1989 softOwners: []string{ 1990 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1991 }, 1992 }, 1993 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { // the kubeconfig secret has explicit OwnerRef to the cluster, so it should NOT be identified as a soft ownership 1994 owners: []string{ 1995 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 1996 }, 1997 }, 1998 }, 1999 }, 2000 }, 2001 { 2002 name: "A ClusterClass with a soft owned Cluster", 2003 fields: fields{ 2004 objs: func() []client.Object { 2005 objs := test.NewFakeClusterClass("ns1", "class1").Objs() 2006 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").WithTopologyClass("class1").Objs()...) 2007 2008 return objs 2009 }(), 2010 }, 2011 want: wantGraph{ 2012 nodes: map[string]wantGraphItem{ 2013 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/class1": { 2014 forceMove: true, 2015 forceMoveHierarchy: true, 2016 }, 2017 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureClusterTemplate, ns1/class1": { 2018 owners: []string{ 2019 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/class1", 2020 }, 2021 }, 2022 "controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlaneTemplate, ns1/class1": { 2023 owners: []string{ 2024 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/class1", 2025 }, 2026 }, 2027 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 2028 forceMove: true, 2029 forceMoveHierarchy: true, 2030 softOwners: []string{ 2031 "cluster.x-k8s.io/v1beta1, Kind=ClusterClass, ns1/class1", // NB. this cluster is not linked to the clusterclass through owner ref, but it is detected as soft ownership 2032 }, 2033 }, 2034 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 2035 owners: []string{ 2036 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 2037 }, 2038 }, 2039 "/v1, Kind=Secret, ns1/cluster1-ca": { 2040 softOwners: []string{ 2041 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref, but it is detected as soft ownership 2042 }, 2043 }, 2044 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 2045 owners: []string{ 2046 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 2047 }, 2048 }, 2049 }, 2050 }, 2051 }, 2052 { 2053 name: "A Cluster with a soft owned ClusterResourceSetBinding", 2054 fields: fields{ 2055 objs: func() []client.Object { 2056 objs := test.NewFakeCluster("ns1", "cluster1").Objs() 2057 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 2058 WithSecret("resource-s1"). 2059 WithConfigMap("resource-c1"). 2060 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 2061 Objs()...) 2062 2063 return objs 2064 }(), 2065 }, 2066 want: wantGraph{ 2067 nodes: map[string]wantGraphItem{ 2068 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 2069 forceMove: true, 2070 forceMoveHierarchy: true, 2071 }, 2072 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": { 2073 owners: []string{ 2074 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 2075 }, 2076 }, 2077 "/v1, Kind=Secret, ns1/cluster1-ca": { 2078 softOwners: []string{ 2079 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref, but it is detected as soft ownership 2080 }, 2081 }, 2082 "/v1, Kind=Secret, ns1/cluster1-kubeconfig": { 2083 owners: []string{ 2084 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", 2085 }, 2086 }, 2087 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": { 2088 forceMove: true, 2089 forceMoveHierarchy: true, 2090 }, 2091 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1": { 2092 owners: []string{ 2093 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 2094 }, 2095 softOwners: []string{ 2096 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // NB. this ClusterResourceSetBinding is not linked to the cluster through owner ref, but it is detected as soft ownership 2097 }, 2098 }, 2099 "/v1, Kind=Secret, ns1/resource-s1": { 2100 owners: []string{ 2101 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 2102 }, 2103 }, 2104 "/v1, Kind=ConfigMap, ns1/resource-c1": { 2105 owners: []string{ 2106 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", 2107 }, 2108 }, 2109 }, 2110 }, 2111 }, 2112 } 2113 for _, tt := range tests { 2114 t.Run(tt.name, func(t *testing.T) { 2115 g := NewWithT(t) 2116 2117 graph, err := getDetachedObjectGraphWihObjs(tt.fields.objs) 2118 g.Expect(err).ToNot(HaveOccurred()) 2119 2120 graph.setSoftOwnership() 2121 2122 assertGraph(t, graph, tt.want) 2123 }) 2124 } 2125 } 2126 2127 func Test_objectGraph_setClusterTenants(t *testing.T) { 2128 type fields struct { 2129 objs []client.Object 2130 } 2131 tests := []struct { 2132 name string 2133 fields fields 2134 wantClusters map[string][]string 2135 }{ 2136 { 2137 name: "One cluster", 2138 fields: fields{ 2139 objs: test.NewFakeCluster("ns1", "foo").Objs(), 2140 }, 2141 wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs 2142 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo": { 2143 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo", // the cluster should be tenant of itself 2144 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/foo", 2145 "/v1, Kind=Secret, ns1/foo-ca", // the ca secret is a soft owned 2146 "/v1, Kind=Secret, ns1/foo-kubeconfig", 2147 }, 2148 }, 2149 }, 2150 { 2151 name: "Object not owned by a cluster should be ignored", 2152 fields: fields{ 2153 objs: func() []client.Object { 2154 objs := []client.Object{} 2155 objs = append(objs, test.NewFakeCluster("ns1", "foo").Objs()...) 2156 objs = append(objs, test.NewFakeInfrastructureTemplate("orphan")) // orphan object, not owned by any cluster 2157 return objs 2158 }(), 2159 }, 2160 wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs 2161 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo": { 2162 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo", // the cluster should be tenant of itself 2163 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/foo", 2164 "/v1, Kind=Secret, ns1/foo-ca", // the ca secret is a soft owned 2165 "/v1, Kind=Secret, ns1/foo-kubeconfig", 2166 }, 2167 }, 2168 }, 2169 { 2170 name: "Two clusters", 2171 fields: fields{ 2172 objs: func() []client.Object { 2173 objs := []client.Object{} 2174 objs = append(objs, test.NewFakeCluster("ns1", "foo").Objs()...) 2175 objs = append(objs, test.NewFakeCluster("ns1", "bar").Objs()...) 2176 return objs 2177 }(), 2178 }, 2179 wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs 2180 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo": { 2181 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/foo", // the cluster should be tenant of itself 2182 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/foo", 2183 "/v1, Kind=Secret, ns1/foo-ca", // the ca secret is a soft owned 2184 "/v1, Kind=Secret, ns1/foo-kubeconfig", 2185 }, 2186 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/bar": { 2187 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/bar", // the cluster should be tenant of itself 2188 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/bar", 2189 "/v1, Kind=Secret, ns1/bar-ca", // the ca secret is a soft owned 2190 "/v1, Kind=Secret, ns1/bar-kubeconfig", 2191 }, 2192 }, 2193 }, 2194 { 2195 name: "Two clusters with a shared object", 2196 fields: fields{ 2197 objs: func() []client.Object { 2198 sharedInfrastructureTemplate := test.NewFakeInfrastructureTemplate("shared") 2199 2200 objs := []client.Object{ 2201 sharedInfrastructureTemplate, 2202 } 2203 2204 objs = append(objs, test.NewFakeCluster("ns1", "cluster1"). 2205 WithMachineSets( 2206 test.NewFakeMachineSet("cluster1-ms1"). 2207 WithInfrastructureTemplate(sharedInfrastructureTemplate). 2208 WithMachines( 2209 test.NewFakeMachine("cluster1-m1"), 2210 ), 2211 ).Objs()...) 2212 2213 objs = append(objs, test.NewFakeCluster("ns1", "cluster2"). 2214 WithMachineSets( 2215 test.NewFakeMachineSet("cluster2-ms1"). 2216 WithInfrastructureTemplate(sharedInfrastructureTemplate). 2217 WithMachines( 2218 test.NewFakeMachine("cluster2-m1"), 2219 ), 2220 ).Objs()...) 2221 2222 return objs 2223 }(), 2224 }, 2225 wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs 2226 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 2227 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/shared", // the shared object should be in both lists 2228 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // the cluster should be tenant of itself 2229 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1", 2230 "/v1, Kind=Secret, ns1/cluster1-ca", // the ca secret is a soft owned 2231 "/v1, Kind=Secret, ns1/cluster1-kubeconfig", 2232 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/cluster1-ms1", 2233 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/cluster1-ms1", 2234 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster1-m1", 2235 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cluster1-m1", 2236 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cluster1-m1", 2237 "/v1, Kind=Secret, ns1/cluster1-m1", 2238 }, 2239 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2": { 2240 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/shared", // the shared object should be in both lists 2241 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", // the cluster should be tenant of itself 2242 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster2", 2243 "/v1, Kind=Secret, ns1/cluster2-ca", // the ca secret is a soft owned 2244 "/v1, Kind=Secret, ns1/cluster2-kubeconfig", 2245 "cluster.x-k8s.io/v1beta1, Kind=MachineSet, ns1/cluster2-ms1", 2246 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/cluster2-ms1", 2247 "cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cluster2-m1", 2248 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cluster2-m1", 2249 "bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cluster2-m1", 2250 "/v1, Kind=Secret, ns1/cluster2-m1", 2251 }, 2252 }, 2253 }, 2254 { 2255 name: "A ClusterResourceSet applied to a cluster", 2256 fields: fields{ 2257 objs: func() []client.Object { 2258 objs := []client.Object{} 2259 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 2260 2261 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 2262 WithSecret("resource-s1"). 2263 WithConfigMap("resource-c1"). 2264 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 2265 Objs()...) 2266 2267 return objs 2268 }(), 2269 }, 2270 wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs 2271 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 2272 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // the cluster should be tenant of itself 2273 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1", 2274 "/v1, Kind=Secret, ns1/cluster1-ca", // the ca secret is a soft owned 2275 "/v1, Kind=Secret, ns1/cluster1-kubeconfig", 2276 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1", // ClusterResourceSetBinding are owned by the cluster 2277 }, 2278 }, 2279 }, 2280 { 2281 name: "A ClusterResourceSet applied to two clusters", 2282 fields: fields{ 2283 objs: func() []client.Object { 2284 objs := []client.Object{} 2285 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 2286 objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) 2287 2288 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 2289 WithSecret("resource-s1"). 2290 WithConfigMap("resource-c1"). 2291 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 2292 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster2")). 2293 Objs()...) 2294 2295 return objs 2296 }(), 2297 }, 2298 wantClusters: map[string][]string{ // wantClusters is a map[Cluster.UID] --> list of UIDs 2299 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": { 2300 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1", // the cluster should be tenant of itself 2301 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1", 2302 "/v1, Kind=Secret, ns1/cluster1-ca", // the ca secret is a soft owned 2303 "/v1, Kind=Secret, ns1/cluster1-kubeconfig", 2304 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1", // ClusterResourceSetBinding are owned by the cluster 2305 }, 2306 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2": { 2307 "cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster2", // the cluster should be tenant of itself 2308 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster2", 2309 "/v1, Kind=Secret, ns1/cluster2-ca", // the ca secret is a soft owned 2310 "/v1, Kind=Secret, ns1/cluster2-kubeconfig", 2311 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster2", // ClusterResourceSetBinding are owned by the cluster 2312 }, 2313 }, 2314 }, 2315 } 2316 for _, tt := range tests { 2317 t.Run(tt.name, func(t *testing.T) { 2318 g := NewWithT(t) 2319 2320 gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs) 2321 g.Expect(err).ToNot(HaveOccurred()) 2322 2323 // we want to check that soft dependent nodes are considered part of the cluster, so we make sure to call SetSoftDependants before SetClusterTenants 2324 gb.setSoftOwnership() 2325 2326 // finally test SetTenants 2327 gb.setTenants() 2328 2329 gotClusters := gb.getClusters() 2330 sort.Slice(gotClusters, func(i, j int) bool { 2331 return gotClusters[i].identity.UID < gotClusters[j].identity.UID 2332 }) 2333 2334 g.Expect(gotClusters).To(HaveLen(len(tt.wantClusters))) 2335 2336 for _, cluster := range gotClusters { 2337 wantTenants, ok := tt.wantClusters[string(cluster.identity.UID)] 2338 g.Expect(ok).To(BeTrue()) 2339 2340 gotTenants := []string{} 2341 for _, node := range gb.uidToNode { 2342 for c := range node.tenant { 2343 if c.identity.UID == cluster.identity.UID { 2344 gotTenants = append(gotTenants, string(node.identity.UID)) 2345 g.Expect(node.isGlobalHierarchy).To(BeFalse()) // We should make sure that everything below a Cluster is not considered global 2346 } 2347 } 2348 } 2349 2350 g.Expect(gotTenants).To(ConsistOf(wantTenants)) 2351 } 2352 }) 2353 } 2354 } 2355 2356 func Test_objectGraph_setCRSTenants(t *testing.T) { 2357 type fields struct { 2358 objs []client.Object 2359 } 2360 tests := []struct { 2361 name string 2362 fields fields 2363 wantCRSs map[string][]string 2364 }{ 2365 { 2366 name: "A ClusterResourceSet applied to a cluster", 2367 fields: fields{ 2368 objs: func() []client.Object { 2369 objs := []client.Object{} 2370 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 2371 2372 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 2373 WithSecret("resource-s1"). 2374 WithConfigMap("resource-c1"). 2375 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 2376 Objs()...) 2377 2378 return objs 2379 }(), 2380 }, 2381 wantCRSs: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs 2382 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": { 2383 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", // the ClusterResourceSet should be tenant of itself 2384 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1", // ClusterResourceSetBinding are owned by ClusterResourceSet 2385 "/v1, Kind=Secret, ns1/resource-s1", // resource are owned by ClusterResourceSet 2386 "/v1, Kind=ConfigMap, ns1/resource-c1", // resource are owned by ClusterResourceSet 2387 }, 2388 }, 2389 }, 2390 { 2391 name: "A ClusterResourceSet applied to two clusters", 2392 fields: fields{ 2393 objs: func() []client.Object { 2394 objs := []client.Object{} 2395 objs = append(objs, test.NewFakeCluster("ns1", "cluster1").Objs()...) 2396 objs = append(objs, test.NewFakeCluster("ns1", "cluster2").Objs()...) 2397 2398 objs = append(objs, test.NewFakeClusterResourceSet("ns1", "crs1"). 2399 WithSecret("resource-s1"). 2400 WithConfigMap("resource-c1"). 2401 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster1")). 2402 ApplyToCluster(test.SelectClusterObj(objs, "ns1", "cluster2")). 2403 Objs()...) 2404 2405 return objs 2406 }(), 2407 }, 2408 wantCRSs: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs 2409 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": { 2410 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1", // the ClusterResourceSet should be tenant of itself 2411 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster1", // ClusterResourceSetBinding are owned by ClusterResourceSet 2412 "addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetBinding, ns1/cluster2", // ClusterResourceSetBinding are owned by ClusterResourceSet 2413 "/v1, Kind=Secret, ns1/resource-s1", // resource are owned by ClusterResourceSet 2414 "/v1, Kind=ConfigMap, ns1/resource-c1", // resource are owned by ClusterResourceSet 2415 }, 2416 }, 2417 }, 2418 } 2419 for _, tt := range tests { 2420 t.Run(tt.name, func(t *testing.T) { 2421 g := NewWithT(t) 2422 2423 gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs) 2424 g.Expect(err).ToNot(HaveOccurred()) 2425 2426 gb.setTenants() 2427 2428 gotCRSs := gb.getCRSs() 2429 sort.Slice(gotCRSs, func(i, j int) bool { 2430 return gotCRSs[i].identity.UID < gotCRSs[j].identity.UID 2431 }) 2432 2433 g.Expect(gotCRSs).To(HaveLen(len(tt.wantCRSs))) 2434 2435 for _, crs := range gotCRSs { 2436 wantTenants, ok := tt.wantCRSs[string(crs.identity.UID)] 2437 g.Expect(ok).To(BeTrue()) 2438 2439 gotTenants := []string{} 2440 for _, node := range gb.uidToNode { 2441 for c := range node.tenant { 2442 if c.identity.UID == crs.identity.UID { 2443 gotTenants = append(gotTenants, string(node.identity.UID)) 2444 g.Expect(node.isGlobalHierarchy).To(BeFalse()) // We should make sure that everything below a CRS is not considered global 2445 } 2446 } 2447 } 2448 2449 g.Expect(gotTenants).To(ConsistOf(wantTenants)) 2450 } 2451 }) 2452 } 2453 } 2454 2455 func Test_objectGraph_setGlobalIdentityTenants(t *testing.T) { 2456 type fields struct { 2457 objs []client.Object 2458 } 2459 tests := []struct { 2460 name string 2461 fields fields 2462 wantIdentity map[string][]string 2463 }{ 2464 { 2465 name: "A global identity for an infrastructure provider owning a Secret with credentials in the provider's namespace", 2466 fields: fields{ 2467 objs: test.NewFakeClusterInfrastructureIdentity("infra1-identity"). 2468 WithSecretIn("infra1-system"). // a secret in infra1-system namespace, where an infrastructure provider is installed 2469 Objs(), 2470 }, 2471 wantIdentity: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs 2472 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericClusterInfrastructureIdentity, infra1-identity": { 2473 "infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericClusterInfrastructureIdentity, infra1-identity", // the global identity should be tenant of itself 2474 "/v1, Kind=Secret, infra1-system/infra1-identity-credentials", 2475 }, 2476 }, 2477 }, 2478 } 2479 for _, tt := range tests { 2480 t.Run(tt.name, func(t *testing.T) { 2481 g := NewWithT(t) 2482 2483 gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs) 2484 g.Expect(err).ToNot(HaveOccurred()) 2485 2486 gb.setTenants() 2487 2488 gotIdentity := []*node{} 2489 for _, n := range gb.getNodes() { 2490 if n.forceMoveHierarchy { 2491 gotIdentity = append(gotIdentity, n) 2492 } 2493 } 2494 sort.Slice(gotIdentity, func(i, j int) bool { 2495 return gotIdentity[i].identity.UID < gotIdentity[j].identity.UID 2496 }) 2497 g.Expect(gotIdentity).To(HaveLen(len(tt.wantIdentity))) 2498 2499 for _, i := range gotIdentity { 2500 wantTenants, ok := tt.wantIdentity[string(i.identity.UID)] 2501 g.Expect(ok).To(BeTrue()) 2502 2503 gotTenants := []string{} 2504 for _, node := range gb.uidToNode { 2505 for c := range node.tenant { 2506 if c.identity.UID == i.identity.UID { 2507 gotTenants = append(gotTenants, string(node.identity.UID)) 2508 g.Expect(node.isGlobalHierarchy).To(BeTrue()) // We should make sure that everything below a global object is considered global 2509 } 2510 } 2511 } 2512 2513 g.Expect(gotTenants).To(ConsistOf(wantTenants)) 2514 } 2515 }) 2516 } 2517 } 2518 2519 func deduplicateObjects(objs []client.Object) []client.Object { 2520 res := []client.Object{} 2521 uniqueObjectKeys := sets.Set[string]{} 2522 for _, o := range objs { 2523 if !uniqueObjectKeys.Has(string(o.GetUID())) { 2524 res = append(res, o) 2525 uniqueObjectKeys.Insert(string(o.GetUID())) 2526 } 2527 } 2528 return res 2529 }