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