sigs.k8s.io/cluster-api@v1.6.3/exp/internal/controllers/machinepool_controller_phases.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 "reflect" 23 "time" 24 25 "github.com/pkg/errors" 26 corev1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 kerrors "k8s.io/apimachinery/pkg/util/errors" 31 "k8s.io/apimachinery/pkg/util/wait" 32 "k8s.io/apiserver/pkg/storage/names" 33 "k8s.io/klog/v2" 34 "k8s.io/utils/pointer" 35 ctrl "sigs.k8s.io/controller-runtime" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 38 "sigs.k8s.io/controller-runtime/pkg/handler" 39 40 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 41 "sigs.k8s.io/cluster-api/controllers/external" 42 capierrors "sigs.k8s.io/cluster-api/errors" 43 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 44 utilexp "sigs.k8s.io/cluster-api/exp/util" 45 "sigs.k8s.io/cluster-api/internal/util/ssa" 46 "sigs.k8s.io/cluster-api/util" 47 "sigs.k8s.io/cluster-api/util/annotations" 48 "sigs.k8s.io/cluster-api/util/conditions" 49 utilconversion "sigs.k8s.io/cluster-api/util/conversion" 50 "sigs.k8s.io/cluster-api/util/labels" 51 "sigs.k8s.io/cluster-api/util/labels/format" 52 "sigs.k8s.io/cluster-api/util/patch" 53 ) 54 55 func (r *MachinePoolReconciler) reconcilePhase(mp *expv1.MachinePool) { 56 // Set the phase to "pending" if nil. 57 if mp.Status.Phase == "" { 58 mp.Status.SetTypedPhase(expv1.MachinePoolPhasePending) 59 } 60 61 // Set the phase to "provisioning" if bootstrap is ready and the infrastructure isn't. 62 if mp.Status.BootstrapReady && !mp.Status.InfrastructureReady { 63 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseProvisioning) 64 } 65 66 // Set the phase to "provisioned" if the infrastructure is ready. 67 if len(mp.Status.NodeRefs) != 0 { 68 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseProvisioned) 69 } 70 71 // Set the phase to "running" if the number of ready replicas is equal to desired replicas. 72 if mp.Status.InfrastructureReady && *mp.Spec.Replicas == mp.Status.ReadyReplicas { 73 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseRunning) 74 } 75 76 // Set the appropriate phase in response to the MachinePool replica count being greater than the observed infrastructure replicas. 77 if mp.Status.InfrastructureReady && *mp.Spec.Replicas > mp.Status.ReadyReplicas { 78 // If we are being managed by an external autoscaler and can't predict scaling direction, set to "Scaling". 79 if annotations.ReplicasManagedByExternalAutoscaler(mp) { 80 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScaling) 81 } else { 82 // Set the phase to "ScalingUp" if we are actively scaling the infrastructure out. 83 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScalingUp) 84 } 85 } 86 87 // Set the appropriate phase in response to the MachinePool replica count being less than the observed infrastructure replicas. 88 if mp.Status.InfrastructureReady && *mp.Spec.Replicas < mp.Status.ReadyReplicas { 89 // If we are being managed by an external autoscaler and can't predict scaling direction, set to "Scaling". 90 if annotations.ReplicasManagedByExternalAutoscaler(mp) { 91 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScaling) 92 } else { 93 // Set the phase to "ScalingDown" if we are actively scaling the infrastructure in. 94 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScalingDown) 95 } 96 } 97 98 // Set the phase to "failed" if any of Status.FailureReason or Status.FailureMessage is not-nil. 99 if mp.Status.FailureReason != nil || mp.Status.FailureMessage != nil { 100 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseFailed) 101 } 102 103 // Set the phase to "deleting" if the deletion timestamp is set. 104 if !mp.DeletionTimestamp.IsZero() { 105 mp.Status.SetTypedPhase(expv1.MachinePoolPhaseDeleting) 106 } 107 } 108 109 // reconcileExternal handles generic unstructured objects referenced by a MachinePool. 110 func (r *MachinePoolReconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, m *expv1.MachinePool, ref *corev1.ObjectReference) (external.ReconcileOutput, error) { 111 log := ctrl.LoggerFrom(ctx) 112 113 if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil { 114 return external.ReconcileOutput{}, err 115 } 116 117 obj, err := external.Get(ctx, r.Client, ref, m.Namespace) 118 if err != nil { 119 if apierrors.IsNotFound(errors.Cause(err)) { 120 return external.ReconcileOutput{}, errors.Wrapf(err, "could not find %v %q for MachinePool %q in namespace %q, requeuing", 121 ref.GroupVersionKind(), ref.Name, m.Name, m.Namespace) 122 } 123 return external.ReconcileOutput{}, err 124 } 125 126 // Ensure we add a watch to the external object, if there isn't one already. 127 if err := r.externalTracker.Watch(log, obj, handler.EnqueueRequestForOwner(r.Client.Scheme(), r.Client.RESTMapper(), &expv1.MachinePool{})); err != nil { 128 return external.ReconcileOutput{}, err 129 } 130 131 // if external ref is paused, return error. 132 if annotations.IsPaused(cluster, obj) { 133 log.V(3).Info("External object referenced is paused") 134 return external.ReconcileOutput{Paused: true}, nil 135 } 136 137 // Initialize the patch helper. 138 patchHelper, err := patch.NewHelper(obj, r.Client) 139 if err != nil { 140 return external.ReconcileOutput{}, err 141 } 142 143 // Set external object ControllerReference to the MachinePool. 144 if err := controllerutil.SetControllerReference(m, obj, r.Client.Scheme()); err != nil { 145 return external.ReconcileOutput{}, err 146 } 147 148 // Set the Cluster label. 149 labels := obj.GetLabels() 150 if labels == nil { 151 labels = make(map[string]string) 152 } 153 labels[clusterv1.ClusterNameLabel] = m.Spec.ClusterName 154 obj.SetLabels(labels) 155 156 // Always attempt to Patch the external object. 157 if err := patchHelper.Patch(ctx, obj); err != nil { 158 return external.ReconcileOutput{}, err 159 } 160 161 // Set failure reason and message, if any. 162 failureReason, failureMessage, err := external.FailuresFrom(obj) 163 if err != nil { 164 return external.ReconcileOutput{}, err 165 } 166 if failureReason != "" { 167 machineStatusFailure := capierrors.MachinePoolStatusFailure(failureReason) 168 m.Status.FailureReason = &machineStatusFailure 169 } 170 if failureMessage != "" { 171 m.Status.FailureMessage = pointer.String( 172 fmt.Sprintf("Failure detected from referenced resource %v with name %q: %s", 173 obj.GroupVersionKind(), obj.GetName(), failureMessage), 174 ) 175 } 176 177 return external.ReconcileOutput{Result: obj}, nil 178 } 179 180 // reconcileBootstrap reconciles the Spec.Bootstrap.ConfigRef object on a MachinePool. 181 func (r *MachinePoolReconciler) reconcileBootstrap(ctx context.Context, cluster *clusterv1.Cluster, m *expv1.MachinePool) (ctrl.Result, error) { 182 log := ctrl.LoggerFrom(ctx) 183 184 // Call generic external reconciler if we have an external reference. 185 var bootstrapConfig *unstructured.Unstructured 186 if m.Spec.Template.Spec.Bootstrap.ConfigRef != nil { 187 bootstrapReconcileResult, err := r.reconcileExternal(ctx, cluster, m, m.Spec.Template.Spec.Bootstrap.ConfigRef) 188 if err != nil { 189 return ctrl.Result{}, err 190 } 191 // if the external object is paused, return without any further processing 192 if bootstrapReconcileResult.Paused { 193 return ctrl.Result{}, nil 194 } 195 bootstrapConfig = bootstrapReconcileResult.Result 196 197 // If the bootstrap config is being deleted, return early. 198 if !bootstrapConfig.GetDeletionTimestamp().IsZero() { 199 return ctrl.Result{}, nil 200 } 201 202 // Determine if the bootstrap provider is ready. 203 ready, err := external.IsReady(bootstrapConfig) 204 if err != nil { 205 return ctrl.Result{}, err 206 } 207 208 // Report a summary of current status of the bootstrap object defined for this machine pool. 209 conditions.SetMirror(m, clusterv1.BootstrapReadyCondition, 210 conditions.UnstructuredGetter(bootstrapConfig), 211 conditions.WithFallbackValue(ready, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, ""), 212 ) 213 214 if !ready { 215 log.Info("Waiting for bootstrap provider to generate data secret and report status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig)) 216 m.Status.BootstrapReady = ready 217 return ctrl.Result{}, nil 218 } 219 220 // Get and set the name of the secret containing the bootstrap data. 221 secretName, _, err := unstructured.NestedString(bootstrapConfig.Object, "status", "dataSecretName") 222 if err != nil { 223 return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve dataSecretName from bootstrap provider for MachinePool %q in namespace %q", m.Name, m.Namespace) 224 } else if secretName == "" { 225 return ctrl.Result{}, errors.Errorf("retrieved empty dataSecretName from bootstrap provider for MachinePool %q in namespace %q", m.Name, m.Namespace) 226 } 227 228 m.Spec.Template.Spec.Bootstrap.DataSecretName = pointer.String(secretName) 229 m.Status.BootstrapReady = true 230 return ctrl.Result{}, nil 231 } 232 233 // If dataSecretName is set without a ConfigRef, this means the user brought their own bootstrap data. 234 if m.Spec.Template.Spec.Bootstrap.DataSecretName != nil { 235 m.Status.BootstrapReady = true 236 conditions.MarkTrue(m, clusterv1.BootstrapReadyCondition) 237 return ctrl.Result{}, nil 238 } 239 240 // This should never happen because the MachinePool webhook would not allow neither ConfigRef nor DataSecretName to be set. 241 return ctrl.Result{}, errors.Errorf("neither .spec.bootstrap.configRef nor .spec.bootstrap.dataSecretName are set for MachinePool %q in namespace %q", m.Name, m.Namespace) 242 } 243 244 // reconcileInfrastructure reconciles the Spec.InfrastructureRef object on a MachinePool. 245 func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, cluster *clusterv1.Cluster, mp *expv1.MachinePool) (ctrl.Result, error) { 246 log := ctrl.LoggerFrom(ctx) 247 248 // Call generic external reconciler. 249 infraReconcileResult, err := r.reconcileExternal(ctx, cluster, mp, &mp.Spec.Template.Spec.InfrastructureRef) 250 if err != nil { 251 if apierrors.IsNotFound(errors.Cause(err)) { 252 log.Error(err, "infrastructure reference could not be found") 253 if mp.Status.InfrastructureReady { 254 // Infra object went missing after the machine pool was up and running 255 log.Error(err, "infrastructure reference has been deleted after being ready, setting failure state") 256 mp.Status.FailureReason = capierrors.MachinePoolStatusErrorPtr(capierrors.InvalidConfigurationMachinePoolError) 257 mp.Status.FailureMessage = pointer.String(fmt.Sprintf("MachinePool infrastructure resource %v with name %q has been deleted after being ready", 258 mp.Spec.Template.Spec.InfrastructureRef.GroupVersionKind(), mp.Spec.Template.Spec.InfrastructureRef.Name)) 259 } 260 conditions.MarkFalse(mp, clusterv1.InfrastructureReadyCondition, clusterv1.IncorrectExternalRefReason, clusterv1.ConditionSeverityError, fmt.Sprintf("could not find infra reference of kind %s with name %s", mp.Spec.Template.Spec.InfrastructureRef.Kind, mp.Spec.Template.Spec.InfrastructureRef.Name)) 261 } 262 return ctrl.Result{}, err 263 } 264 // if the external object is paused, return without any further processing 265 if infraReconcileResult.Paused { 266 return ctrl.Result{}, nil 267 } 268 infraConfig := infraReconcileResult.Result 269 270 if !infraConfig.GetDeletionTimestamp().IsZero() { 271 return ctrl.Result{}, nil 272 } 273 274 ready, err := external.IsReady(infraConfig) 275 if err != nil { 276 return ctrl.Result{}, err 277 } 278 279 mp.Status.InfrastructureReady = ready 280 281 // Report a summary of current status of the infrastructure object defined for this machine pool. 282 conditions.SetMirror(mp, clusterv1.InfrastructureReadyCondition, 283 conditions.UnstructuredGetter(infraConfig), 284 conditions.WithFallbackValue(ready, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), 285 ) 286 287 if err := r.reconcileMachines(ctx, mp, infraConfig); err != nil { 288 return ctrl.Result{}, errors.Wrapf(err, "failed to reconcile Machines for MachinePool %s", klog.KObj(mp)) 289 } 290 291 if !mp.Status.InfrastructureReady { 292 log.Info("Infrastructure provider is not yet ready", infraConfig.GetKind(), klog.KObj(infraConfig)) 293 return ctrl.Result{}, nil 294 } 295 296 var providerIDList []string 297 // Get Spec.ProviderIDList from the infrastructure provider. 298 if err := util.UnstructuredUnmarshalField(infraConfig, &providerIDList, "spec", "providerIDList"); err != nil && !errors.Is(err, util.ErrUnstructuredFieldNotFound) { 299 return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve data from infrastructure provider for MachinePool %q in namespace %q", mp.Name, mp.Namespace) 300 } 301 302 // Get and set Status.Replicas from the infrastructure provider. 303 err = util.UnstructuredUnmarshalField(infraConfig, &mp.Status.Replicas, "status", "replicas") 304 if err != nil { 305 if err != util.ErrUnstructuredFieldNotFound { 306 return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve replicas from infrastructure provider for MachinePool %q in namespace %q", mp.Name, mp.Namespace) 307 } 308 } 309 310 if len(providerIDList) == 0 && mp.Status.Replicas != 0 { 311 log.Info("Retrieved empty spec.providerIDList from infrastructure provider but status.replicas is not zero.", "replicas", mp.Status.Replicas) 312 return ctrl.Result{}, nil 313 } 314 315 if !reflect.DeepEqual(mp.Spec.ProviderIDList, providerIDList) { 316 mp.Spec.ProviderIDList = providerIDList 317 mp.Status.ReadyReplicas = 0 318 mp.Status.AvailableReplicas = 0 319 mp.Status.UnavailableReplicas = mp.Status.Replicas 320 } 321 322 return ctrl.Result{}, nil 323 } 324 325 // reconcileMachines reconciles Machines associated with a MachinePool. 326 // 327 // Note: In the case of MachinePools the machines are created in order to surface in CAPI what exists in the 328 // infrastructure while instead on MachineDeployments, machines are created in CAPI first and then the 329 // infrastructure is created accordingly. 330 // Note: When supported by the cloud provider implementation of the MachinePool, machines will provide a means to interact 331 // with the corresponding infrastructure (e.g. delete a specific machine in case MachineHealthCheck detects it is unhealthy). 332 func (r *MachinePoolReconciler) reconcileMachines(ctx context.Context, mp *expv1.MachinePool, infraMachinePool *unstructured.Unstructured) error { 333 log := ctrl.LoggerFrom(ctx) 334 335 var infraMachineKind string 336 if err := util.UnstructuredUnmarshalField(infraMachinePool, &infraMachineKind, "status", "infrastructureMachineKind"); err != nil { 337 if errors.Is(err, util.ErrUnstructuredFieldNotFound) { 338 log.V(4).Info("MachinePool Machines not supported, no infraMachineKind found") 339 return nil 340 } 341 342 return errors.Wrapf(err, "failed to retrieve infraMachineKind from infrastructure provider for MachinePool %s", klog.KObj(mp)) 343 } 344 345 infraMachineSelector := metav1.LabelSelector{ 346 MatchLabels: map[string]string{ 347 clusterv1.MachinePoolNameLabel: format.MustFormatValue(mp.Name), 348 clusterv1.ClusterNameLabel: mp.Spec.ClusterName, 349 }, 350 } 351 352 log.V(4).Info("Reconciling MachinePool Machines", "infrastructureMachineKind", infraMachineKind, "infrastructureMachineSelector", infraMachineSelector) 353 var infraMachineList unstructured.UnstructuredList 354 355 // Get the list of infraMachines, which are maintained by the InfraMachinePool controller. 356 infraMachineList.SetAPIVersion(infraMachinePool.GetAPIVersion()) 357 infraMachineList.SetKind(infraMachineKind + "List") 358 if err := r.Client.List(ctx, &infraMachineList, client.InNamespace(mp.Namespace), client.MatchingLabels(infraMachineSelector.MatchLabels)); err != nil { 359 return errors.Wrapf(err, "failed to list infra machines for MachinePool %q in namespace %q", mp.Name, mp.Namespace) 360 } 361 362 // Add watcher for infraMachine, if there isn't one already; this will allow this controller to reconcile 363 // immediately changes made by the InfraMachinePool controller. 364 sampleInfraMachine := &unstructured.Unstructured{} 365 sampleInfraMachine.SetAPIVersion(infraMachinePool.GetAPIVersion()) 366 sampleInfraMachine.SetKind(infraMachineKind) 367 368 // Add watcher for infraMachine, if there isn't one already. 369 if err := r.externalTracker.Watch(log, sampleInfraMachine, handler.EnqueueRequestsFromMapFunc(r.infraMachineToMachinePoolMapper)); err != nil { 370 return err 371 } 372 373 // Get the list of machines managed by this controller, and align it with the infra machines managed by 374 // the InfraMachinePool controller. 375 machineList := &clusterv1.MachineList{} 376 if err := r.Client.List(ctx, machineList, client.InNamespace(mp.Namespace), client.MatchingLabels(infraMachineSelector.MatchLabels)); err != nil { 377 return err 378 } 379 380 if err := r.createOrUpdateMachines(ctx, mp, machineList.Items, infraMachineList.Items); err != nil { 381 return errors.Wrapf(err, "failed to create machines for MachinePool %q in namespace %q", mp.Name, mp.Namespace) 382 } 383 384 return nil 385 } 386 387 // createOrUpdateMachines creates a MachinePool Machine for each infraMachine if it doesn't already exist and sets the owner reference and infraRef. 388 func (r *MachinePoolReconciler) createOrUpdateMachines(ctx context.Context, mp *expv1.MachinePool, machines []clusterv1.Machine, infraMachines []unstructured.Unstructured) error { 389 log := ctrl.LoggerFrom(ctx) 390 391 // Construct a set of names of infraMachines that already have a Machine. 392 infraMachineToMachine := map[string]clusterv1.Machine{} 393 for _, machine := range machines { 394 infraRef := machine.Spec.InfrastructureRef 395 infraMachineToMachine[infraRef.Name] = machine 396 } 397 398 createdMachines := []clusterv1.Machine{} 399 var errs []error 400 for i := range infraMachines { 401 infraMachine := &infraMachines[i] 402 // If infraMachine already has a Machine, update it if needed. 403 if existingMachine, ok := infraMachineToMachine[infraMachine.GetName()]; ok { 404 log.V(2).Info("Patching existing Machine for infraMachine", "infraMachine", klog.KObj(infraMachine), "machine", klog.KObj(&existingMachine)) 405 406 desiredMachine := computeDesiredMachine(mp, infraMachine, &existingMachine) 407 if err := ssa.Patch(ctx, r.Client, MachinePoolControllerName, desiredMachine, ssa.WithCachingProxy{Cache: r.ssaCache, Original: &existingMachine}); err != nil { 408 log.Error(err, "failed to update Machine", "Machine", klog.KObj(desiredMachine)) 409 errs = append(errs, errors.Wrapf(err, "failed to update Machine %q", klog.KObj(desiredMachine))) 410 } 411 } else { 412 // Otherwise create a new Machine for the infraMachine. 413 log.Info("Creating new Machine for infraMachine", "infraMachine", klog.KObj(infraMachine)) 414 machine := computeDesiredMachine(mp, infraMachine, nil) 415 416 if err := ssa.Patch(ctx, r.Client, MachinePoolControllerName, machine); err != nil { 417 errs = append(errs, errors.Wrapf(err, "failed to create new Machine for infraMachine %q in namespace %q", infraMachine.GetName(), infraMachine.GetNamespace())) 418 continue 419 } 420 421 createdMachines = append(createdMachines, *machine) 422 } 423 } 424 if err := r.waitForMachineCreation(ctx, createdMachines); err != nil { 425 errs = append(errs, errors.Wrapf(err, "failed to wait for machines to be created")) 426 } 427 if len(errs) > 0 { 428 return kerrors.NewAggregate(errs) 429 } 430 431 return nil 432 } 433 434 // computeDesiredMachine constructs the desired Machine for an infraMachine. 435 // If the Machine exists, it ensures the Machine always owned by the MachinePool. 436 func computeDesiredMachine(mp *expv1.MachinePool, infraMachine *unstructured.Unstructured, existingMachine *clusterv1.Machine) *clusterv1.Machine { 437 infraRef := corev1.ObjectReference{ 438 APIVersion: infraMachine.GetAPIVersion(), 439 Kind: infraMachine.GetKind(), 440 Name: infraMachine.GetName(), 441 Namespace: infraMachine.GetNamespace(), 442 } 443 444 machine := &clusterv1.Machine{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Name: names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-", mp.Name)), 447 // Note: by setting the ownerRef on creation we signal to the Machine controller that this is not a stand-alone Machine. 448 OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(mp, machinePoolKind)}, 449 Namespace: mp.Namespace, 450 Labels: make(map[string]string), 451 Annotations: make(map[string]string), 452 }, 453 Spec: clusterv1.MachineSpec{ 454 ClusterName: mp.Spec.ClusterName, 455 InfrastructureRef: infraRef, 456 }, 457 } 458 459 if existingMachine != nil { 460 machine.SetName(existingMachine.Name) 461 machine.SetUID(existingMachine.UID) 462 } 463 464 for k, v := range mp.Spec.Template.Annotations { 465 machine.Annotations[k] = v 466 } 467 468 // Set the labels from machinePool.Spec.Template.Labels as labels for the new Machine. 469 // Note: We can't just set `machinePool.Spec.Template.Labels` directly and thus "share" the labels 470 // map between Machine and machinePool.Spec.Template.Labels. This would mean that adding the 471 // MachinePoolNameLabel later on the Machine would also add the labels to machinePool.Spec.Template.Labels 472 // and thus modify the labels of the MachinePool. 473 for k, v := range mp.Spec.Template.Labels { 474 machine.Labels[k] = v 475 } 476 477 // Enforce that the MachinePoolNameLabel and ClusterNameLabel are present on the Machine. 478 machine.Labels[clusterv1.MachinePoolNameLabel] = format.MustFormatValue(mp.Name) 479 machine.Labels[clusterv1.ClusterNameLabel] = mp.Spec.ClusterName 480 481 return machine 482 } 483 484 // infraMachineToMachinePoolMapper is a mapper function that maps an InfraMachine to the MachinePool that owns it. 485 // This is used to trigger an update of the MachinePool when a InfraMachine is changed. 486 func (r *MachinePoolReconciler) infraMachineToMachinePoolMapper(ctx context.Context, o client.Object) []ctrl.Request { 487 log := ctrl.LoggerFrom(ctx) 488 489 if labels.IsMachinePoolOwned(o) { 490 machinePool, err := utilexp.GetMachinePoolByLabels(ctx, r.Client, o.GetNamespace(), o.GetLabels()) 491 if err != nil { 492 log.Error(err, "failed to get MachinePool for InfraMachine", "infraMachine", klog.KObj(o), "labels", o.GetLabels()) 493 return nil 494 } 495 if machinePool != nil { 496 return []ctrl.Request{ 497 { 498 NamespacedName: client.ObjectKey{ 499 Namespace: machinePool.Namespace, 500 Name: machinePool.Name, 501 }, 502 }, 503 } 504 } 505 } 506 507 return nil 508 } 509 510 func (r *MachinePoolReconciler) waitForMachineCreation(ctx context.Context, machineList []clusterv1.Machine) error { 511 _ = ctrl.LoggerFrom(ctx) 512 513 // waitForCacheUpdateTimeout is the amount of time allowed to wait for desired state. 514 const waitForCacheUpdateTimeout = 10 * time.Second 515 516 // waitForCacheUpdateInterval is the amount of time between polling for the desired state. 517 // The polling is against a local memory cache. 518 const waitForCacheUpdateInterval = 100 * time.Millisecond 519 520 for i := 0; i < len(machineList); i++ { 521 machine := machineList[i] 522 pollErr := wait.PollUntilContextTimeout(ctx, waitForCacheUpdateInterval, waitForCacheUpdateTimeout, true, func(ctx context.Context) (bool, error) { 523 key := client.ObjectKey{Namespace: machine.Namespace, Name: machine.Name} 524 if err := r.Client.Get(ctx, key, &clusterv1.Machine{}); err != nil { 525 if apierrors.IsNotFound(err) { 526 return false, nil 527 } 528 return false, err 529 } 530 531 return true, nil 532 }) 533 534 if pollErr != nil { 535 return errors.Wrapf(pollErr, "failed waiting for machine object %v to be created", klog.KObj(&machine)) 536 } 537 } 538 539 return nil 540 }