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  }