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  }