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
}