sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/cluster/cluster_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 cluster 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 "github.com/pkg/errors" 25 corev1 "k8s.io/api/core/v1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 "k8s.io/utils/ptr" 28 ctrl "sigs.k8s.io/controller-runtime" 29 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 30 "sigs.k8s.io/controller-runtime/pkg/handler" 31 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 "sigs.k8s.io/cluster-api/controllers/external" 34 capierrors "sigs.k8s.io/cluster-api/errors" 35 "sigs.k8s.io/cluster-api/util" 36 "sigs.k8s.io/cluster-api/util/annotations" 37 "sigs.k8s.io/cluster-api/util/conditions" 38 utilconversion "sigs.k8s.io/cluster-api/util/conversion" 39 "sigs.k8s.io/cluster-api/util/kubeconfig" 40 "sigs.k8s.io/cluster-api/util/patch" 41 "sigs.k8s.io/cluster-api/util/secret" 42 ) 43 44 func (r *Reconciler) reconcilePhase(_ context.Context, cluster *clusterv1.Cluster) { 45 preReconcilePhase := cluster.Status.GetTypedPhase() 46 47 if cluster.Status.Phase == "" { 48 cluster.Status.SetTypedPhase(clusterv1.ClusterPhasePending) 49 } 50 51 if cluster.Spec.InfrastructureRef != nil { 52 cluster.Status.SetTypedPhase(clusterv1.ClusterPhaseProvisioning) 53 } 54 55 if cluster.Status.InfrastructureReady && cluster.Spec.ControlPlaneEndpoint.IsValid() { 56 cluster.Status.SetTypedPhase(clusterv1.ClusterPhaseProvisioned) 57 } 58 59 if cluster.Status.FailureReason != nil || cluster.Status.FailureMessage != nil { 60 cluster.Status.SetTypedPhase(clusterv1.ClusterPhaseFailed) 61 } 62 63 if !cluster.DeletionTimestamp.IsZero() { 64 cluster.Status.SetTypedPhase(clusterv1.ClusterPhaseDeleting) 65 } 66 67 // Only record the event if the status has changed 68 if preReconcilePhase != cluster.Status.GetTypedPhase() { 69 // Failed clusters should get a Warning event 70 if cluster.Status.GetTypedPhase() == clusterv1.ClusterPhaseFailed { 71 r.recorder.Eventf(cluster, corev1.EventTypeWarning, string(cluster.Status.GetTypedPhase()), "Cluster %s is %s: %s", cluster.Name, string(cluster.Status.GetTypedPhase()), ptr.Deref(cluster.Status.FailureMessage, "unknown")) 72 } else { 73 r.recorder.Eventf(cluster, corev1.EventTypeNormal, string(cluster.Status.GetTypedPhase()), "Cluster %s is %s", cluster.Name, string(cluster.Status.GetTypedPhase())) 74 } 75 } 76 } 77 78 // reconcileExternal handles generic unstructured objects referenced by a Cluster. 79 func (r *Reconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) (external.ReconcileOutput, error) { 80 log := ctrl.LoggerFrom(ctx) 81 82 if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil { 83 return external.ReconcileOutput{}, err 84 } 85 86 obj, err := external.Get(ctx, r.UnstructuredCachingClient, ref, cluster.Namespace) 87 if err != nil { 88 if apierrors.IsNotFound(errors.Cause(err)) { 89 log.Info("Could not find external object for cluster, requeuing", "refGroupVersionKind", ref.GroupVersionKind(), "refName", ref.Name) 90 return external.ReconcileOutput{RequeueAfter: 30 * time.Second}, nil 91 } 92 return external.ReconcileOutput{}, err 93 } 94 95 // Ensure we add a watcher to the external object. 96 if err := r.externalTracker.Watch(log, obj, handler.EnqueueRequestForOwner(r.Client.Scheme(), r.Client.RESTMapper(), &clusterv1.Cluster{})); err != nil { 97 return external.ReconcileOutput{}, err 98 } 99 100 // if external ref is paused, return error. 101 if annotations.IsPaused(cluster, obj) { 102 log.V(3).Info("External object referenced is paused") 103 return external.ReconcileOutput{Paused: true}, nil 104 } 105 106 // Initialize the patch helper. 107 patchHelper, err := patch.NewHelper(obj, r.Client) 108 if err != nil { 109 return external.ReconcileOutput{}, err 110 } 111 112 // Set external object ControllerReference to the Cluster. 113 if err := controllerutil.SetControllerReference(cluster, obj, r.Client.Scheme()); err != nil { 114 return external.ReconcileOutput{}, err 115 } 116 117 // Set the Cluster label. 118 labels := obj.GetLabels() 119 if labels == nil { 120 labels = make(map[string]string) 121 } 122 labels[clusterv1.ClusterNameLabel] = cluster.Name 123 obj.SetLabels(labels) 124 125 // Always attempt to Patch the external object. 126 if err := patchHelper.Patch(ctx, obj); err != nil { 127 return external.ReconcileOutput{}, err 128 } 129 130 // Set failure reason and message, if any. 131 failureReason, failureMessage, err := external.FailuresFrom(obj) 132 if err != nil { 133 return external.ReconcileOutput{}, err 134 } 135 if failureReason != "" { 136 clusterStatusError := capierrors.ClusterStatusError(failureReason) 137 cluster.Status.FailureReason = &clusterStatusError 138 } 139 if failureMessage != "" { 140 cluster.Status.FailureMessage = ptr.To( 141 fmt.Sprintf("Failure detected from referenced resource %v with name %q: %s", 142 obj.GroupVersionKind(), obj.GetName(), failureMessage), 143 ) 144 } 145 146 return external.ReconcileOutput{Result: obj}, nil 147 } 148 149 // reconcileInfrastructure reconciles the Spec.InfrastructureRef object on a Cluster. 150 func (r *Reconciler) reconcileInfrastructure(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { 151 log := ctrl.LoggerFrom(ctx) 152 153 if cluster.Spec.InfrastructureRef == nil { 154 return ctrl.Result{}, nil 155 } 156 157 // Call generic external reconciler. 158 infraReconcileResult, err := r.reconcileExternal(ctx, cluster, cluster.Spec.InfrastructureRef) 159 if err != nil { 160 return ctrl.Result{}, err 161 } 162 // Return early if we need to requeue. 163 if infraReconcileResult.RequeueAfter > 0 { 164 return ctrl.Result{RequeueAfter: infraReconcileResult.RequeueAfter}, nil 165 } 166 // If the external object is paused, return without any further processing. 167 if infraReconcileResult.Paused { 168 return ctrl.Result{}, nil 169 } 170 infraConfig := infraReconcileResult.Result 171 172 // There's no need to go any further if the Cluster is marked for deletion. 173 if !infraConfig.GetDeletionTimestamp().IsZero() { 174 return ctrl.Result{}, nil 175 } 176 177 // Determine if the infrastructure provider is ready. 178 preReconcileInfrastructureReady := cluster.Status.InfrastructureReady 179 ready, err := external.IsReady(infraConfig) 180 if err != nil { 181 return ctrl.Result{}, err 182 } 183 cluster.Status.InfrastructureReady = ready 184 // Only record the event if the status has changed 185 if preReconcileInfrastructureReady != cluster.Status.InfrastructureReady { 186 r.recorder.Eventf(cluster, corev1.EventTypeNormal, "InfrastructureReady", "Cluster %s InfrastructureReady is now %t", cluster.Name, cluster.Status.InfrastructureReady) 187 } 188 189 // Report a summary of current status of the infrastructure object defined for this cluster. 190 conditions.SetMirror(cluster, clusterv1.InfrastructureReadyCondition, 191 conditions.UnstructuredGetter(infraConfig), 192 conditions.WithFallbackValue(ready, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, ""), 193 ) 194 195 if !ready { 196 log.V(3).Info("Infrastructure provider is not ready yet") 197 return ctrl.Result{}, nil 198 } 199 200 // Get and parse Spec.ControlPlaneEndpoint field from the infrastructure provider. 201 if !cluster.Spec.ControlPlaneEndpoint.IsValid() { 202 if err := util.UnstructuredUnmarshalField(infraConfig, &cluster.Spec.ControlPlaneEndpoint, "spec", "controlPlaneEndpoint"); err != nil { 203 return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ControlPlaneEndpoint from infrastructure provider for Cluster %q in namespace %q", 204 cluster.Name, cluster.Namespace) 205 } 206 } 207 208 // Get and parse Status.FailureDomains from the infrastructure provider. 209 failureDomains := clusterv1.FailureDomains{} 210 if err := util.UnstructuredUnmarshalField(infraConfig, &failureDomains, "status", "failureDomains"); err != nil && err != util.ErrUnstructuredFieldNotFound { 211 return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Status.FailureDomains from infrastructure provider for Cluster %q in namespace %q", 212 cluster.Name, cluster.Namespace) 213 } 214 cluster.Status.FailureDomains = failureDomains 215 216 return ctrl.Result{}, nil 217 } 218 219 // reconcileControlPlane reconciles the Spec.ControlPlaneRef object on a Cluster. 220 func (r *Reconciler) reconcileControlPlane(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { 221 if cluster.Spec.ControlPlaneRef == nil { 222 return ctrl.Result{}, nil 223 } 224 225 // Call generic external reconciler. 226 controlPlaneReconcileResult, err := r.reconcileExternal(ctx, cluster, cluster.Spec.ControlPlaneRef) 227 if err != nil { 228 return ctrl.Result{}, err 229 } 230 // Return early if we need to requeue. 231 if controlPlaneReconcileResult.RequeueAfter > 0 { 232 return ctrl.Result{RequeueAfter: controlPlaneReconcileResult.RequeueAfter}, nil 233 } 234 // If the external object is paused, return without any further processing. 235 if controlPlaneReconcileResult.Paused { 236 return ctrl.Result{}, nil 237 } 238 controlPlaneConfig := controlPlaneReconcileResult.Result 239 240 // There's no need to go any further if the control plane resource is marked for deletion. 241 if !controlPlaneConfig.GetDeletionTimestamp().IsZero() { 242 return ctrl.Result{}, nil 243 } 244 245 preReconcileControlPlaneReady := cluster.Status.ControlPlaneReady 246 // Determine if the control plane provider is ready. 247 ready, err := external.IsReady(controlPlaneConfig) 248 if err != nil { 249 return ctrl.Result{}, err 250 } 251 cluster.Status.ControlPlaneReady = ready 252 // Only record the event if the status has changed 253 if preReconcileControlPlaneReady != cluster.Status.ControlPlaneReady { 254 r.recorder.Eventf(cluster, corev1.EventTypeNormal, "ControlPlaneReady", "Cluster %s ControlPlaneReady is now %t", cluster.Name, cluster.Status.ControlPlaneReady) 255 } 256 257 // Report a summary of current status of the control plane object defined for this cluster. 258 conditions.SetMirror(cluster, clusterv1.ControlPlaneReadyCondition, 259 conditions.UnstructuredGetter(controlPlaneConfig), 260 conditions.WithFallbackValue(ready, clusterv1.WaitingForControlPlaneFallbackReason, clusterv1.ConditionSeverityInfo, ""), 261 ) 262 263 // Update cluster.Status.ControlPlaneInitialized if it hasn't already been set 264 // Determine if the control plane provider is initialized. 265 if !conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) { 266 initialized, err := external.IsInitialized(controlPlaneConfig) 267 if err != nil { 268 return ctrl.Result{}, err 269 } 270 if initialized { 271 conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition) 272 } else { 273 conditions.MarkFalse(cluster, clusterv1.ControlPlaneInitializedCondition, clusterv1.WaitingForControlPlaneProviderInitializedReason, clusterv1.ConditionSeverityInfo, "Waiting for control plane provider to indicate the control plane has been initialized") 274 } 275 } 276 277 return ctrl.Result{}, nil 278 } 279 280 func (r *Reconciler) reconcileKubeconfig(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) { 281 log := ctrl.LoggerFrom(ctx) 282 283 if !cluster.Spec.ControlPlaneEndpoint.IsValid() { 284 return ctrl.Result{}, nil 285 } 286 287 // Do not generate the Kubeconfig if there is a ControlPlaneRef, since the Control Plane provider is 288 // responsible for the management of the Kubeconfig. We continue to manage it here only for backward 289 // compatibility when a Control Plane provider is not in use. 290 if cluster.Spec.ControlPlaneRef != nil { 291 return ctrl.Result{}, nil 292 } 293 294 _, err := secret.Get(ctx, r.Client, util.ObjectKey(cluster), secret.Kubeconfig) 295 switch { 296 case apierrors.IsNotFound(err): 297 if err := kubeconfig.CreateSecret(ctx, r.Client, cluster); err != nil { 298 if err == kubeconfig.ErrDependentCertificateNotFound { 299 log.Info("Could not find secret for cluster, requeuing", "Secret", secret.ClusterCA) 300 return ctrl.Result{RequeueAfter: 30 * time.Second}, nil 301 } 302 return ctrl.Result{}, err 303 } 304 case err != nil: 305 return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Kubeconfig Secret for Cluster %q in namespace %q", cluster.Name, cluster.Namespace) 306 } 307 308 return ctrl.Result{}, nil 309 }