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  }