func()

in pkg/controllers/clusterresourceplacement/controller.go [161:263]


func (r *Reconciler) handleUpdate(ctx context.Context, crp *fleetv1beta1.ClusterResourcePlacement) (ctrl.Result, error) {
	revisionLimit := int32(defaulter.DefaultRevisionHistoryLimitValue)
	crpKObj := klog.KObj(crp)
	oldCRP := crp.DeepCopy()
	if crp.Spec.RevisionHistoryLimit != nil {
		if revisionLimit <= 0 {
			err := fmt.Errorf("invalid clusterResourcePlacement %s: invalid revisionHistoryLimit %d", crp.Name, revisionLimit)
			klog.ErrorS(controller.NewUnexpectedBehaviorError(err), "Invalid revisionHistoryLimit value and using default value instead", "clusterResourcePlacement", crpKObj)
		} else {
			revisionLimit = *crp.Spec.RevisionHistoryLimit
		}
	}

	// validate the resource selectors first before creating any snapshot
	envelopeObjCount, selectedResources, selectedResourceIDs, err := r.selectResourcesForPlacement(crp)
	if err != nil {
		klog.ErrorS(err, "Failed to select the resources", "clusterResourcePlacement", crpKObj)
		if !errors.Is(err, controller.ErrUserError) {
			return ctrl.Result{}, err
		}

		// TODO, create a separate user type error struct to improve the user facing messages
		scheduleCondition := metav1.Condition{
			Status:             metav1.ConditionFalse,
			Type:               string(fleetv1beta1.ClusterResourcePlacementScheduledConditionType),
			Reason:             InvalidResourceSelectorsReason,
			Message:            fmt.Sprintf("The resource selectors are invalid: %v", err),
			ObservedGeneration: crp.Generation,
		}
		crp.SetConditions(scheduleCondition)
		if updateErr := r.Client.Status().Update(ctx, crp); updateErr != nil {
			klog.ErrorS(updateErr, "Failed to update the status", "clusterResourcePlacement", crpKObj)
			return ctrl.Result{}, controller.NewUpdateIgnoreConflictError(updateErr)
		}
		// no need to retry faster, the user needs to fix the resource selectors
		return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
	}

	latestSchedulingPolicySnapshot, err := r.getOrCreateClusterSchedulingPolicySnapshot(ctx, crp, int(revisionLimit))
	if err != nil {
		klog.ErrorS(err, "Failed to select resources for placement", "clusterResourcePlacement", crpKObj)
		return ctrl.Result{}, err
	}

	latestResourceSnapshot, err := r.getOrCreateClusterResourceSnapshot(ctx, crp, envelopeObjCount,
		&fleetv1beta1.ResourceSnapshotSpec{SelectedResources: selectedResources}, int(revisionLimit))
	if err != nil {
		return ctrl.Result{}, err
	}

	// isClusterScheduled is to indicate whether we need to requeue the CRP request to track the rollout status.
	isClusterScheduled, err := r.setPlacementStatus(ctx, crp, selectedResourceIDs, latestSchedulingPolicySnapshot, latestResourceSnapshot)
	if err != nil {
		return ctrl.Result{}, err
	}

	if err := r.Client.Status().Update(ctx, crp); err != nil {
		klog.ErrorS(err, "Failed to update the status", "clusterResourcePlacement", crpKObj)
		return ctrl.Result{}, err
	}
	klog.V(2).InfoS("Updated the clusterResourcePlacement status", "clusterResourcePlacement", crpKObj)

	// We skip checking the last resource condition (available) because it will be covered by checking isRolloutCompleted func.
	for i := condition.RolloutStartedCondition; i < condition.TotalCondition-1; i++ {
		oldCond := oldCRP.GetCondition(string(i.ClusterResourcePlacementConditionType()))
		newCond := crp.GetCondition(string(i.ClusterResourcePlacementConditionType()))
		if !condition.IsConditionStatusTrue(oldCond, oldCRP.Generation) &&
			condition.IsConditionStatusTrue(newCond, crp.Generation) {
			klog.V(2).InfoS("Placement resource condition status has been changed to true", "clusterResourcePlacement", crpKObj, "generation", crp.Generation, "condition", i.ClusterResourcePlacementConditionType())
			r.Recorder.Event(crp, corev1.EventTypeNormal, i.EventReasonForTrue(), i.EventMessageForTrue())
		}
	}

	// Rollout is considered to be completed when all the expected condition types are set to the
	// True status.
	if isRolloutCompleted(crp) {
		if !isRolloutCompleted(oldCRP) {
			klog.V(2).InfoS("Placement has finished the rollout process and reached the desired status", "clusterResourcePlacement", crpKObj, "generation", crp.Generation)
			r.Recorder.Event(crp, corev1.EventTypeNormal, "PlacementRolloutCompleted", "Placement has finished the rollout process and reached the desired status")
		}
		// We don't need to requeue any request now by watching the binding changes
		return ctrl.Result{}, nil
	}

	if !isClusterScheduled {
		// Note:
		// If the scheduledCondition is failed, it means the placement requirement cannot be satisfied fully. For example,
		// pickN deployment requires 5 clusters and scheduler schedules the resources on 3 clusters. And the appliedCondition
		// could be true when resources are applied successfully on these 3 clusters and the detailed the resourcePlacementStatuses
		// need to be populated.
		// So that we cannot rely on the scheduledCondition as false to decide whether to requeue the request.

		// When isClusterScheduled is false, either scheduler has not finished the scheduling or none of the clusters could be selected.
		// Once the policy snapshot status changes, the policy snapshot watcher should enqueue the request.
		// Here we requeue the request to prevent a bug in the watcher.
		klog.V(2).InfoS("Scheduler has not scheduled any cluster yet and requeue the request as a backup",
			"clusterResourcePlacement", crpKObj, "scheduledCondition", crp.GetCondition(string(fleetv1beta1.ClusterResourcePlacementScheduledConditionType)), "generation", crp.Generation)
		return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
	}
	klog.V(2).InfoS("Placement rollout has not finished yet and requeue the request", "clusterResourcePlacement", crpKObj, "status", crp.Status, "generation", crp.Generation)
	// no need to requeue the request as the binding status will be changed but we add a long resync loop just in case.
	return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
}