mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-02-14 10:00:11 +00:00
Signed-off-by: annelau <annelau@salesforce.com> Co-authored-by: annelau <annelau@salesforce.com>
170 lines
5.6 KiB
Go
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,
|
|
}
|
|
}
|