in pkg/controllers/rollout/controller.go [364:523]
func (r *Reconciler) pickBindingsToRoll(
ctx context.Context,
allBindings []*fleetv1beta1.ClusterResourceBinding,
latestResourceSnapshot *fleetv1beta1.ClusterResourceSnapshot,
crp *fleetv1beta1.ClusterResourcePlacement,
matchedCROs []*fleetv1alpha1.ClusterResourceOverrideSnapshot,
matchedROs []*fleetv1alpha1.ResourceOverrideSnapshot,
) ([]toBeUpdatedBinding, []toBeUpdatedBinding, []toBeUpdatedBinding, bool, time.Duration, error) {
// Those are the bindings that are chosen by the scheduler to be applied to selected clusters.
// They include the bindings that are already applied to the clusters and the bindings that are newly selected by the scheduler.
schedulerTargetedBinds := make([]*fleetv1beta1.ClusterResourceBinding, 0)
// The content of those bindings that are considered to be already running on the targeted clusters.
readyBindings := make([]*fleetv1beta1.ClusterResourceBinding, 0)
// Those are the bindings that have the potential to be ready during the rolling phase.
// It includes all bindings that have been applied to the clusters and not deleted yet so that they can still be ready at any time.
canBeReadyBindings := make([]*fleetv1beta1.ClusterResourceBinding, 0)
// Those are the bindings that have the potential to be unavailable during the rolling phase which
// includes the bindings that are being deleted. It depends on work generator and member agent for the timing of the removal from the cluster.
canBeUnavailableBindings := make([]*fleetv1beta1.ClusterResourceBinding, 0)
// Those are the bindings that are candidates to be updated to be bound during the rolling phase.
boundingCandidates := make([]toBeUpdatedBinding, 0)
// Those are the bindings that are candidates to be removed during the rolling phase.
removeCandidates := make([]toBeUpdatedBinding, 0)
// Those are the bindings that are candidates to be updated to latest resources during the rolling phase.
updateCandidates := make([]toBeUpdatedBinding, 0)
// Those are the bindings that are a sub-set of the candidates to be updated to latest resources but also are failed to apply.
// We can safely update those bindings to latest resources even if we can't update the rest of the bindings when we don't meet the
// minimum AvailableNumber of copies as we won't reduce the total unavailable number of bindings.
applyFailedUpdateCandidates := make([]toBeUpdatedBinding, 0)
// Those are the bindings that have been bound to a cluster and have the latest
// resource/override snapshots, but might or might not have the refresh status information.
upToDateBoundBindings := make([]toBeUpdatedBinding, 0)
// calculate the cutoff time for a binding to be applied before so that it can be considered ready
readyTimeCutOff := time.Now().Add(-time.Duration(*crp.Spec.Strategy.RollingUpdate.UnavailablePeriodSeconds) * time.Second)
// classify the bindings into different categories
// Wait for the first applied but not ready binding to be ready.
// return wait time longer if the rollout is stuck on failed apply/available bindings
minWaitTime := time.Duration(*crp.Spec.Strategy.RollingUpdate.UnavailablePeriodSeconds) * time.Second
allReady := true
crpKObj := klog.KObj(crp)
for idx := range allBindings {
binding := allBindings[idx]
bindingKObj := klog.KObj(binding)
switch binding.Spec.State {
case fleetv1beta1.BindingStateUnscheduled:
if bindingutils.HasBindingFailed(binding) {
klog.V(2).InfoS("Found a failed to be ready unscheduled binding", "clusterResourcePlacement", crpKObj, "binding", bindingKObj)
} else if !bindingutils.IsBindingDiffReported(binding) {
canBeReadyBindings = append(canBeReadyBindings, binding)
}
waitTime, bindingReady := isBindingReady(binding, readyTimeCutOff)
if bindingReady {
klog.V(2).InfoS("Found a ready unscheduled binding", "clusterResourcePlacement", crpKObj, "binding", bindingKObj)
readyBindings = append(readyBindings, binding)
} else {
allReady = false
if waitTime >= 0 && waitTime < minWaitTime {
minWaitTime = waitTime
}
}
if binding.DeletionTimestamp.IsZero() {
// it's not been deleted yet, so it is a removal candidate
klog.V(2).InfoS("Found a not yet deleted unscheduled binding", "clusterResourcePlacement", crpKObj, "binding", bindingKObj)
// The desired binding is nil for the removeCandidates.
removeCandidates = append(removeCandidates, toBeUpdatedBinding{currentBinding: binding})
} else if bindingReady {
// it is being deleted, it can be removed from the cluster at any time, so it can be unavailable at any time
canBeUnavailableBindings = append(canBeUnavailableBindings, binding)
}
case fleetv1beta1.BindingStateScheduled:
// the scheduler has picked a cluster for this binding
schedulerTargetedBinds = append(schedulerTargetedBinds, binding)
// this binding has not been bound yet, so it is an update candidate
// PickFromResourceMatchedOverridesForTargetCluster always returns the ordered list of the overrides.
cro, ro, err := overrider.PickFromResourceMatchedOverridesForTargetCluster(ctx, r.Client, binding.Spec.TargetCluster, matchedCROs, matchedROs)
if err != nil {
return nil, nil, nil, false, minWaitTime, err
}
boundingCandidates = append(boundingCandidates, createUpdateInfo(binding, latestResourceSnapshot, cro, ro))
case fleetv1beta1.BindingStateBound:
bindingFailed := false
schedulerTargetedBinds = append(schedulerTargetedBinds, binding)
waitTime, bindingReady := isBindingReady(binding, readyTimeCutOff)
if bindingReady {
klog.V(2).InfoS("Found a ready bound binding", "clusterResourcePlacement", crpKObj, "binding", bindingKObj)
readyBindings = append(readyBindings, binding)
} else {
allReady = false
if waitTime >= 0 && waitTime < minWaitTime {
minWaitTime = waitTime
}
}
// check if the binding is failed or still on going
if bindingutils.HasBindingFailed(binding) {
klog.V(2).InfoS("Found a failed to be ready bound binding", "clusterResourcePlacement", crpKObj, "binding", bindingKObj)
bindingFailed = true
} else if !bindingutils.IsBindingDiffReported(binding) {
canBeReadyBindings = append(canBeReadyBindings, binding)
}
// check to see if binding is not being deleted.
if binding.DeletionTimestamp.IsZero() {
// PickFromResourceMatchedOverridesForTargetCluster always returns the ordered list of the overrides.
cro, ro, err := overrider.PickFromResourceMatchedOverridesForTargetCluster(ctx, r.Client, binding.Spec.TargetCluster, matchedCROs, matchedROs)
if err != nil {
return nil, nil, nil, false, 0, err
}
// The binding needs update if it's not pointing to the latest resource resourceBinding or the overrides.
if binding.Spec.ResourceSnapshotName != latestResourceSnapshot.Name || !equality.Semantic.DeepEqual(binding.Spec.ClusterResourceOverrideSnapshots, cro) || !equality.Semantic.DeepEqual(binding.Spec.ResourceOverrideSnapshots, ro) {
updateInfo := createUpdateInfo(binding, latestResourceSnapshot, cro, ro)
if bindingFailed {
// the binding has been applied but failed to apply, we can safely update it to latest resources without affecting max unavailable count
applyFailedUpdateCandidates = append(applyFailedUpdateCandidates, updateInfo)
} else {
updateCandidates = append(updateCandidates, updateInfo)
}
} else {
// The binding does not need update, but Fleet might need to refresh its status
// information.
upToDateBoundBindings = append(upToDateBoundBindings, toBeUpdatedBinding{currentBinding: binding})
}
} else if bindingReady {
// it is being deleted, it can be removed from the cluster at any time, so it can be unavailable at any time
canBeUnavailableBindings = append(canBeUnavailableBindings, binding)
}
}
}
if allReady {
minWaitTime = 0
}
// Calculate target number
targetNumber := r.calculateRealTarget(crp, schedulerTargetedBinds)
klog.V(2).InfoS("Calculated the targetNumber", "clusterResourcePlacement", crpKObj,
"targetNumber", targetNumber, "readyBindingNumber", len(readyBindings), "canBeUnavailableBindingNumber", len(canBeUnavailableBindings),
"canBeReadyBindingNumber", len(canBeReadyBindings), "boundingCandidateNumber", len(boundingCandidates),
"removeCandidateNumber", len(removeCandidates), "updateCandidateNumber", len(updateCandidates), "applyFailedUpdateCandidateNumber",
len(applyFailedUpdateCandidates), "minWaitTime", minWaitTime)
// the list of bindings that are to be updated by this rolling phase
toBeUpdatedBindingList := make([]toBeUpdatedBinding, 0)
if len(removeCandidates)+len(updateCandidates)+len(boundingCandidates)+len(applyFailedUpdateCandidates) == 0 {
return toBeUpdatedBindingList, nil, upToDateBoundBindings, false, minWaitTime, nil
}
toBeUpdatedBindingList, staleUnselectedBinding := determineBindingsToUpdate(crp, removeCandidates, updateCandidates, boundingCandidates, applyFailedUpdateCandidates, targetNumber,
readyBindings, canBeReadyBindings, canBeUnavailableBindings)
return toBeUpdatedBindingList, staleUnselectedBinding, upToDateBoundBindings, true, minWaitTime, nil
}