sigs.k8s.io/cluster-api-provider-azure@v1.17.0/controllers/azureasomanagedcluster_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  	"testing"
    22  	"time"
    23  
    24  	. "github.com/onsi/gomega"
    25  	corev1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	infrav1alpha "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha1"
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    34  	ctrl "sigs.k8s.io/controller-runtime"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    37  )
    38  
    39  type fakeResourceReconciler struct {
    40  	owner         client.Object
    41  	reconcileFunc func(context.Context, client.Object) error
    42  	pauseFunc     func(context.Context, client.Object) error
    43  	deleteFunc    func(context.Context, client.Object) error
    44  }
    45  
    46  func (r *fakeResourceReconciler) Reconcile(ctx context.Context) error {
    47  	if r.reconcileFunc == nil {
    48  		return nil
    49  	}
    50  	return r.reconcileFunc(ctx, r.owner)
    51  }
    52  
    53  func (r *fakeResourceReconciler) Pause(ctx context.Context) error {
    54  	if r.pauseFunc == nil {
    55  		return nil
    56  	}
    57  	return r.pauseFunc(ctx, r.owner)
    58  }
    59  
    60  func (r *fakeResourceReconciler) Delete(ctx context.Context) error {
    61  	if r.deleteFunc == nil {
    62  		return nil
    63  	}
    64  	return r.deleteFunc(ctx, r.owner)
    65  }
    66  
    67  func TestAzureASOManagedClusterReconcile(t *testing.T) {
    68  	ctx := context.Background()
    69  
    70  	s := runtime.NewScheme()
    71  	sb := runtime.NewSchemeBuilder(
    72  		infrav1alpha.AddToScheme,
    73  		clusterv1.AddToScheme,
    74  	)
    75  	NewGomegaWithT(t).Expect(sb.AddToScheme(s)).To(Succeed())
    76  
    77  	fakeClientBuilder := func() *fakeclient.ClientBuilder {
    78  		return fakeclient.NewClientBuilder().
    79  			WithScheme(s).
    80  			WithStatusSubresource(&infrav1alpha.AzureASOManagedCluster{})
    81  	}
    82  
    83  	t.Run("AzureASOManagedCluster does not exist", func(t *testing.T) {
    84  		g := NewGomegaWithT(t)
    85  
    86  		c := fakeClientBuilder().
    87  			Build()
    88  		r := &AzureASOManagedClusterReconciler{
    89  			Client: c,
    90  		}
    91  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "doesn't", Name: "exist"}})
    92  		g.Expect(err).NotTo(HaveOccurred())
    93  		g.Expect(result).To(Equal(ctrl.Result{}))
    94  	})
    95  
    96  	t.Run("Cluster does not exist", func(t *testing.T) {
    97  		g := NewGomegaWithT(t)
    98  
    99  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   100  			ObjectMeta: metav1.ObjectMeta{
   101  				Name:      "amc",
   102  				Namespace: "ns",
   103  				OwnerReferences: []metav1.OwnerReference{
   104  					{
   105  						APIVersion: clusterv1.GroupVersion.Identifier(),
   106  						Kind:       "Cluster",
   107  						Name:       "cluster",
   108  					},
   109  				},
   110  			},
   111  		}
   112  		c := fakeClientBuilder().
   113  			WithObjects(asoManagedCluster).
   114  			Build()
   115  		r := &AzureASOManagedClusterReconciler{
   116  			Client: c,
   117  		}
   118  		_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   119  		g.Expect(err).To(HaveOccurred())
   120  	})
   121  
   122  	t.Run("adds a finalizer and block-move annotation", func(t *testing.T) {
   123  		g := NewGomegaWithT(t)
   124  
   125  		cluster := &clusterv1.Cluster{
   126  			ObjectMeta: metav1.ObjectMeta{
   127  				Name:      "cluster",
   128  				Namespace: "ns",
   129  			},
   130  			Spec: clusterv1.ClusterSpec{
   131  				ControlPlaneRef: &corev1.ObjectReference{
   132  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   133  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   134  				},
   135  			},
   136  		}
   137  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   138  			ObjectMeta: metav1.ObjectMeta{
   139  				Name:      "amc",
   140  				Namespace: cluster.Namespace,
   141  				OwnerReferences: []metav1.OwnerReference{
   142  					{
   143  						APIVersion: clusterv1.GroupVersion.Identifier(),
   144  						Kind:       "Cluster",
   145  						Name:       cluster.Name,
   146  					},
   147  				},
   148  			},
   149  		}
   150  		c := fakeClientBuilder().
   151  			WithObjects(cluster, asoManagedCluster).
   152  			Build()
   153  		r := &AzureASOManagedClusterReconciler{
   154  			Client: c,
   155  		}
   156  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   157  		g.Expect(err).NotTo(HaveOccurred())
   158  		g.Expect(result).To(Equal(ctrl.Result{Requeue: true}))
   159  
   160  		g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedCluster), asoManagedCluster)).To(Succeed())
   161  		g.Expect(asoManagedCluster.GetFinalizers()).To(ContainElement(clusterv1.ClusterFinalizer))
   162  		g.Expect(asoManagedCluster.GetAnnotations()).To(HaveKey(clusterctlv1.BlockMoveAnnotation))
   163  	})
   164  
   165  	t.Run("reconciles resources that are not ready", func(t *testing.T) {
   166  		g := NewGomegaWithT(t)
   167  
   168  		cluster := &clusterv1.Cluster{
   169  			ObjectMeta: metav1.ObjectMeta{
   170  				Name:      "cluster",
   171  				Namespace: "ns",
   172  			},
   173  			Spec: clusterv1.ClusterSpec{
   174  				ControlPlaneRef: &corev1.ObjectReference{
   175  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   176  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   177  				},
   178  			},
   179  		}
   180  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   181  			ObjectMeta: metav1.ObjectMeta{
   182  				Name:      "amc",
   183  				Namespace: cluster.Namespace,
   184  				OwnerReferences: []metav1.OwnerReference{
   185  					{
   186  						APIVersion: clusterv1.GroupVersion.Identifier(),
   187  						Kind:       "Cluster",
   188  						Name:       cluster.Name,
   189  					},
   190  				},
   191  				Finalizers: []string{
   192  					clusterv1.ClusterFinalizer,
   193  				},
   194  				Annotations: map[string]string{
   195  					clusterctlv1.BlockMoveAnnotation: "true",
   196  				},
   197  			},
   198  			Status: infrav1alpha.AzureASOManagedClusterStatus{
   199  				Ready: true,
   200  			},
   201  		}
   202  		c := fakeClientBuilder().
   203  			WithObjects(cluster, asoManagedCluster).
   204  			Build()
   205  		r := &AzureASOManagedClusterReconciler{
   206  			Client: c,
   207  			newResourceReconciler: func(asoManagedCluster *infrav1alpha.AzureASOManagedCluster, _ []*unstructured.Unstructured) resourceReconciler {
   208  				return &fakeResourceReconciler{
   209  					owner: asoManagedCluster,
   210  					reconcileFunc: func(ctx context.Context, o client.Object) error {
   211  						asoManagedCluster.SetResourceStatuses([]infrav1alpha.ResourceStatus{
   212  							{Ready: true},
   213  							{Ready: false},
   214  							{Ready: true},
   215  						})
   216  						return nil
   217  					},
   218  				}
   219  			},
   220  		}
   221  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   222  		g.Expect(err).NotTo(HaveOccurred())
   223  		g.Expect(result).To(Equal(ctrl.Result{}))
   224  
   225  		g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedCluster), asoManagedCluster)).To(Succeed())
   226  		g.Expect(asoManagedCluster.Status.Ready).To(BeFalse())
   227  	})
   228  
   229  	t.Run("successfully reconciles normally", func(t *testing.T) {
   230  		g := NewGomegaWithT(t)
   231  
   232  		cluster := &clusterv1.Cluster{
   233  			ObjectMeta: metav1.ObjectMeta{
   234  				Name:      "cluster",
   235  				Namespace: "ns",
   236  			},
   237  			Spec: clusterv1.ClusterSpec{
   238  				ControlPlaneRef: &corev1.ObjectReference{
   239  					APIVersion: infrav1alpha.GroupVersion.Identifier(),
   240  					Kind:       infrav1alpha.AzureASOManagedControlPlaneKind,
   241  					Name:       "amcp",
   242  					Namespace:  "ns",
   243  				},
   244  			},
   245  		}
   246  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   247  			ObjectMeta: metav1.ObjectMeta{
   248  				Name:      "amc",
   249  				Namespace: cluster.Namespace,
   250  				OwnerReferences: []metav1.OwnerReference{
   251  					{
   252  						APIVersion: clusterv1.GroupVersion.Identifier(),
   253  						Kind:       "Cluster",
   254  						Name:       cluster.Name,
   255  					},
   256  				},
   257  				Finalizers: []string{
   258  					clusterv1.ClusterFinalizer,
   259  				},
   260  				Annotations: map[string]string{
   261  					clusterctlv1.BlockMoveAnnotation: "true",
   262  				},
   263  			},
   264  			Status: infrav1alpha.AzureASOManagedClusterStatus{
   265  				Ready: false,
   266  			},
   267  		}
   268  		asoManagedControlPlane := &infrav1alpha.AzureASOManagedControlPlane{
   269  			ObjectMeta: metav1.ObjectMeta{
   270  				Name:      "amcp",
   271  				Namespace: cluster.Namespace,
   272  			},
   273  			Status: infrav1alpha.AzureASOManagedControlPlaneStatus{
   274  				ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "endpoint"},
   275  			},
   276  		}
   277  		c := fakeClientBuilder().
   278  			WithObjects(cluster, asoManagedCluster, asoManagedControlPlane).
   279  			Build()
   280  		r := &AzureASOManagedClusterReconciler{
   281  			Client: c,
   282  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedCluster, _ []*unstructured.Unstructured) resourceReconciler {
   283  				return &fakeResourceReconciler{
   284  					reconcileFunc: func(ctx context.Context, o client.Object) error {
   285  						return nil
   286  					},
   287  				}
   288  			},
   289  		}
   290  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   291  		g.Expect(err).NotTo(HaveOccurred())
   292  		g.Expect(result).To(Equal(ctrl.Result{}))
   293  
   294  		g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedCluster), asoManagedCluster)).To(Succeed())
   295  		g.Expect(asoManagedCluster.Spec.ControlPlaneEndpoint.Host).To(Equal("endpoint"))
   296  		g.Expect(asoManagedCluster.Status.Ready).To(BeTrue())
   297  	})
   298  
   299  	t.Run("successfully reconciles pause", func(t *testing.T) {
   300  		g := NewGomegaWithT(t)
   301  
   302  		cluster := &clusterv1.Cluster{
   303  			ObjectMeta: metav1.ObjectMeta{
   304  				Name:      "cluster",
   305  				Namespace: "ns",
   306  			},
   307  			Spec: clusterv1.ClusterSpec{
   308  				Paused: true,
   309  			},
   310  		}
   311  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   312  			ObjectMeta: metav1.ObjectMeta{
   313  				Name:      "amc",
   314  				Namespace: cluster.Namespace,
   315  				OwnerReferences: []metav1.OwnerReference{
   316  					{
   317  						APIVersion: clusterv1.GroupVersion.Identifier(),
   318  						Kind:       "Cluster",
   319  						Name:       cluster.Name,
   320  					},
   321  				},
   322  				Annotations: map[string]string{
   323  					clusterctlv1.BlockMoveAnnotation: "true",
   324  				},
   325  			},
   326  		}
   327  		c := fakeClientBuilder().
   328  			WithObjects(cluster, asoManagedCluster).
   329  			Build()
   330  		r := &AzureASOManagedClusterReconciler{
   331  			Client: c,
   332  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedCluster, _ []*unstructured.Unstructured) resourceReconciler {
   333  				return &fakeResourceReconciler{
   334  					pauseFunc: func(_ context.Context, _ client.Object) error {
   335  						return nil
   336  					},
   337  				}
   338  			},
   339  		}
   340  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   341  		g.Expect(err).NotTo(HaveOccurred())
   342  		g.Expect(result).To(Equal(ctrl.Result{}))
   343  
   344  		g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedCluster), asoManagedCluster)).To(Succeed())
   345  		g.Expect(asoManagedCluster.GetAnnotations()).NotTo(HaveKey(clusterctlv1.BlockMoveAnnotation))
   346  	})
   347  
   348  	t.Run("successfully reconciles in-progress delete", func(t *testing.T) {
   349  		g := NewGomegaWithT(t)
   350  
   351  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   352  			ObjectMeta: metav1.ObjectMeta{
   353  				Name:      "amc",
   354  				Namespace: "ns",
   355  				Finalizers: []string{
   356  					clusterv1.ClusterFinalizer,
   357  				},
   358  				DeletionTimestamp: &metav1.Time{Time: time.Date(1, 0, 0, 0, 0, 0, 0, time.UTC)},
   359  			},
   360  		}
   361  		c := fakeClientBuilder().
   362  			WithObjects(asoManagedCluster).
   363  			Build()
   364  		r := &AzureASOManagedClusterReconciler{
   365  			Client: c,
   366  			newResourceReconciler: func(asoManagedCluster *infrav1alpha.AzureASOManagedCluster, _ []*unstructured.Unstructured) resourceReconciler {
   367  				return &fakeResourceReconciler{
   368  					owner: asoManagedCluster,
   369  					deleteFunc: func(ctx context.Context, o client.Object) error {
   370  						asoManagedCluster.SetResourceStatuses([]infrav1alpha.ResourceStatus{
   371  							{
   372  								Resource: infrav1alpha.StatusResource{
   373  									Name: "still-deleting",
   374  								},
   375  							},
   376  						})
   377  						return nil
   378  					},
   379  				}
   380  			},
   381  		}
   382  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   383  		g.Expect(err).NotTo(HaveOccurred())
   384  		g.Expect(result).To(Equal(ctrl.Result{}))
   385  
   386  		err = c.Get(ctx, client.ObjectKeyFromObject(asoManagedCluster), asoManagedCluster)
   387  		g.Expect(err).NotTo(HaveOccurred())
   388  		g.Expect(asoManagedCluster.GetFinalizers()).To(ContainElement(clusterv1.ClusterFinalizer))
   389  	})
   390  
   391  	t.Run("successfully reconciles finished delete", func(t *testing.T) {
   392  		g := NewGomegaWithT(t)
   393  
   394  		asoManagedCluster := &infrav1alpha.AzureASOManagedCluster{
   395  			ObjectMeta: metav1.ObjectMeta{
   396  				Name:      "amc",
   397  				Namespace: "ns",
   398  				Finalizers: []string{
   399  					clusterv1.ClusterFinalizer,
   400  				},
   401  				DeletionTimestamp: &metav1.Time{Time: time.Date(1, 0, 0, 0, 0, 0, 0, time.UTC)},
   402  			},
   403  		}
   404  		c := fakeClientBuilder().
   405  			WithObjects(asoManagedCluster).
   406  			Build()
   407  		r := &AzureASOManagedClusterReconciler{
   408  			Client: c,
   409  			newResourceReconciler: func(_ *infrav1alpha.AzureASOManagedCluster, _ []*unstructured.Unstructured) resourceReconciler {
   410  				return &fakeResourceReconciler{
   411  					deleteFunc: func(ctx context.Context, o client.Object) error {
   412  						return nil
   413  					},
   414  				}
   415  			},
   416  		}
   417  		result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(asoManagedCluster)})
   418  		g.Expect(err).NotTo(HaveOccurred())
   419  		g.Expect(result).To(Equal(ctrl.Result{}))
   420  
   421  		err = c.Get(ctx, client.ObjectKeyFromObject(asoManagedCluster), asoManagedCluster)
   422  		g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
   423  	})
   424  }