...

Source file src/edge-infra.dev/pkg/k8s/runtime/conditions/merge.go

Documentation: edge-infra.dev/pkg/k8s/runtime/conditions

     1  package conditions
     2  
     3  import (
     4  	"sort"
     5  
     6  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     7  )
     8  
     9  // localizedCondition defines a condition with the information of the object the conditions was originated from.
    10  type localizedCondition struct {
    11  	*metav1.Condition
    12  	Getter
    13  }
    14  
    15  // merge a list of condition into a single one.
    16  // This operation is designed to ensure visibility of the most relevant conditions for defining the operational state of
    17  // a component. E.g. If there is one error in the condition list, this one takes priority over the other conditions and
    18  // it is should be reflected in the target condition.
    19  //
    20  // More specifically:
    21  // 1. Conditions are grouped by status, polarity and observed generation (optional).
    22  // 2. The resulting condition groups are sorted according to the following priority:
    23  //   - P0 - Status=True, NegativePolarity=True
    24  //   - P1 - Status=False, NegativePolarity=False
    25  //   - P2 - Status=True, NegativePolarity=False
    26  //   - P3 - Status=False, NegativePolarity=True
    27  //   - P4 - Status=Unknown
    28  //
    29  // 3. The group with highest priority is used to determine status, and other info of the target condition.
    30  // 4. If the polarity of the highest priority and target priority differ, it is inverted.
    31  // 5. If the observed generation is considered, the condition groups with the latest generation get the highest
    32  // priority.
    33  //
    34  // Please note that the last operation includes also the task of computing the Reason and the Message for the target
    35  // condition; in order to complete such task some trade-off should be made, because there is no a golden rule for
    36  // summarizing many Reason/Message into single Reason/Message. mergeOptions allows the user to adapt this process to the
    37  // specific needs by exposing a set of merge strategies.
    38  func merge(conditions []localizedCondition, targetCondition string, options *mergeOptions) *metav1.Condition {
    39  	g := getConditionGroups(conditions, options)
    40  	if len(g) == 0 {
    41  		return nil
    42  	}
    43  
    44  	topGroup := g.TopGroup()
    45  	targetReason := getReason(g, options)
    46  	targetMessage := getMessage(g, options)
    47  	targetNegativePolarity := stringInSlice(options.negativePolarityConditionTypes, targetCondition)
    48  
    49  	switch topGroup.status {
    50  	case metav1.ConditionTrue:
    51  		// Inverse the negative polarity if the target condition has positive polarity.
    52  		if topGroup.negativePolarity != targetNegativePolarity {
    53  			return FalseCondition(targetCondition, targetReason, "%s", targetMessage)
    54  		}
    55  		return TrueCondition(targetCondition, targetReason, "%s", targetMessage)
    56  	case metav1.ConditionFalse:
    57  		// Inverse the negative polarity if the target condition has positive polarity.
    58  		if topGroup.negativePolarity != targetNegativePolarity {
    59  			return TrueCondition(targetCondition, targetReason, "%s", targetMessage)
    60  		}
    61  		return FalseCondition(targetCondition, targetReason, "%s", targetMessage)
    62  	default:
    63  		return UnknownCondition(targetCondition, targetReason, "%s", targetMessage)
    64  	}
    65  }
    66  
    67  // getConditionGroups groups a list of conditions according to status values and polarity.
    68  // Additionally, the resulting groups are sorted by mergePriority.
    69  func getConditionGroups(conditions []localizedCondition, options *mergeOptions) conditionGroups {
    70  	groups := conditionGroups{}
    71  
    72  	for _, condition := range conditions {
    73  		if condition.Condition == nil {
    74  			continue
    75  		}
    76  
    77  		added := false
    78  		for i := range groups {
    79  			if groups[i].status == condition.Status &&
    80  				groups[i].negativePolarity == stringInSlice(options.negativePolarityConditionTypes, condition.Type) {
    81  				// If withLatestGeneration is true, add to group only if the generation match.
    82  				if options.withLatestGeneration && groups[i].generation != condition.ObservedGeneration {
    83  					continue
    84  				}
    85  				groups[i].conditions = append(groups[i].conditions, condition)
    86  				added = true
    87  				break
    88  			}
    89  		}
    90  		if !added {
    91  			groups = append(groups, conditionGroup{
    92  				conditions:       []localizedCondition{condition},
    93  				status:           condition.Status,
    94  				negativePolarity: stringInSlice(options.negativePolarityConditionTypes, condition.Type),
    95  				generation:       condition.ObservedGeneration,
    96  			})
    97  		}
    98  	}
    99  
   100  	// If withLatestGeneration is true, form a conditionGroups of the groups
   101  	// with the latest generation.
   102  	if options.withLatestGeneration {
   103  		latestGen := groups.latestGeneration()
   104  		latestGroups := conditionGroups{}
   105  		for _, g := range groups {
   106  			if g.generation == latestGen {
   107  				latestGroups = append(latestGroups, g)
   108  			}
   109  		}
   110  		groups = latestGroups
   111  	}
   112  
   113  	// sort groups by priority
   114  	sort.Sort(groups)
   115  
   116  	// sorts conditions in the TopGroup so we ensure predictable result for merge strategies.
   117  	// condition are sorted using the same lexicographic order used by Set; in case two conditions
   118  	// have the same type, condition are sorted using according to the alphabetical order of the source object name.
   119  	if len(groups) > 0 {
   120  		sort.Slice(groups[0].conditions, func(i, j int) bool {
   121  			a := groups[0].conditions[i]
   122  			b := groups[0].conditions[j]
   123  			if a.Type != b.Type {
   124  				return lexicographicLess(a.Condition, b.Condition)
   125  			}
   126  			return a.GetName() < b.GetName()
   127  		})
   128  	}
   129  
   130  	return groups
   131  }
   132  
   133  // conditionGroups provides supports for grouping a list of conditions to be merged into a single condition.
   134  // ConditionGroups can be sorted by mergePriority.
   135  type conditionGroups []conditionGroup
   136  
   137  func (g conditionGroups) Len() int {
   138  	return len(g)
   139  }
   140  
   141  func (g conditionGroups) Less(i, j int) bool {
   142  	return g[i].mergePriority() < g[j].mergePriority()
   143  }
   144  
   145  func (g conditionGroups) Swap(i, j int) {
   146  	g[i], g[j] = g[j], g[i]
   147  }
   148  
   149  // TopGroup returns the the condition group with the highest mergePriority.
   150  func (g conditionGroups) TopGroup() *conditionGroup {
   151  	if len(g) == 0 {
   152  		return nil
   153  	}
   154  	return &g[0]
   155  }
   156  
   157  // TruePositivePolarityGroup returns the the condition group with status True/Positive, if any.
   158  func (g conditionGroups) TruePositivePolarityGroup() *conditionGroup {
   159  	if g.Len() == 0 {
   160  		return nil
   161  	}
   162  	for _, group := range g {
   163  		if !group.negativePolarity && group.status == metav1.ConditionTrue {
   164  			return &group
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  // latestGeneration returns the latest generation of the conditionGroups.
   171  func (g conditionGroups) latestGeneration() int64 {
   172  	var max int64
   173  	for _, group := range g {
   174  		if group.generation > max {
   175  			max = group.generation
   176  		}
   177  	}
   178  	return max
   179  }
   180  
   181  // conditionGroup defines a group of conditions with the same metav1.ConditionStatus, polarity and observed generation, and thus with the
   182  // same priority when merging into a condition.
   183  type conditionGroup struct {
   184  	status           metav1.ConditionStatus
   185  	negativePolarity bool
   186  	conditions       []localizedCondition
   187  	generation       int64
   188  }
   189  
   190  // mergePriority provides a priority value for the status and polarity tuple that identifies this condition group. The
   191  // mergePriority value allows an easier sorting of conditions groups.
   192  func (g conditionGroup) mergePriority() (p int) {
   193  	switch g.status {
   194  	case metav1.ConditionTrue:
   195  		p = 0
   196  		if !g.negativePolarity {
   197  			p = 2
   198  		}
   199  		return
   200  	case metav1.ConditionFalse:
   201  		p = 1
   202  		if g.negativePolarity {
   203  			p = 3
   204  		}
   205  		return
   206  	case metav1.ConditionUnknown:
   207  		return 4
   208  	default:
   209  		return 99
   210  	}
   211  }
   212  
   213  func stringInSlice(s []string, val string) bool {
   214  	for _, s := range s {
   215  		if s == val {
   216  			return true
   217  		}
   218  	}
   219  	return false
   220  }
   221  

View as plain text