Files
open-cluster-management/pkg/work/spoke/conditions/helpers.go
Anne Lau 4dc99cd621 Progressing status conditions, true wins (#1332)
Signed-off-by: annelau <annelau@salesforce.com>
Co-authored-by: annelau <annelau@salesforce.com>
2026-01-16 06:53:14 +00:00

170 lines
5.6 KiB
Go

package conditions
import (
"fmt"
"slices"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
workapiv1 "open-cluster-management.io/api/work/v1"
)
const (
// conditionRuleReasonPrefix is used to identify conditions that were generated by condition rules
conditionRuleReasonPrefix = "ConditionRule"
)
// IsConditionGeneratedByConditionRule determines if a condition was created by condition rules
func IsConditionGeneratedByConditionRule(condition metav1.Condition) bool {
return strings.HasPrefix(condition.Reason, conditionRuleReasonPrefix)
}
// PruneConditionsGeneratedByConditionRules removes old conditions that were generated by a ConditionRule
// but have not been updated since that latest ManifestWork generation. Removing a ConditionRule will
// always update the ManifestWork generation, so any conditions generated by conditions rules from a
// previous generation must have been from a deleted rule.
func PruneConditionsGeneratedByConditionRules(conditions *[]metav1.Condition, workGeneration int64) {
*conditions = slices.DeleteFunc(*conditions, func(c metav1.Condition) bool {
return IsConditionGeneratedByConditionRule(c) && c.ObservedGeneration != workGeneration
})
}
// AggregateManifestConditions collects conditions from manifests and combines them into top level
// conditions for the ManifestWork. Only the Available condition and conditions generated by condition rules
// are aggregated.
func AggregateManifestConditions(generation int64, manifests []workapiv1.ManifestCondition) []metav1.Condition {
aggregated := map[string]aggregateCondition{
// Always set Available condition, even if there are no manifests
workapiv1.ManifestAvailable: {},
}
// Collect conditions by type
for _, manifest := range manifests {
for _, condition := range manifest.Conditions {
if condition.Type == workapiv1.ManifestAvailable || IsConditionGeneratedByConditionRule(condition) {
aggCondition := aggregated[condition.Type]
switch condition.Status {
case metav1.ConditionFalse:
aggCondition.numFalse++
case metav1.ConditionTrue:
aggCondition.numTrue++
case metav1.ConditionUnknown:
aggCondition.numUnknown++
}
if condition.Message != "" {
aggCondition.message = condition.Message
}
aggregated[condition.Type] = aggCondition
}
}
}
// Create aggregate conditions
conditions := make([]metav1.Condition, 0, len(aggregated))
for conditionType, aggCondition := range aggregated {
var condition metav1.Condition
switch conditionType {
case workapiv1.ManifestAvailable:
condition = newAggregateAvailableCondition(aggCondition, generation, len(manifests))
default:
condition = newAggregateConditionFromRules(conditionType, generation, aggCondition)
}
conditions = append(conditions, condition)
}
// Ensure consistent ordering of aggregated conditions
slices.SortFunc(conditions, func(a, b metav1.Condition) int {
return strings.Compare(a.Type, b.Type)
})
return conditions
}
type aggregateCondition struct {
numTrue, numFalse, numUnknown int
message string
}
func newAggregateAvailableCondition(aggCondition aggregateCondition, generation int64, total int) metav1.Condition {
switch {
case aggCondition.numFalse > 0:
return metav1.Condition{
Type: workapiv1.WorkAvailable,
Status: metav1.ConditionFalse,
Reason: "ResourcesNotAvailable",
ObservedGeneration: generation,
Message: fmt.Sprintf("%d of %d resources are not available", aggCondition.numFalse, total),
}
case aggCondition.numUnknown > 0:
return metav1.Condition{
Type: workapiv1.WorkAvailable,
Status: metav1.ConditionUnknown,
Reason: "ResourcesStatusUnknown",
ObservedGeneration: generation,
Message: fmt.Sprintf("%d of %d resources have unknown status", aggCondition.numUnknown, total),
}
case aggCondition.numTrue == 0:
return metav1.Condition{
Type: workapiv1.WorkAvailable,
Status: metav1.ConditionUnknown,
Reason: "ResourcesStatusUnknown",
ObservedGeneration: generation,
Message: "cannot get any available resource ",
}
default:
return metav1.Condition{
Type: workapiv1.WorkAvailable,
Status: metav1.ConditionTrue,
Reason: "ResourcesAvailable",
ObservedGeneration: generation,
Message: "All resources are available",
}
}
}
func newAggregateConditionFromRules(conditionType string, generation int64, agg aggregateCondition) metav1.Condition {
var status metav1.ConditionStatus
// Progressing uses opposite logic: True if ANY resource is progressing
if conditionType == workapiv1.WorkProgressing {
switch {
case agg.numTrue > 0:
status = metav1.ConditionTrue
case agg.numUnknown > 0:
status = metav1.ConditionUnknown
default:
status = metav1.ConditionFalse
}
} else {
// Default logic: False wins, then Unknown, then True
switch {
case agg.numFalse > 0:
status = metav1.ConditionFalse
case agg.numUnknown > 0 || agg.numTrue == 0:
status = metav1.ConditionUnknown
default:
status = metav1.ConditionTrue
}
}
// Use manifest condition message if there is only one, otherwise use generic message
numTotal := agg.numFalse + agg.numTrue + agg.numUnknown
var message string
if numTotal == 1 && agg.message != "" {
message = agg.message
} else {
message = fmt.Sprintf("Aggregated from %d manifest conditions", numTotal)
}
return metav1.Condition{
Type: conditionType,
Status: status,
Reason: "ConditionRulesAggregated",
ObservedGeneration: generation,
Message: message,
}
}