sigs.k8s.io/cluster-api@v1.7.1/exp/internal/controllers/machinepool_controller_noderef.go (about)

     1  /*
     2  Copyright 2019 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  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	corev1 "k8s.io/api/core/v1"
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    30  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api/internal/util/taints"
    32  	"sigs.k8s.io/cluster-api/util"
    33  	"sigs.k8s.io/cluster-api/util/annotations"
    34  	"sigs.k8s.io/cluster-api/util/conditions"
    35  	"sigs.k8s.io/cluster-api/util/patch"
    36  )
    37  
    38  var (
    39  	errNoAvailableNodes = errors.New("cannot find nodes with matching ProviderIDs in ProviderIDList")
    40  )
    41  
    42  type getNodeReferencesResult struct {
    43  	references []corev1.ObjectReference
    44  	available  int
    45  	ready      int
    46  }
    47  
    48  func (r *MachinePoolReconciler) reconcileNodeRefs(ctx context.Context, cluster *clusterv1.Cluster, mp *expv1.MachinePool) (ctrl.Result, error) {
    49  	log := ctrl.LoggerFrom(ctx)
    50  
    51  	// Create a watch on the nodes in the Cluster.
    52  	if err := r.watchClusterNodes(ctx, cluster); err != nil {
    53  		return ctrl.Result{}, err
    54  	}
    55  
    56  	// Check that the MachinePool hasn't been deleted or in the process.
    57  	if !mp.DeletionTimestamp.IsZero() {
    58  		return ctrl.Result{}, nil
    59  	}
    60  
    61  	// Check that the Machine doesn't already have a NodeRefs.
    62  	// Return early if there is no work to do.
    63  	if mp.Status.Replicas == mp.Status.ReadyReplicas && len(mp.Status.NodeRefs) == int(mp.Status.ReadyReplicas) {
    64  		conditions.MarkTrue(mp, expv1.ReplicasReadyCondition)
    65  		return ctrl.Result{}, nil
    66  	}
    67  
    68  	// Check that the MachinePool has valid ProviderIDList.
    69  	if len(mp.Spec.ProviderIDList) == 0 && (mp.Spec.Replicas == nil || *mp.Spec.Replicas != 0) {
    70  		log.V(2).Info("MachinePool doesn't have any ProviderIDs yet")
    71  		return ctrl.Result{}, nil
    72  	}
    73  
    74  	clusterClient, err := r.Tracker.GetClient(ctx, util.ObjectKey(cluster))
    75  	if err != nil {
    76  		return ctrl.Result{}, err
    77  	}
    78  
    79  	if err = r.deleteRetiredNodes(ctx, clusterClient, mp.Status.NodeRefs, mp.Spec.ProviderIDList); err != nil {
    80  		return ctrl.Result{}, err
    81  	}
    82  
    83  	// Get the Node references.
    84  	nodeRefsResult, err := r.getNodeReferences(ctx, clusterClient, mp.Spec.ProviderIDList)
    85  	if err != nil {
    86  		if err == errNoAvailableNodes {
    87  			log.Info("Cannot assign NodeRefs to MachinePool, no matching Nodes")
    88  			// No need to requeue here. Nodes emit an event that triggers reconciliation.
    89  			return ctrl.Result{}, nil
    90  		}
    91  		r.recorder.Event(mp, corev1.EventTypeWarning, "FailedSetNodeRef", err.Error())
    92  		return ctrl.Result{}, errors.Wrapf(err, "failed to get node references")
    93  	}
    94  
    95  	mp.Status.ReadyReplicas = int32(nodeRefsResult.ready)
    96  	mp.Status.AvailableReplicas = int32(nodeRefsResult.available)
    97  	mp.Status.UnavailableReplicas = mp.Status.Replicas - mp.Status.AvailableReplicas
    98  	mp.Status.NodeRefs = nodeRefsResult.references
    99  
   100  	log.Info("Set MachinePools's NodeRefs", "noderefs", mp.Status.NodeRefs)
   101  	r.recorder.Event(mp, corev1.EventTypeNormal, "SuccessfulSetNodeRefs", fmt.Sprintf("%+v", mp.Status.NodeRefs))
   102  
   103  	// Reconcile node annotations and taints.
   104  	err = r.patchNodes(ctx, clusterClient, nodeRefsResult.references, mp)
   105  	if err != nil {
   106  		return ctrl.Result{}, err
   107  	}
   108  
   109  	if mp.Status.Replicas != mp.Status.ReadyReplicas || len(nodeRefsResult.references) != int(mp.Status.ReadyReplicas) {
   110  		log.Info("NodeRefs != ReadyReplicas", "NodeRefs", len(nodeRefsResult.references), "ReadyReplicas", mp.Status.ReadyReplicas)
   111  		conditions.MarkFalse(mp, expv1.ReplicasReadyCondition, expv1.WaitingForReplicasReadyReason, clusterv1.ConditionSeverityInfo, "")
   112  		return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
   113  	}
   114  
   115  	// At this point, the required number of replicas are ready
   116  	conditions.MarkTrue(mp, expv1.ReplicasReadyCondition)
   117  	return ctrl.Result{}, nil
   118  }
   119  
   120  // deleteRetiredNodes deletes nodes that don't have a corresponding ProviderID in Spec.ProviderIDList.
   121  // A MachinePool infrastructure provider indicates an instance in the set has been deleted by
   122  // removing its ProviderID from the slice.
   123  func (r *MachinePoolReconciler) deleteRetiredNodes(ctx context.Context, c client.Client, nodeRefs []corev1.ObjectReference, providerIDList []string) error {
   124  	log := ctrl.LoggerFrom(ctx, "providerIDList", len(providerIDList))
   125  	nodeRefsMap := make(map[string]*corev1.Node, len(nodeRefs))
   126  	for _, nodeRef := range nodeRefs {
   127  		node := &corev1.Node{}
   128  		if err := c.Get(ctx, client.ObjectKey{Name: nodeRef.Name}, node); err != nil {
   129  			log.V(2).Info("Failed to get Node, skipping", "err", err, "nodeRef.Name", nodeRef.Name)
   130  			continue
   131  		}
   132  
   133  		if node.Spec.ProviderID == "" {
   134  			log.V(2).Info("No ProviderID detected, skipping", "providerID", node.Spec.ProviderID)
   135  			continue
   136  		}
   137  
   138  		nodeRefsMap[node.Spec.ProviderID] = node
   139  	}
   140  	for _, providerID := range providerIDList {
   141  		if providerID == "" {
   142  			log.V(2).Info("No ProviderID detected, skipping", "providerID", providerID)
   143  			continue
   144  		}
   145  		delete(nodeRefsMap, providerID)
   146  	}
   147  	for _, node := range nodeRefsMap {
   148  		if err := c.Delete(ctx, node); err != nil {
   149  			return errors.Wrapf(err, "failed to delete Node")
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (r *MachinePoolReconciler) getNodeReferences(ctx context.Context, c client.Client, providerIDList []string) (getNodeReferencesResult, error) {
   156  	log := ctrl.LoggerFrom(ctx, "providerIDList", len(providerIDList))
   157  
   158  	var ready, available int
   159  	nodeRefsMap := make(map[string]corev1.Node)
   160  	nodeList := corev1.NodeList{}
   161  	for {
   162  		if err := c.List(ctx, &nodeList, client.Continue(nodeList.Continue)); err != nil {
   163  			return getNodeReferencesResult{}, errors.Wrapf(err, "failed to List nodes")
   164  		}
   165  
   166  		for _, node := range nodeList.Items {
   167  			if node.Spec.ProviderID == "" {
   168  				log.V(2).Info("No ProviderID detected, skipping", "providerID", node.Spec.ProviderID)
   169  				continue
   170  			}
   171  
   172  			nodeRefsMap[node.Spec.ProviderID] = node
   173  		}
   174  
   175  		if nodeList.Continue == "" {
   176  			break
   177  		}
   178  	}
   179  
   180  	var nodeRefs []corev1.ObjectReference
   181  	for _, providerID := range providerIDList {
   182  		if providerID == "" {
   183  			log.V(2).Info("No ProviderID detected, skipping", "providerID", providerID)
   184  			continue
   185  		}
   186  		if node, ok := nodeRefsMap[providerID]; ok {
   187  			available++
   188  			if nodeIsReady(&node) {
   189  				ready++
   190  			}
   191  			nodeRefs = append(nodeRefs, corev1.ObjectReference{
   192  				APIVersion: corev1.SchemeGroupVersion.String(),
   193  				Kind:       "Node",
   194  				Name:       node.Name,
   195  				UID:        node.UID,
   196  			})
   197  		}
   198  	}
   199  
   200  	if len(nodeRefs) == 0 && len(providerIDList) != 0 {
   201  		return getNodeReferencesResult{}, errNoAvailableNodes
   202  	}
   203  	return getNodeReferencesResult{nodeRefs, available, ready}, nil
   204  }
   205  
   206  // patchNodes patches the nodes with the cluster name and cluster namespace annotations.
   207  func (r *MachinePoolReconciler) patchNodes(ctx context.Context, c client.Client, references []corev1.ObjectReference, mp *expv1.MachinePool) error {
   208  	log := ctrl.LoggerFrom(ctx)
   209  	for _, nodeRef := range references {
   210  		node := &corev1.Node{}
   211  		if err := c.Get(ctx, client.ObjectKey{Name: nodeRef.Name}, node); err != nil {
   212  			log.V(2).Info("Failed to get Node, skipping setting annotations", "err", err, "nodeRef.Name", nodeRef.Name)
   213  			continue
   214  		}
   215  		patchHelper, err := patch.NewHelper(node, c)
   216  		if err != nil {
   217  			return err
   218  		}
   219  		desired := map[string]string{
   220  			clusterv1.ClusterNameAnnotation:      mp.Spec.ClusterName,
   221  			clusterv1.ClusterNamespaceAnnotation: mp.GetNamespace(),
   222  			clusterv1.OwnerKindAnnotation:        mp.Kind,
   223  			clusterv1.OwnerNameAnnotation:        mp.Name,
   224  		}
   225  		// Add annotations and drop NodeUninitializedTaint.
   226  		hasAnnotationChanges := annotations.AddAnnotations(node, desired)
   227  		hasTaintChanges := taints.RemoveNodeTaint(node, clusterv1.NodeUninitializedTaint)
   228  		// Patch the node if needed.
   229  		if hasAnnotationChanges || hasTaintChanges {
   230  			if err := patchHelper.Patch(ctx, node); err != nil {
   231  				log.V(2).Info("Failed patch Node to set annotations and drop taints", "err", err, "node name", node.Name)
   232  				return err
   233  			}
   234  		}
   235  	}
   236  	return nil
   237  }
   238  
   239  func nodeIsReady(node *corev1.Node) bool {
   240  	for _, n := range node.Status.Conditions {
   241  		if n.Type == corev1.NodeReady {
   242  			return n.Status == corev1.ConditionTrue
   243  		}
   244  	}
   245  	return false
   246  }