sigs.k8s.io/cluster-api-provider-aws@v1.5.5/exp/controllers/awsmanagedmachinepool_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/go-logr/logr"
    24  	"github.com/pkg/errors"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/client-go/tools/record"
    29  	ctrl "sigs.k8s.io/controller-runtime"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
    32  	"sigs.k8s.io/controller-runtime/pkg/controller"
    33  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    34  	"sigs.k8s.io/controller-runtime/pkg/handler"
    35  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    36  	"sigs.k8s.io/controller-runtime/pkg/source"
    37  
    38  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    39  	expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
    40  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    41  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/eks"
    42  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    43  	expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    44  	"sigs.k8s.io/cluster-api/util"
    45  	"sigs.k8s.io/cluster-api/util/annotations"
    46  	"sigs.k8s.io/cluster-api/util/conditions"
    47  	"sigs.k8s.io/cluster-api/util/predicates"
    48  )
    49  
    50  // AWSManagedMachinePoolReconciler reconciles a AWSManagedMachinePool object.
    51  type AWSManagedMachinePoolReconciler struct {
    52  	client.Client
    53  	Recorder             record.EventRecorder
    54  	Endpoints            []scope.ServiceEndpoint
    55  	EnableIAM            bool
    56  	AllowAdditionalRoles bool
    57  	WatchFilterValue     string
    58  }
    59  
    60  // SetupWithManager is used to setup the controller.
    61  func (r *AWSManagedMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
    62  	log := ctrl.LoggerFrom(ctx)
    63  
    64  	gvk, err := apiutil.GVKForObject(new(expinfrav1.AWSManagedMachinePool), mgr.GetScheme())
    65  	if err != nil {
    66  		return errors.Wrapf(err, "failed to find GVK for AWSManagedMachinePool")
    67  	}
    68  	managedControlPlaneToManagedMachinePoolMap := managedControlPlaneToManagedMachinePoolMapFunc(r.Client, gvk, log)
    69  	return ctrl.NewControllerManagedBy(mgr).
    70  		For(&expinfrav1.AWSManagedMachinePool{}).
    71  		WithOptions(options).
    72  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue)).
    73  		Watches(
    74  			&source.Kind{Type: &expclusterv1.MachinePool{}},
    75  			handler.EnqueueRequestsFromMapFunc(machinePoolToInfrastructureMapFunc(gvk)),
    76  		).
    77  		Watches(
    78  			&source.Kind{Type: &ekscontrolplanev1.AWSManagedControlPlane{}},
    79  			handler.EnqueueRequestsFromMapFunc(managedControlPlaneToManagedMachinePoolMap),
    80  		).
    81  		Complete(r)
    82  }
    83  
    84  // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools;machinepools/status,verbs=get;list;watch
    85  // +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;patch
    86  // +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=awsmanagedcontrolplanes;awsmanagedcontrolplanes/status,verbs=get;list;watch
    87  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmanagedmachinepools,verbs=get;list;watch;create;update;patch;delete
    88  // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmanagedmachinepools/status,verbs=get;update;patch
    89  
    90  // Reconcile reconciles AWSManagedMachinePools.
    91  func (r *AWSManagedMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
    92  	log := ctrl.LoggerFrom(ctx)
    93  
    94  	awsPool := &expinfrav1.AWSManagedMachinePool{}
    95  	if err := r.Get(ctx, req.NamespacedName, awsPool); err != nil {
    96  		if apierrors.IsNotFound(err) {
    97  			return ctrl.Result{}, nil
    98  		}
    99  		return ctrl.Result{Requeue: true}, nil
   100  	}
   101  
   102  	machinePool, err := getOwnerMachinePool(ctx, r.Client, awsPool.ObjectMeta)
   103  	if err != nil {
   104  		log.Error(err, "Failed to retrieve owner MachinePool from the API Server")
   105  		return ctrl.Result{}, err
   106  	}
   107  	if machinePool == nil {
   108  		log.Info("MachinePool Controller has not yet set OwnerRef")
   109  		return ctrl.Result{}, nil
   110  	}
   111  
   112  	log = log.WithValues("MachinePool", machinePool.Name)
   113  
   114  	cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machinePool.ObjectMeta)
   115  	if err != nil {
   116  		log.Info("Failed to retrieve Cluster from MachinePool")
   117  		return reconcile.Result{}, nil
   118  	}
   119  
   120  	if annotations.IsPaused(cluster, awsPool) {
   121  		log.Info("Reconciliation is paused for this object")
   122  		return ctrl.Result{}, nil
   123  	}
   124  
   125  	log = log.WithValues("Cluster", cluster.Name)
   126  
   127  	controlPlaneKey := client.ObjectKey{
   128  		Namespace: awsPool.Namespace,
   129  		Name:      cluster.Spec.ControlPlaneRef.Name,
   130  	}
   131  	controlPlane := &ekscontrolplanev1.AWSManagedControlPlane{}
   132  	if err := r.Client.Get(ctx, controlPlaneKey, controlPlane); err != nil {
   133  		log.Info("Failed to retrieve ControlPlane from MachinePool")
   134  		return reconcile.Result{}, nil
   135  	}
   136  
   137  	if !controlPlane.Status.Ready {
   138  		log.Info("Control plane is not ready yet")
   139  		conditions.MarkFalse(awsPool, expinfrav1.EKSNodegroupReadyCondition, expinfrav1.WaitingForEKSControlPlaneReason, clusterv1.ConditionSeverityInfo, "")
   140  		return ctrl.Result{}, nil
   141  	}
   142  
   143  	machinePoolScope, err := scope.NewManagedMachinePoolScope(scope.ManagedMachinePoolScopeParams{
   144  		Client:               r.Client,
   145  		ControllerName:       "awsmanagedmachinepool",
   146  		Cluster:              cluster,
   147  		ControlPlane:         controlPlane,
   148  		MachinePool:          machinePool,
   149  		ManagedMachinePool:   awsPool,
   150  		EnableIAM:            r.EnableIAM,
   151  		AllowAdditionalRoles: r.AllowAdditionalRoles,
   152  		Endpoints:            r.Endpoints,
   153  	})
   154  	if err != nil {
   155  		return ctrl.Result{}, errors.Wrap(err, "failed to create scope")
   156  	}
   157  
   158  	defer func() {
   159  		applicableConditions := []clusterv1.ConditionType{
   160  			expinfrav1.EKSNodegroupReadyCondition,
   161  			expinfrav1.IAMNodegroupRolesReadyCondition,
   162  		}
   163  
   164  		conditions.SetSummary(machinePoolScope.ManagedMachinePool, conditions.WithConditions(applicableConditions...), conditions.WithStepCounter())
   165  
   166  		if err := machinePoolScope.Close(); err != nil && reterr == nil {
   167  			reterr = err
   168  		}
   169  	}()
   170  
   171  	if !awsPool.ObjectMeta.DeletionTimestamp.IsZero() {
   172  		return r.reconcileDelete(ctx, machinePoolScope)
   173  	}
   174  
   175  	return r.reconcileNormal(ctx, machinePoolScope)
   176  }
   177  
   178  func (r *AWSManagedMachinePoolReconciler) reconcileNormal(
   179  	_ context.Context,
   180  	machinePoolScope *scope.ManagedMachinePoolScope,
   181  ) (ctrl.Result, error) {
   182  	machinePoolScope.Info("Reconciling AWSManagedMachinePool")
   183  
   184  	controllerutil.AddFinalizer(machinePoolScope.ManagedMachinePool, expinfrav1.ManagedMachinePoolFinalizer)
   185  	if err := machinePoolScope.PatchObject(); err != nil {
   186  		return ctrl.Result{}, err
   187  	}
   188  
   189  	ekssvc := eks.NewNodegroupService(machinePoolScope)
   190  
   191  	if err := ekssvc.ReconcilePool(); err != nil {
   192  		return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile machine pool for AWSManagedMachinePool %s/%s", machinePoolScope.ManagedMachinePool.Namespace, machinePoolScope.ManagedMachinePool.Name)
   193  	}
   194  
   195  	return ctrl.Result{}, nil
   196  }
   197  
   198  func (r *AWSManagedMachinePoolReconciler) reconcileDelete(
   199  	_ context.Context,
   200  	machinePoolScope *scope.ManagedMachinePoolScope,
   201  ) (ctrl.Result, error) {
   202  	machinePoolScope.Info("Reconciling deletion of AWSManagedMachinePool")
   203  
   204  	ekssvc := eks.NewNodegroupService(machinePoolScope)
   205  
   206  	if err := ekssvc.ReconcilePoolDelete(); err != nil {
   207  		return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile machine pool deletion for AWSManagedMachinePool %s/%s", machinePoolScope.ManagedMachinePool.Namespace, machinePoolScope.ManagedMachinePool.Name)
   208  	}
   209  
   210  	controllerutil.RemoveFinalizer(machinePoolScope.ManagedMachinePool, expinfrav1.ManagedMachinePoolFinalizer)
   211  
   212  	return reconcile.Result{}, nil
   213  }
   214  
   215  // GetOwnerClusterKey returns only the Cluster name and namespace.
   216  func GetOwnerClusterKey(obj metav1.ObjectMeta) (*client.ObjectKey, error) {
   217  	for _, ref := range obj.OwnerReferences {
   218  		if ref.Kind != "Cluster" {
   219  			continue
   220  		}
   221  		gv, err := schema.ParseGroupVersion(ref.APIVersion)
   222  		if err != nil {
   223  			return nil, errors.WithStack(err)
   224  		}
   225  		if gv.Group == clusterv1.GroupVersion.Group {
   226  			return &client.ObjectKey{
   227  				Namespace: obj.Namespace,
   228  				Name:      ref.Name,
   229  			}, nil
   230  		}
   231  	}
   232  	return nil, nil
   233  }
   234  
   235  func managedControlPlaneToManagedMachinePoolMapFunc(c client.Client, gvk schema.GroupVersionKind, log logr.Logger) handler.MapFunc {
   236  	return func(o client.Object) []reconcile.Request {
   237  		ctx := context.Background()
   238  		awsControlPlane, ok := o.(*ekscontrolplanev1.AWSManagedControlPlane)
   239  		if !ok {
   240  			panic(fmt.Sprintf("Expected a AWSManagedControlPlane but got a %T", o))
   241  		}
   242  
   243  		if !awsControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
   244  			return nil
   245  		}
   246  
   247  		clusterKey, err := GetOwnerClusterKey(awsControlPlane.ObjectMeta)
   248  		if err != nil {
   249  			log.Error(err, "couldn't get AWS control plane owner ObjectKey")
   250  			return nil
   251  		}
   252  		if clusterKey == nil {
   253  			return nil
   254  		}
   255  
   256  		managedPoolForClusterList := expclusterv1.MachinePoolList{}
   257  		if err := c.List(
   258  			ctx, &managedPoolForClusterList, client.InNamespace(clusterKey.Namespace), client.MatchingLabels{clusterv1.ClusterLabelName: clusterKey.Name},
   259  		); err != nil {
   260  			log.Error(err, "couldn't list pools for cluster")
   261  			return nil
   262  		}
   263  
   264  		mapFunc := machinePoolToInfrastructureMapFunc(gvk)
   265  
   266  		var results []ctrl.Request
   267  		for i := range managedPoolForClusterList.Items {
   268  			managedPool := mapFunc(&managedPoolForClusterList.Items[i])
   269  			results = append(results, managedPool...)
   270  		}
   271  
   272  		return results
   273  	}
   274  }