sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azuremanagedcontrolplane_controller.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 controllers 18 19 import ( 20 "context" 21 "fmt" 22 23 "github.com/pkg/errors" 24 corev1 "k8s.io/api/core/v1" 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 "k8s.io/client-go/tools/record" 27 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 28 "sigs.k8s.io/cluster-api-provider-azure/azure" 29 "sigs.k8s.io/cluster-api-provider-azure/azure/scope" 30 "sigs.k8s.io/cluster-api-provider-azure/pkg/coalescing" 31 "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 32 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 35 capiexputil "sigs.k8s.io/cluster-api/exp/util" 36 "sigs.k8s.io/cluster-api/util" 37 "sigs.k8s.io/cluster-api/util/annotations" 38 "sigs.k8s.io/cluster-api/util/predicates" 39 ctrl "sigs.k8s.io/controller-runtime" 40 "sigs.k8s.io/controller-runtime/pkg/client" 41 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 42 "sigs.k8s.io/controller-runtime/pkg/handler" 43 "sigs.k8s.io/controller-runtime/pkg/reconcile" 44 "sigs.k8s.io/controller-runtime/pkg/source" 45 ) 46 47 // AzureManagedControlPlaneReconciler reconciles an AzureManagedControlPlane object. 48 type AzureManagedControlPlaneReconciler struct { 49 client.Client 50 Recorder record.EventRecorder 51 Timeouts reconciler.Timeouts 52 WatchFilterValue string 53 getNewAzureManagedControlPlaneReconciler func(scope *scope.ManagedControlPlaneScope) (*azureManagedControlPlaneService, error) 54 } 55 56 // SetupWithManager initializes this controller with a manager. 57 func (amcpr *AzureManagedControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options Options) error { 58 ctx, log, done := tele.StartSpanWithLogger(ctx, 59 "controllers.AzureManagedControlPlaneReconciler.SetupWithManager", 60 tele.KVP("controller", "AzureManagedControlPlane"), 61 ) 62 defer done() 63 64 amcpr.getNewAzureManagedControlPlaneReconciler = newAzureManagedControlPlaneReconciler 65 var r reconcile.Reconciler = amcpr 66 if options.Cache != nil { 67 r = coalescing.NewReconciler(amcpr, options.Cache, log) 68 } 69 70 azManagedControlPlane := &infrav1.AzureManagedControlPlane{} 71 // create mapper to transform incoming AzureManagedClusters into AzureManagedControlPlane requests 72 azureManagedClusterMapper, err := AzureManagedClusterToAzureManagedControlPlaneMapper(ctx, amcpr.Client, log) 73 if err != nil { 74 return errors.Wrap(err, "failed to create AzureManagedCluster to AzureManagedControlPlane mapper") 75 } 76 77 // map requests for machine pools corresponding to AzureManagedControlPlane's defaultPool back to the corresponding AzureManagedControlPlane. 78 azureManagedMachinePoolMapper := MachinePoolToAzureManagedControlPlaneMapFunc(ctx, amcpr.Client, infrav1.GroupVersion.WithKind(infrav1.AzureManagedControlPlaneKind), log) 79 80 c, err := ctrl.NewControllerManagedBy(mgr). 81 WithOptions(options.Options). 82 For(azManagedControlPlane). 83 WithEventFilter(predicates.ResourceHasFilterLabel(log, amcpr.WatchFilterValue)). 84 // watch AzureManagedCluster resources 85 Watches( 86 &infrav1.AzureManagedCluster{}, 87 handler.EnqueueRequestsFromMapFunc(azureManagedClusterMapper), 88 ). 89 // watch MachinePool resources 90 Watches( 91 &expv1.MachinePool{}, 92 handler.EnqueueRequestsFromMapFunc(azureManagedMachinePoolMapper), 93 ). 94 Build(r) 95 if err != nil { 96 return errors.Wrap(err, "error creating controller") 97 } 98 99 // Add a watch on clusterv1.Cluster object for pause/unpause & ready notifications. 100 if err = c.Watch( 101 source.Kind(mgr.GetCache(), &clusterv1.Cluster{}), 102 handler.EnqueueRequestsFromMapFunc(amcpr.ClusterToAzureManagedControlPlane), 103 ClusterPauseChangeAndInfrastructureReady(log), 104 predicates.ResourceHasFilterLabel(log, amcpr.WatchFilterValue), 105 ); err != nil { 106 return errors.Wrap(err, "failed adding a watch for ready clusters") 107 } 108 109 return nil 110 } 111 112 // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremanagedcontrolplanes,verbs=get;list;watch;create;update;patch;delete 113 // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremanagedcontrolplanes/status,verbs=get;update;patch 114 // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch 115 // +kubebuilder:rbac:groups=resources.azure.com,resources=resourcegroups,verbs=get;list;watch;create;update;patch;delete 116 // +kubebuilder:rbac:groups=resources.azure.com,resources=resourcegroups/status,verbs=get;list;watch 117 // +kubebuilder:rbac:groups=containerservice.azure.com,resources=managedclusters,verbs=get;list;watch;create;update;patch;delete 118 // +kubebuilder:rbac:groups=containerservice.azure.com,resources=managedclusters/status,verbs=get;list;watch 119 // +kubebuilder:rbac:groups=network.azure.com,resources=privateendpoints;virtualnetworks;virtualnetworkssubnets,verbs=get;list;watch;create;update;patch;delete 120 // +kubebuilder:rbac:groups=network.azure.com,resources=privateendpoints/status;virtualnetworks/status;virtualnetworkssubnets/status,verbs=get;list;watch 121 // +kubebuilder:rbac:groups=containerservice.azure.com,resources=fleetsmembers,verbs=get;list;watch;create;update;patch;delete 122 // +kubebuilder:rbac:groups=containerservice.azure.com,resources=fleetsmembers/status,verbs=get;list;watch 123 // +kubebuilder:rbac:groups=kubernetesconfiguration.azure.com,resources=extensions,verbs=get;list;watch;create;update;patch;delete 124 // +kubebuilder:rbac:groups=kubernetesconfiguration.azure.com,resources=extensions/status,verbs=get;list;watch 125 126 // Reconcile idempotently gets, creates, and updates a managed control plane. 127 func (amcpr *AzureManagedControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { 128 ctx, cancel := context.WithTimeout(ctx, amcpr.Timeouts.DefaultedLoopTimeout()) 129 defer cancel() 130 131 ctx, log, done := tele.StartSpanWithLogger(ctx, "controllers.AzureManagedControlPlaneReconciler.Reconcile", 132 tele.KVP("namespace", req.Namespace), 133 tele.KVP("name", req.Name), 134 tele.KVP("kind", infrav1.AzureManagedControlPlaneKind), 135 ) 136 defer done() 137 138 // Fetch the AzureManagedControlPlane instance 139 azureControlPlane := &infrav1.AzureManagedControlPlane{} 140 err := amcpr.Get(ctx, req.NamespacedName, azureControlPlane) 141 if err != nil { 142 if apierrors.IsNotFound(err) { 143 return reconcile.Result{}, nil 144 } 145 return reconcile.Result{}, err 146 } 147 148 // Fetch the Cluster. 149 cluster, err := util.GetOwnerCluster(ctx, amcpr.Client, azureControlPlane.ObjectMeta) 150 if err != nil { 151 return reconcile.Result{}, err 152 } 153 if cluster == nil { 154 log.Info("Cluster Controller has not yet set OwnerRef") 155 return reconcile.Result{}, nil 156 } 157 158 log = log.WithValues("cluster", cluster.Name) 159 160 // Fetch all the ManagedMachinePools owned by this Cluster. 161 opt1 := client.InNamespace(azureControlPlane.Namespace) 162 opt2 := client.MatchingLabels(map[string]string{ 163 clusterv1.ClusterNameLabel: cluster.Name, 164 }) 165 166 ammpList := &infrav1.AzureManagedMachinePoolList{} 167 if err := amcpr.List(ctx, ammpList, opt1, opt2); err != nil { 168 return reconcile.Result{}, err 169 } 170 171 var pools = make([]scope.ManagedMachinePool, len(ammpList.Items)) 172 173 for i, ammp := range ammpList.Items { 174 // Fetch the owner MachinePool. 175 ownerPool, err := capiexputil.GetOwnerMachinePool(ctx, amcpr.Client, ammp.ObjectMeta) 176 if err != nil || ownerPool == nil { 177 return reconcile.Result{}, errors.Wrapf(err, "failed to fetch owner MachinePool for AzureManagedMachinePool: %s", ammp.Name) 178 } 179 pools[i] = scope.ManagedMachinePool{ 180 InfraMachinePool: &ammpList.Items[i], 181 MachinePool: ownerPool, 182 } 183 } 184 185 // Create the scope. 186 mcpScope, err := scope.NewManagedControlPlaneScope(ctx, scope.ManagedControlPlaneScopeParams{ 187 Client: amcpr.Client, 188 Cluster: cluster, 189 ControlPlane: azureControlPlane, 190 ManagedMachinePools: pools, 191 Timeouts: amcpr.Timeouts, 192 }) 193 if err != nil { 194 return reconcile.Result{}, errors.Wrap(err, "failed to create scope") 195 } 196 197 // Always patch when exiting so we can persist changes to finalizers and status 198 defer func() { 199 if err := mcpScope.Close(ctx); err != nil && reterr == nil { 200 reterr = err 201 } 202 }() 203 204 // Return early if the object or Cluster is paused. 205 if annotations.IsPaused(cluster, azureControlPlane) { 206 log.Info("AzureManagedControlPlane or linked Cluster is marked as paused. Won't reconcile normally") 207 return amcpr.reconcilePause(ctx, mcpScope) 208 } 209 210 // check if the control plane's namespace is allowed for this identity and update owner references for the identity. 211 if azureControlPlane.Spec.IdentityRef != nil { 212 err := EnsureClusterIdentity(ctx, amcpr.Client, azureControlPlane, azureControlPlane.Spec.IdentityRef, infrav1.ManagedClusterFinalizer) 213 if err != nil { 214 return reconcile.Result{}, err 215 } 216 } else { 217 warningMessage := "You're using deprecated functionality: " + 218 "Using Azure credentials from the manager environment is deprecated and will be removed in future releases. " + 219 "Please specify an AzureClusterIdentity for the AzureManagedControlPlane instead, see: https://capz.sigs.k8s.io/topics/multitenancy.html " 220 log.Info(fmt.Sprintf("WARNING, %s", warningMessage)) 221 amcpr.Recorder.Eventf(azureControlPlane, corev1.EventTypeWarning, "AzureClusterIdentity", warningMessage) 222 } 223 224 // Handle deleted clusters 225 if !azureControlPlane.DeletionTimestamp.IsZero() { 226 return amcpr.reconcileDelete(ctx, mcpScope) 227 } 228 // Handle non-deleted clusters 229 return amcpr.reconcileNormal(ctx, mcpScope) 230 } 231 232 func (amcpr *AzureManagedControlPlaneReconciler) reconcileNormal(ctx context.Context, scope *scope.ManagedControlPlaneScope) (reconcile.Result, error) { 233 ctx, log, done := tele.StartSpanWithLogger(ctx, "controllers.AzureManagedControlPlaneReconciler.reconcileNormal") 234 defer done() 235 236 log.Info("Reconciling AzureManagedControlPlane") 237 238 // Remove deprecated Cluster finalizer if it exists 239 needsPatch := controllerutil.RemoveFinalizer(scope.ControlPlane, infrav1.ClusterFinalizer) 240 // Register our finalizer immediately to avoid orphaning Azure resources on delete 241 needsPatch = controllerutil.AddFinalizer(scope.ControlPlane, infrav1.ManagedClusterFinalizer) || needsPatch 242 // Register the block-move annotation immediately to avoid moving un-paused ASO resources 243 needsPatch = AddBlockMoveAnnotation(scope.ControlPlane) || needsPatch 244 if needsPatch { 245 if err := scope.PatchObject(ctx); err != nil { 246 amcpr.Recorder.Eventf(scope.ControlPlane, corev1.EventTypeWarning, "AzureManagedControlPlane unavailable", "failed to patch resource: %s", err) 247 return reconcile.Result{}, err 248 } 249 } 250 251 svc, err := amcpr.getNewAzureManagedControlPlaneReconciler(scope) 252 if err != nil { 253 return reconcile.Result{}, errors.Wrap(err, "failed to create azureManagedControlPlane service") 254 } 255 if err := svc.Reconcile(ctx); err != nil { 256 // Handle transient and terminal errors 257 log := log.WithValues("name", scope.ControlPlane.Name, "namespace", scope.ControlPlane.Namespace) 258 var reconcileError azure.ReconcileError 259 if errors.As(err, &reconcileError) { 260 if reconcileError.IsTerminal() { 261 log.Error(err, "failed to reconcile AzureManagedControlPlane") 262 return reconcile.Result{}, nil 263 } 264 265 if reconcileError.IsTransient() { 266 log.V(4).Info("requeuing due to transient failure", "error", err) 267 return reconcile.Result{RequeueAfter: reconcileError.RequeueAfter()}, nil 268 } 269 270 return reconcile.Result{}, errors.Wrap(err, "failed to reconcile AzureManagedControlPlane") 271 } 272 273 return reconcile.Result{}, errors.Wrapf(err, "error creating AzureManagedControlPlane %s/%s", scope.ControlPlane.Namespace, scope.ControlPlane.Name) 274 } 275 276 // No errors, so mark us ready so the Cluster API Cluster Controller can pull it 277 scope.ControlPlane.Status.Ready = true 278 scope.ControlPlane.Status.Initialized = true 279 scope.ControlPlane.Status.Version = scope.ControlPlane.Spec.Version 280 281 log.Info("Successfully reconciled") 282 283 return reconcile.Result{}, nil 284 } 285 286 //nolint:unparam // Always returns an empty struct for reconcile.Result 287 func (amcpr *AzureManagedControlPlaneReconciler) reconcilePause(ctx context.Context, scope *scope.ManagedControlPlaneScope) (reconcile.Result, error) { 288 ctx, log, done := tele.StartSpanWithLogger(ctx, "controllers.AzureManagedControlPlane.reconcilePause") 289 defer done() 290 291 log.Info("Reconciling AzureManagedControlPlane pause") 292 293 svc, err := amcpr.getNewAzureManagedControlPlaneReconciler(scope) 294 if err != nil { 295 return reconcile.Result{}, errors.Wrap(err, "failed to create azureManagedControlPlane service") 296 } 297 if err := svc.Pause(ctx); err != nil { 298 return reconcile.Result{}, errors.Wrap(err, "failed to pause control plane services") 299 } 300 RemoveBlockMoveAnnotation(scope.ControlPlane) 301 302 return reconcile.Result{}, nil 303 } 304 305 func (amcpr *AzureManagedControlPlaneReconciler) reconcileDelete(ctx context.Context, scope *scope.ManagedControlPlaneScope) (reconcile.Result, error) { 306 ctx, log, done := tele.StartSpanWithLogger(ctx, "controllers.AzureManagedControlPlaneReconciler.reconcileDelete") 307 defer done() 308 309 log.Info("Reconciling AzureManagedControlPlane delete") 310 311 svc, err := amcpr.getNewAzureManagedControlPlaneReconciler(scope) 312 if err != nil { 313 return reconcile.Result{}, errors.Wrap(err, "failed to create azureManagedControlPlane service") 314 } 315 if err := svc.Delete(ctx); err != nil { 316 // Handle transient errors 317 var reconcileError azure.ReconcileError 318 if errors.As(err, &reconcileError) && reconcileError.IsTransient() { 319 if azure.IsOperationNotDoneError(reconcileError) { 320 log.V(2).Info(fmt.Sprintf("AzureManagedControlPlane delete not done: %s", reconcileError.Error())) 321 } else { 322 log.V(2).Info("transient failure to delete AzureManagedControlPlane, retrying") 323 } 324 return reconcile.Result{RequeueAfter: reconcileError.RequeueAfter()}, nil 325 } 326 return reconcile.Result{}, errors.Wrapf(err, "error deleting AzureManagedControlPlane %s/%s", scope.ControlPlane.Namespace, scope.ControlPlane.Name) 327 } 328 329 // Cluster is deleted so remove the finalizer. 330 controllerutil.RemoveFinalizer(scope.ControlPlane, infrav1.ManagedClusterFinalizer) 331 332 if scope.ControlPlane.Spec.IdentityRef != nil { 333 err := RemoveClusterIdentityFinalizer(ctx, amcpr.Client, scope.ControlPlane, scope.ControlPlane.Spec.IdentityRef, infrav1.ManagedClusterFinalizer) 334 if err != nil { 335 return reconcile.Result{}, err 336 } 337 } 338 339 return reconcile.Result{}, nil 340 } 341 342 // ClusterToAzureManagedControlPlane is a handler.ToRequestsFunc to be used to enqueue requests for 343 // reconciliation for AzureManagedControlPlane based on updates to a Cluster. 344 func (amcpr *AzureManagedControlPlaneReconciler) ClusterToAzureManagedControlPlane(_ context.Context, o client.Object) []ctrl.Request { 345 c, ok := o.(*clusterv1.Cluster) 346 if !ok { 347 panic(fmt.Sprintf("Expected a Cluster but got a %T", o)) 348 } 349 350 controlPlaneRef := c.Spec.ControlPlaneRef 351 if controlPlaneRef != nil && controlPlaneRef.Kind == infrav1.AzureManagedControlPlaneKind { 352 return []ctrl.Request{{NamespacedName: client.ObjectKey{Namespace: controlPlaneRef.Namespace, Name: controlPlaneRef.Name}}} 353 } 354 355 return nil 356 }