sigs.k8s.io/cluster-api@v1.7.1/util/conditions/merge.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package conditions
    18  
    19  import (
    20  	"sort"
    21  
    22  	corev1 "k8s.io/api/core/v1"
    23  
    24  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    25  )
    26  
    27  // localizedCondition defines a condition with the information of the object the conditions
    28  // was originated from.
    29  type localizedCondition struct {
    30  	*clusterv1.Condition
    31  	Getter
    32  }
    33  
    34  // merge a list of condition into a single one.
    35  // This operation is designed to ensure visibility of the most relevant conditions for defining the
    36  // operational state of a component. E.g. If there is one error in the condition list, this one takes
    37  // priority over the other conditions and it is should be reflected in the target condition.
    38  //
    39  // More specifically:
    40  // 1. Conditions are grouped by status, severity
    41  // 2. The resulting condition groups are sorted according to the following priority:
    42  //   - P0 - Status=False, Severity=Error
    43  //   - P1 - Status=False, Severity=Warning
    44  //   - P2 - Status=False, Severity=Info
    45  //   - P3 - Status=True
    46  //   - P4 - Status=Unknown
    47  //
    48  // 3. The group with highest priority is used to determine status, severity and other info of the target condition.
    49  //
    50  // Please note that the last operation includes also the task of computing the Reason and the Message for the target
    51  // condition; in order to complete such task some trade-off should be made, because there is no a golden rule
    52  // for summarizing many Reason/Message into single Reason/Message.
    53  // mergeOptions allows the user to adapt this process to the specific needs by exposing a set of merge strategies.
    54  func merge(conditions []localizedCondition, targetCondition clusterv1.ConditionType, options *mergeOptions) *clusterv1.Condition {
    55  	g := getConditionGroups(conditions)
    56  	if len(g) == 0 {
    57  		return nil
    58  	}
    59  
    60  	if g.TopGroup().status == corev1.ConditionTrue {
    61  		return TrueCondition(targetCondition)
    62  	}
    63  
    64  	targetReason := getReason(g, options)
    65  	targetMessage := getMessage(g, options)
    66  	if g.TopGroup().status == corev1.ConditionFalse {
    67  		return FalseCondition(targetCondition, targetReason, g.TopGroup().severity, targetMessage)
    68  	}
    69  	return UnknownCondition(targetCondition, targetReason, targetMessage)
    70  }
    71  
    72  // getConditionGroups groups a list of conditions according to status, severity values.
    73  // Additionally, the resulting groups are sorted by mergePriority.
    74  func getConditionGroups(conditions []localizedCondition) conditionGroups {
    75  	groups := conditionGroups{}
    76  
    77  	for _, condition := range conditions {
    78  		if condition.Condition == nil {
    79  			continue
    80  		}
    81  
    82  		added := false
    83  		for i := range groups {
    84  			if groups[i].status == condition.Status && groups[i].severity == condition.Severity {
    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  				severity:   condition.Severity,
    95  			})
    96  		}
    97  	}
    98  
    99  	// sort groups by priority
   100  	sort.Sort(groups)
   101  
   102  	// sorts conditions in the TopGroup so we ensure predictable result for merge strategies.
   103  	// condition are sorted using the same lexicographic order used by Set; in case two conditions
   104  	// have the same type, condition are sorted using according to the alphabetical order of the source object name.
   105  	if len(groups) > 0 {
   106  		sort.Slice(groups[0].conditions, func(i, j int) bool {
   107  			a := groups[0].conditions[i]
   108  			b := groups[0].conditions[j]
   109  			if a.Type != b.Type {
   110  				return lexicographicLess(a.Condition, b.Condition)
   111  			}
   112  			return a.GetName() < b.GetName()
   113  		})
   114  	}
   115  
   116  	return groups
   117  }
   118  
   119  // conditionGroups provides supports for grouping a list of conditions to be
   120  // merged into a single condition. ConditionGroups can be sorted by mergePriority.
   121  type conditionGroups []conditionGroup
   122  
   123  func (g conditionGroups) Len() int {
   124  	return len(g)
   125  }
   126  
   127  func (g conditionGroups) Less(i, j int) bool {
   128  	return g[i].mergePriority() < g[j].mergePriority()
   129  }
   130  
   131  func (g conditionGroups) Swap(i, j int) {
   132  	g[i], g[j] = g[j], g[i]
   133  }
   134  
   135  // TopGroup returns the condition group with the highest mergePriority.
   136  func (g conditionGroups) TopGroup() *conditionGroup {
   137  	if len(g) == 0 {
   138  		return nil
   139  	}
   140  	return &g[0]
   141  }
   142  
   143  // TrueGroup returns the condition group with status True, if any.
   144  func (g conditionGroups) TrueGroup() *conditionGroup {
   145  	return g.getByStatusAndSeverity(corev1.ConditionTrue, clusterv1.ConditionSeverityNone)
   146  }
   147  
   148  // ErrorGroup returns the condition group with status False and severity Error, if any.
   149  func (g conditionGroups) ErrorGroup() *conditionGroup {
   150  	return g.getByStatusAndSeverity(corev1.ConditionFalse, clusterv1.ConditionSeverityError)
   151  }
   152  
   153  // WarningGroup returns the condition group with status False and severity Warning, if any.
   154  func (g conditionGroups) WarningGroup() *conditionGroup {
   155  	return g.getByStatusAndSeverity(corev1.ConditionFalse, clusterv1.ConditionSeverityWarning)
   156  }
   157  
   158  func (g conditionGroups) getByStatusAndSeverity(status corev1.ConditionStatus, severity clusterv1.ConditionSeverity) *conditionGroup {
   159  	if len(g) == 0 {
   160  		return nil
   161  	}
   162  	for _, group := range g {
   163  		if group.status == status && group.severity == severity {
   164  			return &group
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  // conditionGroup define a group of conditions with the same status and severity,
   171  // and thus with the same priority when merging into a Ready condition.
   172  type conditionGroup struct {
   173  	status     corev1.ConditionStatus
   174  	severity   clusterv1.ConditionSeverity
   175  	conditions []localizedCondition
   176  }
   177  
   178  // mergePriority provides a priority value for the status and severity tuple that identifies this
   179  // condition group. The mergePriority value allows an easier sorting of conditions groups.
   180  func (g conditionGroup) mergePriority() int {
   181  	switch g.status {
   182  	case corev1.ConditionFalse:
   183  		switch g.severity {
   184  		case clusterv1.ConditionSeverityError:
   185  			return 0
   186  		case clusterv1.ConditionSeverityWarning:
   187  			return 1
   188  		case clusterv1.ConditionSeverityInfo:
   189  			return 2
   190  		}
   191  	case corev1.ConditionTrue:
   192  		return 3
   193  	case corev1.ConditionUnknown:
   194  		return 4
   195  	}
   196  
   197  	// this should never happen
   198  	return 99
   199  }