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 }