sigs.k8s.io/cluster-api-provider-azure@v1.17.0/pkg/mutators/azureasomanagedcontrolplane_test.go (about)

     1  /*
     2  Copyright 2024 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 mutators
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  
    24  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    25  	asocontainerservicev1preview "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231102preview"
    26  	"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
    27  	"github.com/google/go-cmp/cmp"
    28  	. "github.com/onsi/gomega"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/utils/ptr"
    33  	infrav1alpha "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha1"
    34  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    35  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    36  	"sigs.k8s.io/cluster-api/util/secret"
    37  	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    38  	"sigs.k8s.io/controller-runtime/pkg/conversion"
    39  )
    40  
    41  func TestSetManagedClusterDefaults(t *testing.T) {
    42  	ctx := context.Background()
    43  	g := NewGomegaWithT(t)
    44  
    45  	tests := []struct {
    46  		name                   string
    47  		asoManagedControlPlane *infrav1alpha.AzureASOManagedControlPlane
    48  		cluster                *clusterv1.Cluster
    49  		expected               []*unstructured.Unstructured
    50  		expectedErr            error
    51  	}{
    52  		{
    53  			name: "no ManagedCluster",
    54  			asoManagedControlPlane: &infrav1alpha.AzureASOManagedControlPlane{
    55  				Spec: infrav1alpha.AzureASOManagedControlPlaneSpec{
    56  					AzureASOManagedControlPlaneTemplateResourceSpec: infrav1alpha.AzureASOManagedControlPlaneTemplateResourceSpec{
    57  						Resources: []runtime.RawExtension{},
    58  					},
    59  				},
    60  			},
    61  			expectedErr: ErrNoManagedClusterDefined,
    62  		},
    63  		{
    64  			name: "success",
    65  			asoManagedControlPlane: &infrav1alpha.AzureASOManagedControlPlane{
    66  				Spec: infrav1alpha.AzureASOManagedControlPlaneSpec{
    67  					AzureASOManagedControlPlaneTemplateResourceSpec: infrav1alpha.AzureASOManagedControlPlaneTemplateResourceSpec{
    68  						Version: "vCAPI k8s version",
    69  						Resources: []runtime.RawExtension{
    70  							{
    71  								Raw: mcJSON(g, &asocontainerservicev1.ManagedCluster{}),
    72  							},
    73  						},
    74  					},
    75  				},
    76  			},
    77  			cluster: &clusterv1.Cluster{
    78  				ObjectMeta: metav1.ObjectMeta{
    79  					Name: "cluster",
    80  				},
    81  				Spec: clusterv1.ClusterSpec{
    82  					ClusterNetwork: &clusterv1.ClusterNetwork{
    83  						Pods: &clusterv1.NetworkRanges{
    84  							CIDRBlocks: []string{"pod-0", "pod-1"},
    85  						},
    86  						Services: &clusterv1.NetworkRanges{
    87  							CIDRBlocks: []string{"svc-0", "svc-1"},
    88  						},
    89  					},
    90  				},
    91  			},
    92  			expected: []*unstructured.Unstructured{
    93  				mcUnstructured(g, &asocontainerservicev1.ManagedCluster{
    94  					Spec: asocontainerservicev1.ManagedCluster_Spec{
    95  						KubernetesVersion: ptr.To("CAPI k8s version"),
    96  						NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
    97  							ServiceCidr: ptr.To("svc-0"),
    98  							PodCidr:     ptr.To("pod-0"),
    99  						},
   100  						OperatorSpec: &asocontainerservicev1.ManagedClusterOperatorSpec{
   101  							Secrets: &asocontainerservicev1.ManagedClusterOperatorSecrets{
   102  								AdminCredentials: &genruntime.SecretDestination{
   103  									Name: secret.Name("cluster", secret.Kubeconfig),
   104  									Key:  secret.KubeconfigDataName,
   105  								},
   106  							},
   107  						},
   108  					},
   109  				}),
   110  			},
   111  		},
   112  	}
   113  
   114  	for _, test := range tests {
   115  		t.Run(test.name, func(t *testing.T) {
   116  			g := NewGomegaWithT(t)
   117  
   118  			s := runtime.NewScheme()
   119  			g.Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   120  			g.Expect(infrav1alpha.AddToScheme(s)).To(Succeed())
   121  			c := fakeclient.NewClientBuilder().
   122  				WithScheme(s).
   123  				Build()
   124  
   125  			mutator := SetManagedClusterDefaults(c, test.asoManagedControlPlane, test.cluster)
   126  			actual, err := ApplyMutators(ctx, test.asoManagedControlPlane.Spec.Resources, mutator)
   127  			if test.expectedErr != nil {
   128  				g.Expect(err).To(MatchError(test.expectedErr))
   129  			} else {
   130  				g.Expect(err).NotTo(HaveOccurred())
   131  			}
   132  			g.Expect(cmp.Diff(test.expected, actual)).To(BeEmpty())
   133  		})
   134  	}
   135  }
   136  
   137  func TestSetManagedClusterKubernetesVersion(t *testing.T) {
   138  	ctx := context.Background()
   139  
   140  	tests := []struct {
   141  		name                   string
   142  		asoManagedControlPlane *infrav1alpha.AzureASOManagedControlPlane
   143  		managedCluster         *asocontainerservicev1.ManagedCluster
   144  		expected               *asocontainerservicev1.ManagedCluster
   145  		expectedErr            error
   146  	}{
   147  		{
   148  			name:                   "no CAPI opinion",
   149  			asoManagedControlPlane: &infrav1alpha.AzureASOManagedControlPlane{},
   150  			managedCluster: &asocontainerservicev1.ManagedCluster{
   151  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   152  					KubernetesVersion: ptr.To("user k8s version"),
   153  				},
   154  			},
   155  			expected: &asocontainerservicev1.ManagedCluster{
   156  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   157  					KubernetesVersion: ptr.To("user k8s version"),
   158  				},
   159  			},
   160  		},
   161  		{
   162  			name: "set from CAPI opinion",
   163  			asoManagedControlPlane: &infrav1alpha.AzureASOManagedControlPlane{
   164  				Spec: infrav1alpha.AzureASOManagedControlPlaneSpec{
   165  					AzureASOManagedControlPlaneTemplateResourceSpec: infrav1alpha.AzureASOManagedControlPlaneTemplateResourceSpec{
   166  						Version: "vCAPI k8s version",
   167  					},
   168  				},
   169  			},
   170  			managedCluster: &asocontainerservicev1.ManagedCluster{},
   171  			expected: &asocontainerservicev1.ManagedCluster{
   172  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   173  					KubernetesVersion: ptr.To("CAPI k8s version"),
   174  				},
   175  			},
   176  		},
   177  		{
   178  			name: "user value matching CAPI ok",
   179  			asoManagedControlPlane: &infrav1alpha.AzureASOManagedControlPlane{
   180  				Spec: infrav1alpha.AzureASOManagedControlPlaneSpec{
   181  					AzureASOManagedControlPlaneTemplateResourceSpec: infrav1alpha.AzureASOManagedControlPlaneTemplateResourceSpec{
   182  						Version: "vCAPI k8s version",
   183  					},
   184  				},
   185  			},
   186  			managedCluster: &asocontainerservicev1.ManagedCluster{
   187  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   188  					KubernetesVersion: ptr.To("CAPI k8s version"),
   189  				},
   190  			},
   191  			expected: &asocontainerservicev1.ManagedCluster{
   192  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   193  					KubernetesVersion: ptr.To("CAPI k8s version"),
   194  				},
   195  			},
   196  		},
   197  		{
   198  			name: "incompatible",
   199  			asoManagedControlPlane: &infrav1alpha.AzureASOManagedControlPlane{
   200  				Spec: infrav1alpha.AzureASOManagedControlPlaneSpec{
   201  					AzureASOManagedControlPlaneTemplateResourceSpec: infrav1alpha.AzureASOManagedControlPlaneTemplateResourceSpec{
   202  						Version: "vCAPI k8s version",
   203  					},
   204  				},
   205  			},
   206  			managedCluster: &asocontainerservicev1.ManagedCluster{
   207  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   208  					KubernetesVersion: ptr.To("user k8s version"),
   209  				},
   210  			},
   211  			expectedErr: Incompatible{
   212  				mutation: mutation{
   213  					location: ".spec.kubernetesVersion",
   214  					val:      "CAPI k8s version",
   215  					reason:   "because spec.version is set to vCAPI k8s version",
   216  				},
   217  				userVal: "user k8s version",
   218  			},
   219  		},
   220  	}
   221  
   222  	s := runtime.NewScheme()
   223  	NewGomegaWithT(t).Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   224  
   225  	for _, test := range tests {
   226  		t.Run(test.name, func(t *testing.T) {
   227  			g := NewGomegaWithT(t)
   228  
   229  			before := test.managedCluster.DeepCopy()
   230  			umc := mcUnstructured(g, test.managedCluster)
   231  
   232  			err := setManagedClusterKubernetesVersion(ctx, test.asoManagedControlPlane, "", umc)
   233  			g.Expect(s.Convert(umc, test.managedCluster, nil)).To(Succeed())
   234  			if test.expectedErr != nil {
   235  				g.Expect(err).To(MatchError(test.expectedErr))
   236  				g.Expect(cmp.Diff(before, test.managedCluster)).To(BeEmpty()) // errors should never modify the resource.
   237  			} else {
   238  				g.Expect(err).NotTo(HaveOccurred())
   239  				g.Expect(cmp.Diff(test.expected, test.managedCluster)).To(BeEmpty())
   240  			}
   241  		})
   242  	}
   243  }
   244  
   245  func TestSetManagedClusterServiceCIDR(t *testing.T) {
   246  	ctx := context.Background()
   247  
   248  	tests := []struct {
   249  		name           string
   250  		cluster        *clusterv1.Cluster
   251  		managedCluster *asocontainerservicev1.ManagedCluster
   252  		expected       *asocontainerservicev1.ManagedCluster
   253  		expectedErr    error
   254  	}{
   255  		{
   256  			name:    "no CAPI opinion",
   257  			cluster: &clusterv1.Cluster{},
   258  			managedCluster: &asocontainerservicev1.ManagedCluster{
   259  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   260  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   261  						ServiceCidr: ptr.To("user cidr"),
   262  					},
   263  				},
   264  			},
   265  			expected: &asocontainerservicev1.ManagedCluster{
   266  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   267  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   268  						ServiceCidr: ptr.To("user cidr"),
   269  					},
   270  				},
   271  			},
   272  		},
   273  		{
   274  			name: "set from CAPI opinion",
   275  			cluster: &clusterv1.Cluster{
   276  				Spec: clusterv1.ClusterSpec{
   277  					ClusterNetwork: &clusterv1.ClusterNetwork{
   278  						Services: &clusterv1.NetworkRanges{
   279  							CIDRBlocks: []string{"capi cidr"},
   280  						},
   281  					},
   282  				},
   283  			},
   284  			managedCluster: &asocontainerservicev1.ManagedCluster{},
   285  			expected: &asocontainerservicev1.ManagedCluster{
   286  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   287  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   288  						ServiceCidr: ptr.To("capi cidr"),
   289  					},
   290  				},
   291  			},
   292  		},
   293  		{
   294  			name: "user value matching CAPI ok",
   295  			cluster: &clusterv1.Cluster{
   296  				Spec: clusterv1.ClusterSpec{
   297  					ClusterNetwork: &clusterv1.ClusterNetwork{
   298  						Services: &clusterv1.NetworkRanges{
   299  							CIDRBlocks: []string{"capi cidr"},
   300  						},
   301  					},
   302  				},
   303  			},
   304  			managedCluster: &asocontainerservicev1.ManagedCluster{
   305  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   306  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   307  						ServiceCidr: ptr.To("capi cidr"),
   308  					},
   309  				},
   310  			},
   311  			expected: &asocontainerservicev1.ManagedCluster{
   312  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   313  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   314  						ServiceCidr: ptr.To("capi cidr"),
   315  					},
   316  				},
   317  			},
   318  		},
   319  		{
   320  			name: "incompatible",
   321  			cluster: &clusterv1.Cluster{
   322  				ObjectMeta: metav1.ObjectMeta{
   323  					Name:      "name",
   324  					Namespace: "ns",
   325  				},
   326  				Spec: clusterv1.ClusterSpec{
   327  					ClusterNetwork: &clusterv1.ClusterNetwork{
   328  						Services: &clusterv1.NetworkRanges{
   329  							CIDRBlocks: []string{"capi cidr"},
   330  						},
   331  					},
   332  				},
   333  			},
   334  			managedCluster: &asocontainerservicev1.ManagedCluster{
   335  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   336  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   337  						ServiceCidr: ptr.To("user cidr"),
   338  					},
   339  				},
   340  			},
   341  			expectedErr: Incompatible{
   342  				mutation: mutation{
   343  					location: ".spec.networkProfile.serviceCidr",
   344  					val:      "capi cidr",
   345  					reason:   "because spec.clusterNetwork.services.cidrBlocks[0] in Cluster ns/name is set to capi cidr",
   346  				},
   347  				userVal: "user cidr",
   348  			},
   349  		},
   350  	}
   351  
   352  	s := runtime.NewScheme()
   353  	NewGomegaWithT(t).Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   354  
   355  	for _, test := range tests {
   356  		t.Run(test.name, func(t *testing.T) {
   357  			g := NewGomegaWithT(t)
   358  
   359  			before := test.managedCluster.DeepCopy()
   360  			umc := mcUnstructured(g, test.managedCluster)
   361  
   362  			err := setManagedClusterServiceCIDR(ctx, test.cluster, "", umc)
   363  			g.Expect(s.Convert(umc, test.managedCluster, nil)).To(Succeed())
   364  			if test.expectedErr != nil {
   365  				g.Expect(err).To(MatchError(test.expectedErr))
   366  				g.Expect(cmp.Diff(before, test.managedCluster)).To(BeEmpty()) // errors should never modify the resource.
   367  			} else {
   368  				g.Expect(err).NotTo(HaveOccurred())
   369  				g.Expect(cmp.Diff(test.expected, test.managedCluster)).To(BeEmpty())
   370  			}
   371  		})
   372  	}
   373  }
   374  
   375  func TestSetManagedClusterPodCIDR(t *testing.T) {
   376  	ctx := context.Background()
   377  
   378  	tests := []struct {
   379  		name           string
   380  		cluster        *clusterv1.Cluster
   381  		managedCluster *asocontainerservicev1.ManagedCluster
   382  		expected       *asocontainerservicev1.ManagedCluster
   383  		expectedErr    error
   384  	}{
   385  		{
   386  			name:    "no CAPI opinion",
   387  			cluster: &clusterv1.Cluster{},
   388  			managedCluster: &asocontainerservicev1.ManagedCluster{
   389  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   390  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   391  						PodCidr: ptr.To("user cidr"),
   392  					},
   393  				},
   394  			},
   395  			expected: &asocontainerservicev1.ManagedCluster{
   396  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   397  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   398  						PodCidr: ptr.To("user cidr"),
   399  					},
   400  				},
   401  			},
   402  		},
   403  		{
   404  			name: "set from CAPI opinion",
   405  			cluster: &clusterv1.Cluster{
   406  				Spec: clusterv1.ClusterSpec{
   407  					ClusterNetwork: &clusterv1.ClusterNetwork{
   408  						Pods: &clusterv1.NetworkRanges{
   409  							CIDRBlocks: []string{"capi cidr"},
   410  						},
   411  					},
   412  				},
   413  			},
   414  			managedCluster: &asocontainerservicev1.ManagedCluster{},
   415  			expected: &asocontainerservicev1.ManagedCluster{
   416  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   417  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   418  						PodCidr: ptr.To("capi cidr"),
   419  					},
   420  				},
   421  			},
   422  		},
   423  		{
   424  			name: "user value matching CAPI ok",
   425  			cluster: &clusterv1.Cluster{
   426  				Spec: clusterv1.ClusterSpec{
   427  					ClusterNetwork: &clusterv1.ClusterNetwork{
   428  						Pods: &clusterv1.NetworkRanges{
   429  							CIDRBlocks: []string{"capi cidr"},
   430  						},
   431  					},
   432  				},
   433  			},
   434  			managedCluster: &asocontainerservicev1.ManagedCluster{
   435  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   436  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   437  						PodCidr: ptr.To("capi cidr"),
   438  					},
   439  				},
   440  			},
   441  			expected: &asocontainerservicev1.ManagedCluster{
   442  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   443  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   444  						PodCidr: ptr.To("capi cidr"),
   445  					},
   446  				},
   447  			},
   448  		},
   449  		{
   450  			name: "incompatible",
   451  			cluster: &clusterv1.Cluster{
   452  				ObjectMeta: metav1.ObjectMeta{
   453  					Name:      "name",
   454  					Namespace: "ns",
   455  				},
   456  				Spec: clusterv1.ClusterSpec{
   457  					ClusterNetwork: &clusterv1.ClusterNetwork{
   458  						Pods: &clusterv1.NetworkRanges{
   459  							CIDRBlocks: []string{"capi cidr"},
   460  						},
   461  					},
   462  				},
   463  			},
   464  			managedCluster: &asocontainerservicev1.ManagedCluster{
   465  				Spec: asocontainerservicev1.ManagedCluster_Spec{
   466  					NetworkProfile: &asocontainerservicev1.ContainerServiceNetworkProfile{
   467  						PodCidr: ptr.To("user cidr"),
   468  					},
   469  				},
   470  			},
   471  			expectedErr: Incompatible{
   472  				mutation: mutation{
   473  					location: ".spec.networkProfile.podCidr",
   474  					val:      "capi cidr",
   475  					reason:   "because spec.clusterNetwork.pods.cidrBlocks[0] in Cluster ns/name is set to capi cidr",
   476  				},
   477  				userVal: "user cidr",
   478  			},
   479  		},
   480  	}
   481  
   482  	s := runtime.NewScheme()
   483  	NewGomegaWithT(t).Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   484  
   485  	for _, test := range tests {
   486  		t.Run(test.name, func(t *testing.T) {
   487  			g := NewGomegaWithT(t)
   488  
   489  			before := test.managedCluster.DeepCopy()
   490  			umc := mcUnstructured(g, test.managedCluster)
   491  
   492  			err := setManagedClusterPodCIDR(ctx, test.cluster, "", umc)
   493  			g.Expect(s.Convert(umc, test.managedCluster, nil)).To(Succeed())
   494  			if test.expectedErr != nil {
   495  				g.Expect(err).To(MatchError(test.expectedErr))
   496  				g.Expect(cmp.Diff(before, test.managedCluster)).To(BeEmpty()) // errors should never modify the resource.
   497  			} else {
   498  				g.Expect(err).NotTo(HaveOccurred())
   499  				g.Expect(cmp.Diff(test.expected, test.managedCluster)).To(BeEmpty())
   500  			}
   501  		})
   502  	}
   503  }
   504  
   505  func TestSetManagedClusterAgentPoolProfiles(t *testing.T) {
   506  	g := NewGomegaWithT(t)
   507  	ctx := context.Background()
   508  	s := runtime.NewScheme()
   509  	g.Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   510  	g.Expect(infrav1alpha.AddToScheme(s)).To(Succeed())
   511  	g.Expect(expv1.AddToScheme(s)).To(Succeed())
   512  	fakeClientBuilder := func() *fakeclient.ClientBuilder {
   513  		return fakeclient.NewClientBuilder().WithScheme(s)
   514  	}
   515  
   516  	t.Run("agent pools should not be defined on user's ManagedCluster", func(t *testing.T) {
   517  		g := NewGomegaWithT(t)
   518  
   519  		umc := mcUnstructured(g, &asocontainerservicev1.ManagedCluster{
   520  			Spec: asocontainerservicev1.ManagedCluster_Spec{
   521  				AgentPoolProfiles: []asocontainerservicev1.ManagedClusterAgentPoolProfile{{}},
   522  			},
   523  		})
   524  
   525  		err := setManagedClusterAgentPoolProfiles(ctx, nil, "", nil, "", umc)
   526  		g.Expect(err).To(MatchError(Incompatible{
   527  			mutation: mutation{
   528  				location: ".spec.agentPoolProfiles",
   529  				val:      "nil",
   530  				reason:   "because agent pool definitions must be inherited from AzureASOManagedMachinePools",
   531  			},
   532  			userVal: "<slice of length 1>",
   533  		}))
   534  	})
   535  
   536  	t.Run("agent pool profiles already created", func(t *testing.T) {
   537  		g := NewGomegaWithT(t)
   538  
   539  		namespace := "ns"
   540  		managedCluster := &asocontainerservicev1.ManagedCluster{
   541  			ObjectMeta: metav1.ObjectMeta{
   542  				Name:      "mc",
   543  				Namespace: namespace,
   544  			},
   545  			Status: asocontainerservicev1.ManagedCluster_STATUS{
   546  				AgentPoolProfiles: []asocontainerservicev1.ManagedClusterAgentPoolProfile_STATUS{{}},
   547  			},
   548  		}
   549  		umc := mcUnstructured(g, managedCluster)
   550  
   551  		c := fakeClientBuilder().
   552  			WithObjects(managedCluster).
   553  			Build()
   554  
   555  		err := setManagedClusterAgentPoolProfiles(ctx, c, namespace, nil, "", umc)
   556  		g.Expect(err).NotTo(HaveOccurred())
   557  	})
   558  
   559  	t.Run("agent pool profiles derived from managed machine pools", func(t *testing.T) {
   560  		g := NewGomegaWithT(t)
   561  
   562  		namespace := "ns"
   563  		clusterName := "cluster"
   564  		managedCluster := &asocontainerservicev1.ManagedCluster{}
   565  		umc := mcUnstructured(g, managedCluster)
   566  
   567  		asoManagedMachinePools := &infrav1alpha.AzureASOManagedMachinePoolList{
   568  			Items: []infrav1alpha.AzureASOManagedMachinePool{
   569  				{
   570  					ObjectMeta: metav1.ObjectMeta{
   571  						Name:      "wrong-label",
   572  						Namespace: namespace,
   573  						Labels: map[string]string{
   574  							clusterv1.ClusterNameLabel: "not-" + clusterName,
   575  						},
   576  						OwnerReferences: []metav1.OwnerReference{
   577  							{
   578  								APIVersion: expv1.GroupVersion.Identifier(),
   579  								Kind:       "MachinePool",
   580  								Name:       "wrong-label",
   581  							},
   582  						},
   583  					},
   584  					Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   585  						AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   586  							Resources: []runtime.RawExtension{
   587  								{
   588  									Raw: apJSON(g, &asocontainerservicev1.ManagedClustersAgentPool{
   589  										Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   590  											AzureName: "no",
   591  										},
   592  									}),
   593  								},
   594  							},
   595  						},
   596  					},
   597  				},
   598  				{
   599  					ObjectMeta: metav1.ObjectMeta{
   600  						Name:      "wrong-namespace",
   601  						Namespace: "not-" + namespace,
   602  						Labels: map[string]string{
   603  							clusterv1.ClusterNameLabel: clusterName,
   604  						},
   605  						OwnerReferences: []metav1.OwnerReference{
   606  							{
   607  								APIVersion: expv1.GroupVersion.Identifier(),
   608  								Kind:       "MachinePool",
   609  								Name:       "wrong-namespace",
   610  							},
   611  						},
   612  					},
   613  					Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   614  						AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   615  							Resources: []runtime.RawExtension{
   616  								{
   617  									Raw: apJSON(g, &asocontainerservicev1.ManagedClustersAgentPool{
   618  										Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   619  											AzureName: "no",
   620  										},
   621  									}),
   622  								},
   623  							},
   624  						},
   625  					},
   626  				},
   627  				{
   628  					ObjectMeta: metav1.ObjectMeta{
   629  						Name:      "pool0",
   630  						Namespace: namespace,
   631  						Labels: map[string]string{
   632  							clusterv1.ClusterNameLabel: clusterName,
   633  						},
   634  						OwnerReferences: []metav1.OwnerReference{
   635  							{
   636  								APIVersion: expv1.GroupVersion.Identifier(),
   637  								Kind:       "MachinePool",
   638  								Name:       "pool0",
   639  							},
   640  						},
   641  					},
   642  					Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   643  						AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   644  							Resources: []runtime.RawExtension{
   645  								{
   646  									Raw: apJSON(g, &asocontainerservicev1.ManagedClustersAgentPool{
   647  										Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   648  											AzureName: "azpool0",
   649  										},
   650  									}),
   651  								},
   652  							},
   653  						},
   654  					},
   655  				},
   656  				{
   657  					ObjectMeta: metav1.ObjectMeta{
   658  						Name:      "pool1",
   659  						Namespace: namespace,
   660  						Labels: map[string]string{
   661  							clusterv1.ClusterNameLabel: clusterName,
   662  						},
   663  						OwnerReferences: []metav1.OwnerReference{
   664  							{
   665  								APIVersion: expv1.GroupVersion.Identifier(),
   666  								Kind:       "MachinePool",
   667  								Name:       "pool1",
   668  							},
   669  						},
   670  					},
   671  					Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   672  						AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   673  							Resources: []runtime.RawExtension{
   674  								{
   675  									Raw: apJSON(g, &asocontainerservicev1.ManagedClustersAgentPool{
   676  										Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   677  											AzureName: "azpool1",
   678  										},
   679  									}),
   680  								},
   681  							},
   682  						},
   683  					},
   684  				},
   685  			},
   686  		}
   687  		machinePools := &expv1.MachinePoolList{
   688  			Items: []expv1.MachinePool{
   689  				{
   690  					ObjectMeta: metav1.ObjectMeta{
   691  						Namespace: namespace,
   692  						Name:      "wrong-label",
   693  					},
   694  				},
   695  				{
   696  					ObjectMeta: metav1.ObjectMeta{
   697  						Namespace: "not-" + namespace,
   698  						Name:      "wrong-namespace",
   699  					},
   700  				},
   701  				{
   702  					ObjectMeta: metav1.ObjectMeta{
   703  						Namespace: namespace,
   704  						Name:      "pool0",
   705  					},
   706  					Spec: expv1.MachinePoolSpec{
   707  						Replicas: ptr.To[int32](1),
   708  					},
   709  				},
   710  				{
   711  					ObjectMeta: metav1.ObjectMeta{
   712  						Namespace: namespace,
   713  						Name:      "pool1",
   714  					},
   715  					Spec: expv1.MachinePoolSpec{
   716  						Replicas: ptr.To[int32](2),
   717  					},
   718  				},
   719  			},
   720  		}
   721  		expected := &asocontainerservicev1.ManagedCluster{
   722  			Spec: asocontainerservicev1.ManagedCluster_Spec{
   723  				AgentPoolProfiles: []asocontainerservicev1.ManagedClusterAgentPoolProfile{
   724  					{Name: ptr.To("azpool0"), Count: ptr.To(1)},
   725  					{Name: ptr.To("azpool1"), Count: ptr.To(2)},
   726  				},
   727  			},
   728  		}
   729  
   730  		c := fakeClientBuilder().
   731  			WithLists(asoManagedMachinePools, machinePools).
   732  			Build()
   733  
   734  		cluster := &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName}}
   735  		err := setManagedClusterAgentPoolProfiles(ctx, c, namespace, cluster, "", umc)
   736  		g.Expect(err).NotTo(HaveOccurred())
   737  		g.Expect(s.Convert(umc, managedCluster, nil)).To(Succeed())
   738  		g.Expect(cmp.Diff(expected, managedCluster)).To(BeEmpty())
   739  	})
   740  }
   741  
   742  func TestSetAgentPoolProfilesFromAgentPools(t *testing.T) {
   743  	t.Run("stable with no pools", func(t *testing.T) {
   744  		g := NewGomegaWithT(t)
   745  
   746  		mc := &asocontainerservicev1.ManagedCluster{}
   747  		var pools []conversion.Convertible
   748  		var expected []asocontainerservicev1.ManagedClusterAgentPoolProfile
   749  
   750  		err := setAgentPoolProfilesFromAgentPools(mc, pools)
   751  		g.Expect(err).NotTo(HaveOccurred())
   752  		g.Expect(cmp.Diff(expected, mc.Spec.AgentPoolProfiles)).To(BeEmpty())
   753  	})
   754  
   755  	t.Run("stable with pools", func(t *testing.T) {
   756  		g := NewGomegaWithT(t)
   757  
   758  		mc := &asocontainerservicev1.ManagedCluster{}
   759  		pools := []conversion.Convertible{
   760  			&asocontainerservicev1.ManagedClustersAgentPool{
   761  				Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   762  					AzureName: "pool0",
   763  					MaxCount:  ptr.To(1),
   764  				},
   765  			},
   766  			// Not all pools have to be the same version, or the same version as the cluster.
   767  			&asocontainerservicev1preview.ManagedClustersAgentPool{
   768  				Spec: asocontainerservicev1preview.ManagedClusters_AgentPool_Spec{
   769  					AzureName:           "pool1",
   770  					MinCount:            ptr.To(2),
   771  					EnableCustomCATrust: ptr.To(true),
   772  				},
   773  			},
   774  		}
   775  		expected := []asocontainerservicev1.ManagedClusterAgentPoolProfile{
   776  			{
   777  				Name:     ptr.To("pool0"),
   778  				MaxCount: ptr.To(1),
   779  			},
   780  			{
   781  				Name:     ptr.To("pool1"),
   782  				MinCount: ptr.To(2),
   783  				// EnableCustomCATrust is a preview-only feature that can't be represented here, so it should be lost.
   784  			},
   785  		}
   786  
   787  		err := setAgentPoolProfilesFromAgentPools(mc, pools)
   788  		g.Expect(err).NotTo(HaveOccurred())
   789  		g.Expect(cmp.Diff(expected, mc.Spec.AgentPoolProfiles)).To(BeEmpty())
   790  	})
   791  
   792  	t.Run("preview with pools", func(t *testing.T) {
   793  		g := NewGomegaWithT(t)
   794  
   795  		mc := &asocontainerservicev1preview.ManagedCluster{}
   796  		pools := []conversion.Convertible{
   797  			&asocontainerservicev1.ManagedClustersAgentPool{
   798  				Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   799  					AzureName: "pool0",
   800  					MaxCount:  ptr.To(1),
   801  				},
   802  			},
   803  			&asocontainerservicev1preview.ManagedClustersAgentPool{
   804  				Spec: asocontainerservicev1preview.ManagedClusters_AgentPool_Spec{
   805  					AzureName:           "pool1",
   806  					MinCount:            ptr.To(2),
   807  					EnableCustomCATrust: ptr.To(true),
   808  				},
   809  			},
   810  		}
   811  		expected := []asocontainerservicev1preview.ManagedClusterAgentPoolProfile{
   812  			{
   813  				Name:     ptr.To("pool0"),
   814  				MaxCount: ptr.To(1),
   815  			},
   816  			{
   817  				Name:                ptr.To("pool1"),
   818  				MinCount:            ptr.To(2),
   819  				EnableCustomCATrust: ptr.To(true),
   820  			},
   821  		}
   822  
   823  		err := setAgentPoolProfilesFromAgentPools(mc, pools)
   824  		g.Expect(err).NotTo(HaveOccurred())
   825  		g.Expect(cmp.Diff(expected, mc.Spec.AgentPoolProfiles)).To(BeEmpty())
   826  	})
   827  }
   828  
   829  func mcJSON(g Gomega, mc *asocontainerservicev1.ManagedCluster) []byte {
   830  	mc.SetGroupVersionKind(asocontainerservicev1.GroupVersion.WithKind("ManagedCluster"))
   831  	j, err := json.Marshal(mc)
   832  	g.Expect(err).NotTo(HaveOccurred())
   833  	return j
   834  }
   835  
   836  func apJSON(g Gomega, mc *asocontainerservicev1.ManagedClustersAgentPool) []byte {
   837  	mc.SetGroupVersionKind(asocontainerservicev1.GroupVersion.WithKind("ManagedClustersAgentPool"))
   838  	j, err := json.Marshal(mc)
   839  	g.Expect(err).NotTo(HaveOccurred())
   840  	return j
   841  }
   842  
   843  func mcUnstructured(g Gomega, mc *asocontainerservicev1.ManagedCluster) *unstructured.Unstructured {
   844  	s := runtime.NewScheme()
   845  	g.Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   846  	u := &unstructured.Unstructured{}
   847  	g.Expect(s.Convert(mc, u, nil)).To(Succeed())
   848  	return u
   849  }
   850  
   851  func apUnstructured(g Gomega, ap *asocontainerservicev1.ManagedClustersAgentPool) *unstructured.Unstructured {
   852  	s := runtime.NewScheme()
   853  	g.Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   854  	u := &unstructured.Unstructured{}
   855  	g.Expect(s.Convert(ap, u, nil)).To(Succeed())
   856  	return u
   857  }