sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/tree/discovery_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 tree
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"testing"
    23  
    24  	. "github.com/onsi/gomega"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
    30  )
    31  
    32  func clusterObjectsWithResourceSet() []client.Object {
    33  	namespace := "ns1"
    34  	clusterObjs := test.NewFakeCluster(namespace, "cluster1").
    35  		WithControlPlane(
    36  			test.NewFakeControlPlane("cp").
    37  				WithMachines(
    38  					test.NewFakeMachine("cp1"),
    39  				),
    40  		).
    41  		WithMachineDeployments(
    42  			test.NewFakeMachineDeployment("md1").
    43  				WithMachineSets(
    44  					test.NewFakeMachineSet("ms1").
    45  						WithMachines(
    46  							test.NewFakeMachine("m1"),
    47  							test.NewFakeMachine("m2"),
    48  						),
    49  				),
    50  		).
    51  		Objs()
    52  
    53  	var cluster *clusterv1.Cluster
    54  	for _, obj := range clusterObjs {
    55  		if obj.GetObjectKind().GroupVersionKind().Kind == "Cluster" {
    56  			cluster = obj.(*clusterv1.Cluster)
    57  		}
    58  	}
    59  	resourceSet := test.NewFakeClusterResourceSet(namespace, "crs1")
    60  	resourceSetObjs := resourceSet.ApplyToCluster(cluster).Objs()
    61  
    62  	return append(clusterObjs, resourceSetObjs...)
    63  }
    64  
    65  func Test_Discovery(t *testing.T) {
    66  	type nodeCheck func(*WithT, client.Object)
    67  	type args struct {
    68  		objs            []client.Object
    69  		discoverOptions DiscoverOptions
    70  	}
    71  	tests := []struct {
    72  		name          string
    73  		args          args
    74  		wantTree      map[string][]string
    75  		wantNodeCheck map[string]nodeCheck
    76  	}{
    77  		{
    78  			name: "Discovery with default discovery settings",
    79  			args: args{
    80  				discoverOptions: DiscoverOptions{
    81  					Grouping: true,
    82  				},
    83  				objs: test.NewFakeCluster("ns1", "cluster1").
    84  					WithControlPlane(
    85  						test.NewFakeControlPlane("cp").
    86  							WithMachines(
    87  								test.NewFakeMachine("cp1"),
    88  							),
    89  					).
    90  					WithMachineDeployments(
    91  						test.NewFakeMachineDeployment("md1").
    92  							WithMachineSets(
    93  								test.NewFakeMachineSet("ms1").
    94  									WithMachines(
    95  										test.NewFakeMachine("m1"),
    96  										test.NewFakeMachine("m2"),
    97  									),
    98  							),
    99  					).
   100  					Objs(),
   101  			},
   102  			wantTree: map[string][]string{
   103  				// Cluster should be parent of InfrastructureCluster, ControlPlane, and WorkerNodes
   104  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   105  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   106  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   107  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   108  				},
   109  				// InfrastructureCluster should be leaf
   110  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   111  				// ControlPlane should have a machine
   112  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   113  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   114  				},
   115  				// Machine should be leaf (no echo)
   116  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
   117  				// Workers should have a machine deployment
   118  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   119  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   120  				},
   121  				// Machine deployment should have a group of machines (grouping)
   122  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   123  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   124  				},
   125  			},
   126  			wantNodeCheck: map[string]nodeCheck{
   127  				// InfrastructureCluster should have a meta name
   128  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   129  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   130  				},
   131  				// ControlPlane should have a meta name, be a grouping object
   132  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   133  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   134  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   135  				},
   136  				// Workers should be a virtual node
   137  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   138  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   139  				},
   140  				// Machine deployment should be a grouping object
   141  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   142  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   143  				},
   144  			},
   145  		},
   146  		{
   147  			name: "Discovery with grouping disabled",
   148  			args: args{
   149  				discoverOptions: DiscoverOptions{
   150  					Grouping: false,
   151  				},
   152  				objs: test.NewFakeCluster("ns1", "cluster1").
   153  					WithControlPlane(
   154  						test.NewFakeControlPlane("cp").
   155  							WithMachines(
   156  								test.NewFakeMachine("cp1"),
   157  							),
   158  					).
   159  					WithMachineDeployments(
   160  						test.NewFakeMachineDeployment("md1").
   161  							WithMachineSets(
   162  								test.NewFakeMachineSet("ms1").
   163  									WithMachines(
   164  										test.NewFakeMachine("m1"),
   165  										test.NewFakeMachine("m2"),
   166  									),
   167  							),
   168  					).
   169  					Objs(),
   170  			},
   171  			wantTree: map[string][]string{
   172  				// Cluster should be parent of InfrastructureCluster, ControlPlane, and WorkerNodes
   173  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   174  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   175  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   176  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   177  				},
   178  				// InfrastructureCluster should be leaf
   179  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   180  				// ControlPlane should have a machine
   181  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   182  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   183  				},
   184  				// Workers should have a machine deployment
   185  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   186  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   187  				},
   188  				// Machine deployment should have a group of machines
   189  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   190  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1",
   191  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m2",
   192  				},
   193  				// Machine should be leaf (no echo)
   194  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
   195  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1":  {},
   196  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m2":  {},
   197  			},
   198  			wantNodeCheck: map[string]nodeCheck{
   199  				// InfrastructureCluster should have a meta name
   200  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   201  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   202  				},
   203  				// ControlPlane should have a meta name, should NOT be a grouping object
   204  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   205  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   206  					g.Expect(IsGroupingObject(obj)).To(BeFalse())
   207  				},
   208  				// Workers should be a virtual node
   209  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   210  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   211  				},
   212  				// Machine deployment should NOT be a grouping object
   213  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   214  					g.Expect(IsGroupingObject(obj)).To(BeFalse())
   215  				},
   216  			},
   217  		},
   218  		{
   219  			name: "Discovery with MachinePool Machines with echo enabled",
   220  			args: args{
   221  				discoverOptions: DiscoverOptions{
   222  					Grouping: false,
   223  					Echo:     true,
   224  				},
   225  				objs: test.NewFakeCluster("ns1", "cluster1").
   226  					WithControlPlane(
   227  						test.NewFakeControlPlane("cp").
   228  							WithMachines(
   229  								test.NewFakeMachine("cp1"),
   230  							),
   231  					).
   232  					WithMachinePools(
   233  						test.NewFakeMachinePool("mp1").
   234  							WithMachines(
   235  								test.NewFakeMachine("mp1m1"),
   236  								test.NewFakeMachine("mp1m2"),
   237  							),
   238  					).
   239  					Objs(),
   240  			},
   241  			wantTree: map[string][]string{
   242  				// Cluster should be parent of InfrastructureCluster, ControlPlane, and WorkerNodes
   243  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   244  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   245  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   246  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   247  				},
   248  				// InfrastructureCluster should be leaf
   249  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   250  				// ControlPlane should have a machine
   251  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   252  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   253  				},
   254  				// Workers should have a machine deployment
   255  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   256  					"cluster.x-k8s.io/v1beta1, Kind=MachinePool, ns1/mp1",
   257  				},
   258  				// Machine Pool should have a group of machines
   259  				"cluster.x-k8s.io/v1beta1, Kind=MachinePool, ns1/mp1": {
   260  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/mp1",
   261  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/mp1",
   262  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/mp1m1",
   263  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/mp1m2",
   264  				},
   265  				// Machine should have infra machine and bootstrap (echo)
   266  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {
   267  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cp1",
   268  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cp1",
   269  				},
   270  				// MachinePool Machine should only have infra machine
   271  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/mp1m1": {
   272  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/mp1m1",
   273  				},
   274  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/mp1m2": {
   275  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/mp1m2",
   276  				},
   277  			},
   278  			wantNodeCheck: map[string]nodeCheck{
   279  				// InfrastructureCluster should have a meta name
   280  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   281  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   282  				},
   283  				// ControlPlane should have a meta name, should NOT be a grouping object
   284  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   285  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   286  					g.Expect(IsGroupingObject(obj)).To(BeFalse())
   287  				},
   288  				// Workers should be a virtual node
   289  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   290  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   291  				},
   292  				// Machine pool should NOT be a grouping object
   293  				"cluster.x-k8s.io/v1beta1, Kind=MachinePool, ns1/mp1": func(g *WithT, obj client.Object) {
   294  					g.Expect(IsGroupingObject(obj)).To(BeFalse())
   295  				},
   296  			},
   297  		},
   298  		{
   299  			name: "Discovery with grouping and no-echo disabled",
   300  			args: args{
   301  				discoverOptions: DiscoverOptions{
   302  					Grouping: false,
   303  					Echo:     true,
   304  				},
   305  				objs: test.NewFakeCluster("ns1", "cluster1").
   306  					WithControlPlane(
   307  						test.NewFakeControlPlane("cp").
   308  							WithMachines(
   309  								test.NewFakeMachine("cp1"),
   310  							),
   311  					).
   312  					WithMachineDeployments(
   313  						test.NewFakeMachineDeployment("md1").
   314  							WithMachineSets(
   315  								test.NewFakeMachineSet("ms1").
   316  									WithMachines(
   317  										test.NewFakeMachine("m1"),
   318  									),
   319  							),
   320  					).
   321  					Objs(),
   322  			},
   323  			wantTree: map[string][]string{
   324  				// Cluster should be parent of InfrastructureCluster, ControlPlane, and WorkerNodes
   325  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   326  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   327  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   328  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   329  				},
   330  				// InfrastructureCluster should be leaf
   331  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   332  				// ControlPlane should have a machine
   333  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   334  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   335  				},
   336  				// Machine should have infra machine and bootstrap (echo)
   337  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {
   338  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cp1",
   339  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cp1",
   340  				},
   341  				// Workers should have a machine deployment
   342  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   343  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   344  				},
   345  				// Machine deployment should have a group of machines
   346  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   347  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1",
   348  				},
   349  				// Machine should have infra machine and bootstrap (echo)
   350  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/m1": {
   351  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1",
   352  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1",
   353  				},
   354  			},
   355  			wantNodeCheck: map[string]nodeCheck{
   356  				// InfrastructureCluster should have a meta name
   357  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   358  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   359  				},
   360  				// ControlPlane should have a meta name, should NOT be a grouping object
   361  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   362  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   363  					g.Expect(IsGroupingObject(obj)).To(BeFalse())
   364  				},
   365  				// Workers should be a virtual node
   366  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   367  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   368  				},
   369  				// Machine deployment should NOT be a grouping object
   370  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   371  					g.Expect(IsGroupingObject(obj)).To(BeFalse())
   372  				},
   373  				// infra machines and boostrap should have meta names
   374  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/cp1": func(g *WithT, obj client.Object) {
   375  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructure"))
   376  				},
   377  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cp1": func(g *WithT, obj client.Object) {
   378  					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfig"))
   379  				},
   380  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachine, ns1/m1": func(g *WithT, obj client.Object) {
   381  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructure"))
   382  				},
   383  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/m1": func(g *WithT, obj client.Object) {
   384  					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfig"))
   385  				},
   386  			},
   387  		},
   388  		{
   389  			name: "Discovery with cluster resource sets shown",
   390  			args: args{
   391  				discoverOptions: DiscoverOptions{
   392  					Grouping:                true,
   393  					ShowClusterResourceSets: true,
   394  				},
   395  				objs: clusterObjectsWithResourceSet(),
   396  			},
   397  			wantTree: map[string][]string{
   398  				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
   399  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   400  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   401  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   402  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   403  					"virtual.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetGroup, ns1/ClusterResourceSets",
   404  				},
   405  				// InfrastructureCluster should be leaf
   406  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   407  				// ControlPlane should have a machine
   408  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   409  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   410  				},
   411  				// Machine should be leaf (no echo)
   412  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
   413  				// Workers should have a machine deployment
   414  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   415  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   416  				},
   417  				// Machine deployment should have a group of machines (grouping)
   418  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   419  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   420  				},
   421  				// ClusterResourceSetGroup should have a ClusterResourceSet
   422  				"virtual.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetGroup, ns1/ClusterResourceSets": {
   423  					"addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1",
   424  				},
   425  				// ClusterResourceSet should be a leaf
   426  				"addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": {},
   427  			},
   428  			wantNodeCheck: map[string]nodeCheck{
   429  				// InfrastructureCluster should have a meta name
   430  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   431  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   432  				},
   433  				// ControlPlane should have a meta name, be a grouping object
   434  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   435  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   436  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   437  				},
   438  				// Workers should be a virtual node
   439  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   440  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   441  				},
   442  				// ClusterResourceSetGroup should be a virtual node
   443  				"virtual.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetGroup, ns1/ClusterResourceSets": func(g *WithT, obj client.Object) {
   444  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   445  				},
   446  				// Machine deployment should be a grouping object
   447  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   448  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   449  				},
   450  			},
   451  		},
   452  		{
   453  			name: "Discovery with templates shown with template virtual nodes",
   454  			args: args{
   455  				discoverOptions: DiscoverOptions{
   456  					Grouping:               true,
   457  					ShowTemplates:          true,
   458  					AddTemplateVirtualNode: true,
   459  				},
   460  				objs: test.NewFakeCluster("ns1", "cluster1").
   461  					WithControlPlane(
   462  						test.NewFakeControlPlane("cp").
   463  							WithMachines(
   464  								test.NewFakeMachine("cp1"),
   465  							),
   466  					).
   467  					WithMachineDeployments(
   468  						test.NewFakeMachineDeployment("md1").
   469  							WithMachineSets(
   470  								test.NewFakeMachineSet("ms1").
   471  									WithMachines(
   472  										test.NewFakeMachine("m1"),
   473  										test.NewFakeMachine("m2"),
   474  									),
   475  							).
   476  							WithInfrastructureTemplate(
   477  								test.NewFakeInfrastructureTemplate("md1"),
   478  							),
   479  					).
   480  					WithMachineDeployments(
   481  						test.NewFakeMachineDeployment("md2").
   482  							WithStaticBootstrapConfig().
   483  							WithMachineSets(
   484  								test.NewFakeMachineSet("ms2").
   485  									WithMachines(
   486  										test.NewFakeMachine("m3"),
   487  										test.NewFakeMachine("m4"),
   488  									),
   489  							),
   490  					).
   491  					Objs(),
   492  			},
   493  			wantTree: map[string][]string{
   494  				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
   495  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   496  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   497  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   498  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   499  				},
   500  				// InfrastructureCluster should be leaf
   501  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   502  				// ControlPlane should have a machine and template group
   503  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   504  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   505  					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp",
   506  				},
   507  				// Machine should be leaf (no echo)
   508  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
   509  				// Workers should have a machine deployment
   510  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   511  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   512  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md2",
   513  				},
   514  				// Machine deployment should have a group of machines (grouping) and templates group
   515  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   516  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   517  					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1",
   518  				},
   519  				// MachineDeployment TemplateGroup should have a BootstrapConfigRef and InfrastructureRef
   520  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": {
   521  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1",
   522  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1",
   523  				},
   524  				// MachineDeployment InfrastructureRef should be a leaf
   525  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": {},
   526  				// MachineDeployment BootstrapConfigRef should be a leaf
   527  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": {},
   528  				// Machine deployment should have a group of machines (grouping) and templates group
   529  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md2": {
   530  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   531  					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2",
   532  				},
   533  				// MachineDeployment TemplateGroup using static bootstrap will only have InfrastructureRef
   534  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2": {
   535  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2",
   536  				},
   537  				// MachineDeployment InfrastructureRef should be a leaf
   538  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2": {},
   539  				// ControlPlane TemplateGroup should have a InfrastructureRef
   540  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": {
   541  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp",
   542  				},
   543  				// ControlPlane InfrastructureRef should be a leaf
   544  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp": {},
   545  			},
   546  			wantNodeCheck: map[string]nodeCheck{
   547  				// InfrastructureCluster should have a meta name
   548  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   549  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   550  				},
   551  				// ControlPlane should have a meta name, be a grouping object
   552  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   553  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   554  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   555  				},
   556  				// Workers should be a virtual node
   557  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   558  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   559  				},
   560  				// Machine deployment should be a grouping object
   561  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   562  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   563  				},
   564  				// ControlPlane TemplateGroup should be a virtual node
   565  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": func(g *WithT, obj client.Object) {
   566  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   567  				},
   568  				// MachineDeployment TemplateGroup should be a virtual node
   569  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": func(g *WithT, obj client.Object) {
   570  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   571  				},
   572  				// MachineDeployment InfrastructureRef should have a meta name
   573  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": func(g *WithT, obj client.Object) {
   574  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   575  				},
   576  				// MachineDeployment BootstrapConfigRef should have a meta name
   577  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": func(g *WithT, obj client.Object) {
   578  					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
   579  				},
   580  				// ControlPlane InfrastructureRef should have a meta name
   581  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": func(g *WithT, obj client.Object) {
   582  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   583  				},
   584  			},
   585  		},
   586  		{
   587  			name: "Discovery with templates shown without template virtual nodes",
   588  			args: args{
   589  				discoverOptions: DiscoverOptions{
   590  					Grouping:               true,
   591  					ShowTemplates:          true,
   592  					AddTemplateVirtualNode: false,
   593  				},
   594  				objs: test.NewFakeCluster("ns1", "cluster1").
   595  					WithControlPlane(
   596  						test.NewFakeControlPlane("cp").
   597  							WithMachines(
   598  								test.NewFakeMachine("cp1"),
   599  							),
   600  					).
   601  					WithMachineDeployments(
   602  						test.NewFakeMachineDeployment("md1").
   603  							WithMachineSets(
   604  								test.NewFakeMachineSet("ms1").
   605  									WithMachines(
   606  										test.NewFakeMachine("m1"),
   607  										test.NewFakeMachine("m2"),
   608  									),
   609  							).
   610  							WithInfrastructureTemplate(
   611  								test.NewFakeInfrastructureTemplate("md1"),
   612  							),
   613  					).
   614  					Objs(),
   615  			},
   616  			wantTree: map[string][]string{
   617  				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
   618  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   619  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   620  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   621  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   622  				},
   623  				// InfrastructureCluster should be leaf
   624  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   625  				// ControlPlane should have a machine and template
   626  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   627  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   628  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp",
   629  				},
   630  				// Machine should be leaf (no echo)
   631  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
   632  				// Workers should have a machine deployment
   633  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   634  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   635  				},
   636  				// Machine deployment should have a group of machines (grouping) and templates
   637  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   638  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   639  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1",
   640  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1",
   641  				},
   642  				// MachineDeployment InfrastructureRef should be a leaf
   643  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": {},
   644  				// MachineDeployment BootstrapConfigRef should be a leaf
   645  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": {},
   646  				// ControlPlane InfrastructureRef should be a leaf
   647  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp": {},
   648  			},
   649  			wantNodeCheck: map[string]nodeCheck{
   650  				// InfrastructureCluster should have a meta name
   651  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   652  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   653  				},
   654  				// ControlPlane should have a meta name, be a grouping object
   655  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   656  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   657  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   658  				},
   659  				// Workers should be a virtual node
   660  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   661  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   662  				},
   663  				// Machine deployment should be a grouping object
   664  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   665  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   666  				},
   667  				// ControlPlane TemplateGroup should be a virtual node
   668  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": func(g *WithT, obj client.Object) {
   669  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   670  				},
   671  				// MachineDeployment TemplateGroup should be a virtual node
   672  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": func(g *WithT, obj client.Object) {
   673  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   674  				},
   675  				// MachineDeployment InfrastructureRef should have a meta name
   676  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": func(g *WithT, obj client.Object) {
   677  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   678  				},
   679  				// MachineDeployment BootstrapConfigRef should have a meta name
   680  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": func(g *WithT, obj client.Object) {
   681  					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
   682  				},
   683  				// ControlPlane InfrastructureRef should have a meta name
   684  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": func(g *WithT, obj client.Object) {
   685  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   686  				},
   687  			},
   688  		},
   689  		{
   690  			name: "Discovery with multiple machine deployments does not cause template virtual nodes to collide",
   691  			args: args{
   692  				discoverOptions: DiscoverOptions{
   693  					Grouping:               true,
   694  					ShowTemplates:          true,
   695  					AddTemplateVirtualNode: true,
   696  				},
   697  				objs: test.NewFakeCluster("ns1", "cluster1").
   698  					WithControlPlane(
   699  						test.NewFakeControlPlane("cp").
   700  							WithMachines(
   701  								test.NewFakeMachine("cp1"),
   702  							),
   703  					).
   704  					WithMachineDeployments(
   705  						test.NewFakeMachineDeployment("md1").
   706  							WithMachineSets(
   707  								test.NewFakeMachineSet("ms1").
   708  									WithMachines(
   709  										test.NewFakeMachine("m1"),
   710  										test.NewFakeMachine("m2"),
   711  									),
   712  							).
   713  							WithInfrastructureTemplate(
   714  								test.NewFakeInfrastructureTemplate("md1"),
   715  							),
   716  						test.NewFakeMachineDeployment("md2").
   717  							WithMachineSets(
   718  								test.NewFakeMachineSet("ms2").
   719  									WithMachines(
   720  										test.NewFakeMachine("m3"),
   721  										test.NewFakeMachine("m4"),
   722  										test.NewFakeMachine("m5"),
   723  									),
   724  							).
   725  							WithInfrastructureTemplate(
   726  								test.NewFakeInfrastructureTemplate("md2"),
   727  							),
   728  					).
   729  					Objs(),
   730  			},
   731  			wantTree: map[string][]string{
   732  				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
   733  				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
   734  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
   735  					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
   736  					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
   737  				},
   738  				// InfrastructureCluster should be leaf
   739  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
   740  				// ControlPlane should have a machine and template group
   741  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
   742  					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
   743  					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp",
   744  				},
   745  				// ControlPlane TemplateGroup should have a InfrastructureRef
   746  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": {
   747  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp",
   748  				},
   749  				// ControlPlane InfrastructureRef should be a leaf
   750  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp": {},
   751  				// Machine should be leaf (no echo)
   752  				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
   753  				// Workers should have 2 machine deployments
   754  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
   755  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
   756  					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md2",
   757  				},
   758  				// Machine deployment 1 should have a group of machines (grouping) and templates group
   759  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
   760  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   761  					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1",
   762  				},
   763  				// MachineDeployment 1 TemplateGroup should have a BootstrapConfigRef and InfrastructureRef
   764  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": {
   765  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1",
   766  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1",
   767  				},
   768  				// MachineDeployment 1 InfrastructureRef should be a leaf
   769  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": {},
   770  				// MachineDeployment 1 BootstrapConfigRef should be a leaf
   771  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": {},
   772  				// Machine deployment 2 should have a group of machines (grouping) and templates group
   773  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md2": {
   774  					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
   775  					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2",
   776  				},
   777  				// MachineDeployment 2 TemplateGroup should have a BootstrapConfigRef and InfrastructureRef
   778  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2": {
   779  					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2",
   780  					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md2",
   781  				},
   782  				// MachineDeployment 2 InfrastructureRef should be a leaf
   783  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2": {},
   784  				// MachineDeployment 2 BootstrapConfigRef should be a leaf
   785  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md2": {},
   786  			},
   787  			wantNodeCheck: map[string]nodeCheck{
   788  				// InfrastructureCluster should have a meta name
   789  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
   790  					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
   791  				},
   792  				// ControlPlane should have a meta name, be a grouping object
   793  				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
   794  					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
   795  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   796  				},
   797  				// Workers should be a virtual node
   798  				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
   799  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   800  				},
   801  				// Machine deployment should be a grouping object
   802  				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
   803  					g.Expect(IsGroupingObject(obj)).To(BeTrue())
   804  				},
   805  				// ControlPlane TemplateGroup should be a virtual node
   806  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": func(g *WithT, obj client.Object) {
   807  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   808  				},
   809  				// ControlPlane InfrastructureRef should have a meta name
   810  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": func(g *WithT, obj client.Object) {
   811  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   812  				},
   813  				// MachineDeployment 1 TemplateGroup should be a virtual node
   814  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": func(g *WithT, obj client.Object) {
   815  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   816  				},
   817  				// MachineDeployment 1 InfrastructureRef should have a meta name
   818  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": func(g *WithT, obj client.Object) {
   819  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   820  				},
   821  				// MachineDeployment 1 BootstrapConfigRef should have a meta name
   822  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": func(g *WithT, obj client.Object) {
   823  					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
   824  				},
   825  				// MachineDeployment 2 TemplateGroup should be a virtual node
   826  				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2": func(g *WithT, obj client.Object) {
   827  					g.Expect(IsVirtualObject(obj)).To(BeTrue())
   828  				},
   829  				// MachineDeployment 2 InfrastructureRef should have a meta name
   830  				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2": func(g *WithT, obj client.Object) {
   831  					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
   832  				},
   833  				// MachineDeployment 2 BootstrapConfigRef should have a meta name
   834  				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md2": func(g *WithT, obj client.Object) {
   835  					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
   836  				},
   837  			},
   838  		},
   839  	}
   840  	for _, tt := range tests {
   841  		t.Run(tt.name, func(t *testing.T) {
   842  			g := NewWithT(t)
   843  
   844  			client, err := test.NewFakeProxy().WithObjs(tt.args.objs...).NewClient(context.Background())
   845  			g.Expect(client).ToNot(BeNil())
   846  			g.Expect(err).ToNot(HaveOccurred())
   847  
   848  			tree, err := Discovery(context.TODO(), client, "ns1", "cluster1", tt.args.discoverOptions)
   849  			g.Expect(tree).ToNot(BeNil())
   850  			g.Expect(err).ToNot(HaveOccurred())
   851  
   852  			for parent, wantChildren := range tt.wantTree {
   853  				gotChildren := tree.GetObjectsByParent(types.UID(parent))
   854  				g.Expect(gotChildren).To(HaveLen(len(wantChildren)), "%q doesn't have the expected number of children nodes", parent)
   855  
   856  				for _, gotChild := range gotChildren {
   857  					found := false
   858  					for _, wantChild := range wantChildren {
   859  						if strings.HasPrefix(string(gotChild.GetUID()), wantChild) {
   860  							found = true
   861  							break
   862  						}
   863  					}
   864  					g.Expect(found).To(BeTrue(), "got child %q for parent %q, expecting [%s]", gotChild.GetUID(), parent, strings.Join(wantChildren, "] ["))
   865  
   866  					if test, ok := tt.wantNodeCheck[string(gotChild.GetUID())]; ok {
   867  						test(g, gotChild)
   868  					}
   869  				}
   870  			}
   871  		})
   872  	}
   873  }