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 }