sigs.k8s.io/cluster-api-provider-azure@v1.17.0/controllers/azureasomanagedmachinepool_controller_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 controllers
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  	"time"
    24  
    25  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    26  	"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
    27  	. "github.com/onsi/gomega"
    28  	corev1 "k8s.io/api/core/v1"
    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/apimachinery/pkg/types"
    33  	"k8s.io/utils/ptr"
    34  	infrav1alpha "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha1"
    35  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    36  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    37  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    38  	ctrl "sigs.k8s.io/controller-runtime"
    39  	"sigs.k8s.io/controller-runtime/pkg/client"
    40  	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    41  )
    42  
    43  type FakeClusterTracker struct {
    44  	getClientFunc func(context.Context, types.NamespacedName) (client.Client, error)
    45  }
    46  
    47  func (c *FakeClusterTracker) GetClient(ctx context.Context, name types.NamespacedName) (client.Client, error) {
    48  	if c.getClientFunc == nil {
    49  		return nil, nil
    50  	}
    51  	return c.getClientFunc(ctx, name)
    52  }
    53  
    54  func TestAzureASOManagedMachinePoolReconcile(t *testing.T) {
    55  	ctx := context.Background()
    56  
    57  	s := runtime.NewScheme()
    58  	sb := runtime.NewSchemeBuilder(
    59  		infrav1alpha.AddToScheme,
    60  		clusterv1.AddToScheme,
    61  		expv1.AddToScheme,
    62  		asocontainerservicev1.AddToScheme,
    63  	)
    64  	NewGomegaWithT(t).Expect(sb.AddToScheme(s)).To(Succeed())
    65  	fakeClientBuilder := func() *fakeclient.ClientBuilder {
    66  		return fakeclient.NewClientBuilder().
    67  			WithScheme(s).
    68  			WithStatusSubresource(&infrav1alpha.AzureASOManagedMachinePool{})
    69  	}
    70  
    71  	t.Run("AzureASOManagedMachinePool does not exist", func(t *testing.T) {
    72  		g := NewGomegaWithT(t)
    73  
    74  		c := fakeClientBuilder().
    75  			Build()
    76  		r := &AzureASOManagedMachinePoolReconciler{
    77  			Client: c,
    78  		}
    79  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "doesn't", Name: "exist"}})
    80  		g.Expect(err).NotTo(HaveOccurred())
    81  		g.Expect(result).To(Equal(ctrl.Result{}))
    82  	})
    83  
    84  	t.Run("MachinePool does not exist", func(t *testing.T) {
    85  		g := NewGomegaWithT(t)
    86  
    87  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
    88  			ObjectMeta: metav1.ObjectMeta{
    89  				Name:      "ammp",
    90  				Namespace: "ns",
    91  				OwnerReferences: []metav1.OwnerReference{
    92  					{
    93  						APIVersion: expv1.GroupVersion.Identifier(),
    94  						Kind:       "MachinePool",
    95  						Name:       "mp",
    96  					},
    97  				},
    98  			},
    99  		}
   100  		c := fakeClientBuilder().
   101  			WithObjects(asoManagedMachinePool).
   102  			Build()
   103  		r := &AzureASOManagedMachinePoolReconciler{
   104  			Client: c,
   105  		}
   106  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   107  		g.Expect(err).To(HaveOccurred())
   108  		g.Expect(err.Error()).To(ContainSubstring("machinepools.cluster.x-k8s.io \"mp\" not found"))
   109  		g.Expect(result).To(Equal(ctrl.Result{}))
   110  	})
   111  
   112  	t.Run("Cluster does not exist", func(t *testing.T) {
   113  		g := NewGomegaWithT(t)
   114  
   115  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   116  			ObjectMeta: metav1.ObjectMeta{
   117  				Name:      "ammp",
   118  				Namespace: "ns",
   119  				OwnerReferences: []metav1.OwnerReference{
   120  					{
   121  						APIVersion: expv1.GroupVersion.Identifier(),
   122  						Kind:       "MachinePool",
   123  						Name:       "mp",
   124  					},
   125  				},
   126  			},
   127  		}
   128  		machinePool := &expv1.MachinePool{
   129  			ObjectMeta: metav1.ObjectMeta{
   130  				Name:      "mp",
   131  				Namespace: asoManagedMachinePool.Namespace,
   132  				Labels: map[string]string{
   133  					clusterv1.ClusterNameLabel: "cluster",
   134  				},
   135  			},
   136  		}
   137  		c := fakeClientBuilder().
   138  			WithObjects(asoManagedMachinePool, machinePool).
   139  			Build()
   140  		r := &AzureASOManagedMachinePoolReconciler{
   141  			Client: c,
   142  		}
   143  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   144  		g.Expect(err).To(HaveOccurred())
   145  		g.Expect(err.Error()).To(ContainSubstring("clusters.cluster.x-k8s.io \"cluster\" not found"))
   146  		g.Expect(result).To(Equal(ctrl.Result{}))
   147  	})
   148  
   149  	t.Run("adds a finalizer and block-move annotation", func(t *testing.T) {
   150  		g := NewGomegaWithT(t)
   151  
   152  		cluster := &clusterv1.Cluster{
   153  			ObjectMeta: metav1.ObjectMeta{
   154  				Name:      "cluster",
   155  				Namespace: "ns",
   156  			},
   157  			Spec: clusterv1.ClusterSpec{
   158  				ControlPlaneRef: &corev1.ObjectReference{
   159  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   160  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   161  				},
   162  			},
   163  		}
   164  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   165  			ObjectMeta: metav1.ObjectMeta{
   166  				Name:      "ammp",
   167  				Namespace: cluster.Namespace,
   168  				OwnerReferences: []metav1.OwnerReference{
   169  					{
   170  						APIVersion: expv1.GroupVersion.Identifier(),
   171  						Kind:       "MachinePool",
   172  						Name:       "mp",
   173  					},
   174  				},
   175  			},
   176  		}
   177  		machinePool := &expv1.MachinePool{
   178  			ObjectMeta: metav1.ObjectMeta{
   179  				Name:      "mp",
   180  				Namespace: cluster.Namespace,
   181  				Labels: map[string]string{
   182  					clusterv1.ClusterNameLabel: "cluster",
   183  				},
   184  			},
   185  		}
   186  		c := fakeClientBuilder().
   187  			WithObjects(asoManagedMachinePool, machinePool, cluster).
   188  			Build()
   189  		r := &AzureASOManagedMachinePoolReconciler{
   190  			Client: c,
   191  		}
   192  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   193  		g.Expect(err).NotTo(HaveOccurred())
   194  		g.Expect(result).To(Equal(ctrl.Result{Requeue: true}))
   195  
   196  		g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedMachinePool), asoManagedMachinePool)).To(Succeed())
   197  		g.Expect(asoManagedMachinePool.GetFinalizers()).To(ContainElement(clusterv1.ClusterFinalizer))
   198  		g.Expect(asoManagedMachinePool.GetAnnotations()).To(HaveKey(clusterctlv1.BlockMoveAnnotation))
   199  	})
   200  
   201  	t.Run("reconciles resources that are not ready", func(t *testing.T) {
   202  		g := NewGomegaWithT(t)
   203  
   204  		cluster := &clusterv1.Cluster{
   205  			ObjectMeta: metav1.ObjectMeta{
   206  				Name:      "cluster",
   207  				Namespace: "ns",
   208  			},
   209  			Spec: clusterv1.ClusterSpec{
   210  				ControlPlaneRef: &corev1.ObjectReference{
   211  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   212  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   213  				},
   214  			},
   215  		}
   216  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   217  			ObjectMeta: metav1.ObjectMeta{
   218  				Name:      "ammp",
   219  				Namespace: cluster.Namespace,
   220  				OwnerReferences: []metav1.OwnerReference{
   221  					{
   222  						APIVersion: expv1.GroupVersion.Identifier(),
   223  						Kind:       "MachinePool",
   224  						Name:       "mp",
   225  					},
   226  				},
   227  				Finalizers: []string{
   228  					clusterv1.ClusterFinalizer,
   229  				},
   230  				Annotations: map[string]string{
   231  					clusterctlv1.BlockMoveAnnotation: "true",
   232  				},
   233  			},
   234  			Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   235  				AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   236  					Resources: []runtime.RawExtension{
   237  						{
   238  							Raw: apJSON(g, &asocontainerservicev1.ManagedClustersAgentPool{
   239  								ObjectMeta: metav1.ObjectMeta{
   240  									Name: "ap",
   241  								},
   242  							}),
   243  						},
   244  					},
   245  				},
   246  			},
   247  			Status: infrav1alpha.AzureASOManagedMachinePoolStatus{
   248  				Ready: true,
   249  			},
   250  		}
   251  		machinePool := &expv1.MachinePool{
   252  			ObjectMeta: metav1.ObjectMeta{
   253  				Name:      "mp",
   254  				Namespace: cluster.Namespace,
   255  				Labels: map[string]string{
   256  					clusterv1.ClusterNameLabel: "cluster",
   257  				},
   258  			},
   259  		}
   260  		c := fakeClientBuilder().
   261  			WithObjects(asoManagedMachinePool, machinePool, cluster).
   262  			Build()
   263  		r := &AzureASOManagedMachinePoolReconciler{
   264  			Client: c,
   265  			newResourceReconciler: func(asoManagedMachinePool *infrav1alpha.AzureASOManagedMachinePool, _ []*unstructured.Unstructured) resourceReconciler {
   266  				return &fakeResourceReconciler{
   267  					owner: asoManagedMachinePool,
   268  					reconcileFunc: func(ctx context.Context, o client.Object) error {
   269  						asoManagedMachinePool.SetResourceStatuses([]infrav1alpha.ResourceStatus{
   270  							{Ready: true},
   271  							{Ready: false},
   272  							{Ready: true},
   273  						})
   274  						return nil
   275  					},
   276  				}
   277  			},
   278  		}
   279  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   280  		g.Expect(err).NotTo(HaveOccurred())
   281  		g.Expect(result).To(Equal(ctrl.Result{}))
   282  
   283  		g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedMachinePool), asoManagedMachinePool)).To(Succeed())
   284  		g.Expect(asoManagedMachinePool.Status.Ready).To(BeFalse())
   285  	})
   286  
   287  	t.Run("successfully reconciles normally", func(t *testing.T) {
   288  		g := NewGomegaWithT(t)
   289  
   290  		cluster := &clusterv1.Cluster{
   291  			ObjectMeta: metav1.ObjectMeta{
   292  				Name:      "cluster",
   293  				Namespace: "ns",
   294  			},
   295  			Spec: clusterv1.ClusterSpec{
   296  				ControlPlaneRef: &corev1.ObjectReference{
   297  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   298  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   299  				},
   300  			},
   301  		}
   302  		asoManagedCluster := &asocontainerservicev1.ManagedCluster{
   303  			ObjectMeta: metav1.ObjectMeta{
   304  				Name:      "mc",
   305  				Namespace: cluster.Namespace,
   306  			},
   307  			Status: asocontainerservicev1.ManagedCluster_STATUS{
   308  				NodeResourceGroup: ptr.To("MC_rg"),
   309  			},
   310  		}
   311  		asoAgentPool := &asocontainerservicev1.ManagedClustersAgentPool{
   312  			ObjectMeta: metav1.ObjectMeta{
   313  				Name:      "ap",
   314  				Namespace: cluster.Namespace,
   315  			},
   316  			Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   317  				AzureName: "pool1",
   318  				Owner: &genruntime.KnownResourceReference{
   319  					Name: asoManagedCluster.Name,
   320  				},
   321  			},
   322  			Status: asocontainerservicev1.ManagedClusters_AgentPool_STATUS{
   323  				Count: ptr.To(3),
   324  			},
   325  		}
   326  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   327  			ObjectMeta: metav1.ObjectMeta{
   328  				Name:      "ammp",
   329  				Namespace: cluster.Namespace,
   330  				OwnerReferences: []metav1.OwnerReference{
   331  					{
   332  						APIVersion: expv1.GroupVersion.Identifier(),
   333  						Kind:       "MachinePool",
   334  						Name:       "mp",
   335  					},
   336  				},
   337  				Finalizers: []string{
   338  					clusterv1.ClusterFinalizer,
   339  				},
   340  				Annotations: map[string]string{
   341  					clusterctlv1.BlockMoveAnnotation: "true",
   342  				},
   343  			},
   344  			Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   345  				AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   346  					Resources: []runtime.RawExtension{
   347  						{
   348  							Raw: apJSON(g, asoAgentPool),
   349  						},
   350  					},
   351  				},
   352  			},
   353  			Status: infrav1alpha.AzureASOManagedMachinePoolStatus{
   354  				Ready: false,
   355  			},
   356  		}
   357  		machinePool := &expv1.MachinePool{
   358  			ObjectMeta: metav1.ObjectMeta{
   359  				Name:      "mp",
   360  				Namespace: cluster.Namespace,
   361  				Labels: map[string]string{
   362  					clusterv1.ClusterNameLabel: "cluster",
   363  				},
   364  			},
   365  			Spec: expv1.MachinePoolSpec{
   366  				Replicas: ptr.To[int32](1),
   367  			},
   368  		}
   369  		c := fakeClientBuilder().
   370  			WithObjects(asoManagedMachinePool, machinePool, cluster, asoAgentPool, asoManagedCluster).
   371  			Build()
   372  		r := &AzureASOManagedMachinePoolReconciler{
   373  			Client: c,
   374  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedMachinePool, _ []*unstructured.Unstructured) resourceReconciler {
   375  				return &fakeResourceReconciler{
   376  					reconcileFunc: func(ctx context.Context, o client.Object) error {
   377  						return nil
   378  					},
   379  				}
   380  			},
   381  			Tracker: &FakeClusterTracker{
   382  				getClientFunc: func(_ context.Context, _ types.NamespacedName) (client.Client, error) {
   383  					return fakeclient.NewClientBuilder().
   384  						WithObjects(
   385  							&corev1.Node{
   386  								ObjectMeta: metav1.ObjectMeta{
   387  									Name:   "node1",
   388  									Labels: expectedNodeLabels(asoAgentPool.AzureName(), *asoManagedCluster.Status.NodeResourceGroup),
   389  								},
   390  								Spec: corev1.NodeSpec{
   391  									ProviderID: "azure://node1",
   392  								},
   393  							},
   394  							&corev1.Node{
   395  								ObjectMeta: metav1.ObjectMeta{
   396  									Name:   "node2",
   397  									Labels: expectedNodeLabels(asoAgentPool.AzureName(), *asoManagedCluster.Status.NodeResourceGroup),
   398  								},
   399  								Spec: corev1.NodeSpec{
   400  									ProviderID: "azure://node2",
   401  								},
   402  							},
   403  							&corev1.Node{
   404  								ObjectMeta: metav1.ObjectMeta{
   405  									Name: "no-labels",
   406  								},
   407  								Spec: corev1.NodeSpec{
   408  									ProviderID: "azure://node3",
   409  								},
   410  							},
   411  						).
   412  						Build(), nil
   413  				},
   414  			},
   415  		}
   416  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   417  		g.Expect(err).NotTo(HaveOccurred())
   418  		g.Expect(result).To(Equal(ctrl.Result{}))
   419  
   420  		g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedMachinePool), asoManagedMachinePool)).To(Succeed())
   421  		g.Expect(asoManagedMachinePool.Spec.ProviderIDList).To(ConsistOf("azure://node1", "azure://node2"))
   422  		g.Expect(asoManagedMachinePool.Status.Replicas).To(Equal(int32(3)))
   423  		g.Expect(asoManagedMachinePool.Status.Ready).To(BeTrue())
   424  
   425  		g.Expect(r.Get(ctx, client.ObjectKeyFromObject(machinePool), machinePool)).To(Succeed())
   426  		g.Expect(*machinePool.Spec.Replicas).To(Equal(int32(1)))
   427  	})
   428  
   429  	t.Run("successfully reconciles normally with autoscaling", func(t *testing.T) {
   430  		g := NewGomegaWithT(t)
   431  
   432  		cluster := &clusterv1.Cluster{
   433  			ObjectMeta: metav1.ObjectMeta{
   434  				Name:      "cluster",
   435  				Namespace: "ns",
   436  			},
   437  			Spec: clusterv1.ClusterSpec{
   438  				ControlPlaneRef: &corev1.ObjectReference{
   439  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   440  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   441  				},
   442  			},
   443  		}
   444  		asoManagedCluster := &asocontainerservicev1.ManagedCluster{
   445  			ObjectMeta: metav1.ObjectMeta{
   446  				Name:      "mc",
   447  				Namespace: cluster.Namespace,
   448  			},
   449  			Status: asocontainerservicev1.ManagedCluster_STATUS{
   450  				NodeResourceGroup: ptr.To("MC_rg"),
   451  			},
   452  		}
   453  		asoAgentPool := &asocontainerservicev1.ManagedClustersAgentPool{
   454  			ObjectMeta: metav1.ObjectMeta{
   455  				Name:      "ap",
   456  				Namespace: cluster.Namespace,
   457  			},
   458  			Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{
   459  				AzureName: "pool1",
   460  				Owner: &genruntime.KnownResourceReference{
   461  					Name: asoManagedCluster.Name,
   462  				},
   463  				EnableAutoScaling: ptr.To(true),
   464  			},
   465  			Status: asocontainerservicev1.ManagedClusters_AgentPool_STATUS{
   466  				Count: ptr.To(3),
   467  			},
   468  		}
   469  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   470  			ObjectMeta: metav1.ObjectMeta{
   471  				Name:      "ammp",
   472  				Namespace: cluster.Namespace,
   473  				OwnerReferences: []metav1.OwnerReference{
   474  					{
   475  						APIVersion: expv1.GroupVersion.Identifier(),
   476  						Kind:       "MachinePool",
   477  						Name:       "mp",
   478  					},
   479  				},
   480  				Finalizers: []string{
   481  					clusterv1.ClusterFinalizer,
   482  				},
   483  				Annotations: map[string]string{
   484  					clusterctlv1.BlockMoveAnnotation: "true",
   485  				},
   486  			},
   487  			Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{
   488  				AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{
   489  					Resources: []runtime.RawExtension{
   490  						{
   491  							Raw: apJSON(g, asoAgentPool),
   492  						},
   493  					},
   494  				},
   495  			},
   496  			Status: infrav1alpha.AzureASOManagedMachinePoolStatus{
   497  				Ready: false,
   498  			},
   499  		}
   500  		machinePool := &expv1.MachinePool{
   501  			ObjectMeta: metav1.ObjectMeta{
   502  				Name:      "mp",
   503  				Namespace: cluster.Namespace,
   504  				Labels: map[string]string{
   505  					clusterv1.ClusterNameLabel: "cluster",
   506  				},
   507  			},
   508  		}
   509  		c := fakeClientBuilder().
   510  			WithObjects(asoManagedMachinePool, machinePool, cluster, asoAgentPool, asoManagedCluster).
   511  			Build()
   512  		r := &AzureASOManagedMachinePoolReconciler{
   513  			Client: c,
   514  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedMachinePool, _ []*unstructured.Unstructured) resourceReconciler {
   515  				return &fakeResourceReconciler{
   516  					reconcileFunc: func(ctx context.Context, o client.Object) error {
   517  						return nil
   518  					},
   519  				}
   520  			},
   521  			Tracker: &FakeClusterTracker{
   522  				getClientFunc: func(_ context.Context, _ types.NamespacedName) (client.Client, error) {
   523  					return fakeclient.NewClientBuilder().Build(), nil
   524  				},
   525  			},
   526  		}
   527  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   528  		g.Expect(err).NotTo(HaveOccurred())
   529  		g.Expect(result).To(Equal(ctrl.Result{}))
   530  
   531  		g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedMachinePool), asoManagedMachinePool)).To(Succeed())
   532  		g.Expect(asoManagedMachinePool.Status.Replicas).To(Equal(int32(3)))
   533  		g.Expect(asoManagedMachinePool.Status.Ready).To(BeTrue())
   534  
   535  		g.Expect(r.Get(ctx, client.ObjectKeyFromObject(machinePool), machinePool)).To(Succeed())
   536  		g.Expect(*machinePool.Spec.Replicas).To(Equal(int32(3)))
   537  	})
   538  
   539  	t.Run("successfully reconciles pause", func(t *testing.T) {
   540  		g := NewGomegaWithT(t)
   541  
   542  		cluster := &clusterv1.Cluster{
   543  			ObjectMeta: metav1.ObjectMeta{
   544  				Name:      "cluster",
   545  				Namespace: "ns",
   546  			},
   547  			Spec: clusterv1.ClusterSpec{
   548  				Paused: true,
   549  				ControlPlaneRef: &corev1.ObjectReference{
   550  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   551  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   552  				},
   553  			},
   554  		}
   555  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   556  			ObjectMeta: metav1.ObjectMeta{
   557  				Name:      "ammp",
   558  				Namespace: cluster.Namespace,
   559  				OwnerReferences: []metav1.OwnerReference{
   560  					{
   561  						APIVersion: expv1.GroupVersion.Identifier(),
   562  						Kind:       "MachinePool",
   563  						Name:       "mp",
   564  					},
   565  				},
   566  				Annotations: map[string]string{
   567  					clusterctlv1.BlockMoveAnnotation: "true",
   568  				},
   569  			},
   570  		}
   571  		machinePool := &expv1.MachinePool{
   572  			ObjectMeta: metav1.ObjectMeta{
   573  				Name:      "mp",
   574  				Namespace: cluster.Namespace,
   575  				Labels: map[string]string{
   576  					clusterv1.ClusterNameLabel: "cluster",
   577  				},
   578  			},
   579  		}
   580  		c := fakeClientBuilder().
   581  			WithObjects(asoManagedMachinePool, machinePool, cluster).
   582  			Build()
   583  		r := &AzureASOManagedMachinePoolReconciler{
   584  			Client: c,
   585  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedMachinePool, _ []*unstructured.Unstructured) resourceReconciler {
   586  				return &fakeResourceReconciler{
   587  					pauseFunc: func(_ context.Context, _ client.Object) error {
   588  						return nil
   589  					},
   590  				}
   591  			},
   592  		}
   593  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   594  		g.Expect(err).NotTo(HaveOccurred())
   595  		g.Expect(result).To(Equal(ctrl.Result{}))
   596  
   597  		g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedMachinePool), asoManagedMachinePool)).To(Succeed())
   598  		g.Expect(asoManagedMachinePool.GetAnnotations()).NotTo(HaveKey(clusterctlv1.BlockMoveAnnotation))
   599  	})
   600  
   601  	t.Run("successfully reconciles delete", func(t *testing.T) {
   602  		g := NewGomegaWithT(t)
   603  
   604  		cluster := &clusterv1.Cluster{
   605  			ObjectMeta: metav1.ObjectMeta{
   606  				Name:      "cluster",
   607  				Namespace: "ns",
   608  			},
   609  			Spec: clusterv1.ClusterSpec{
   610  				ControlPlaneRef: &corev1.ObjectReference{
   611  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   612  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   613  				},
   614  			},
   615  		}
   616  		asoManagedMachinePool := &infrav1alpha.AzureASOManagedMachinePool{
   617  			ObjectMeta: metav1.ObjectMeta{
   618  				Name:      "ammp",
   619  				Namespace: cluster.Namespace,
   620  				OwnerReferences: []metav1.OwnerReference{
   621  					{
   622  						APIVersion: expv1.GroupVersion.Identifier(),
   623  						Kind:       "MachinePool",
   624  						Name:       "mp",
   625  					},
   626  				},
   627  				DeletionTimestamp: &metav1.Time{Time: time.Date(1, 0, 0, 0, 0, 0, 0, time.UTC)},
   628  				Finalizers: []string{
   629  					clusterv1.ClusterFinalizer,
   630  				},
   631  			},
   632  		}
   633  		machinePool := &expv1.MachinePool{
   634  			ObjectMeta: metav1.ObjectMeta{
   635  				Name:      "mp",
   636  				Namespace: cluster.Namespace,
   637  				Labels: map[string]string{
   638  					clusterv1.ClusterNameLabel: "cluster",
   639  				},
   640  			},
   641  		}
   642  		c := fakeClientBuilder().
   643  			WithObjects(asoManagedMachinePool, machinePool, cluster).
   644  			Build()
   645  		r := &AzureASOManagedMachinePoolReconciler{
   646  			Client: c,
   647  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedMachinePool, _ []*unstructured.Unstructured) resourceReconciler {
   648  				return &fakeResourceReconciler{
   649  					deleteFunc: func(ctx context.Context, o client.Object) error {
   650  						return nil
   651  					},
   652  				}
   653  			},
   654  		}
   655  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedMachinePool)})
   656  		g.Expect(err).NotTo(HaveOccurred())
   657  		g.Expect(result).To(Equal(ctrl.Result{}))
   658  	})
   659  }
   660  
   661  func apJSON(g Gomega, ap *asocontainerservicev1.ManagedClustersAgentPool) []byte {
   662  	ap.SetGroupVersionKind(asocontainerservicev1.GroupVersion.WithKind("ManagedClustersAgentPool"))
   663  	j, err := json.Marshal(ap)
   664  	g.Expect(err).NotTo(HaveOccurred())
   665  	return j
   666  }