...

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

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

     1  package conditions
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  // mergeOptions allows to set strategies for merging a set of conditions into a single condition, and more specifically
     9  // for computing the target Reason and the target Message.
    10  type mergeOptions struct {
    11  	conditionTypes                 []string
    12  	negativePolarityConditionTypes []string
    13  
    14  	addSourceRef                       bool
    15  	addSourceRefIfConditionTypes       []string
    16  	addCounter                         bool
    17  	addCounterOnlyIfConditionTypes     []string
    18  	addStepCounter                     bool
    19  	addStepCounterIfOnlyConditionTypes []string
    20  
    21  	stepCounter int
    22  
    23  	withLatestGeneration bool
    24  }
    25  
    26  // MergeOption defines an option for computing a summary of conditions.
    27  type MergeOption func(*mergeOptions)
    28  
    29  // WithConditions instructs merge about the condition types to consider when
    30  // doing a merge operation; if this option is not specified, all the conditions
    31  // (except Ready, Stalled, and Reconciling) will be considered. This is required
    32  // so we can provide some guarantees about the semantic of the target condition
    33  // without worrying about side effects if someone or something adds custom
    34  // conditions to the objects.
    35  //
    36  // NOTE: The order of conditions types defines the priority for determining the
    37  // Reason and Message for the target condition.
    38  func WithConditions(t ...string) MergeOption {
    39  	return func(c *mergeOptions) {
    40  		c.conditionTypes = t
    41  	}
    42  }
    43  
    44  // WithNegativePolarityConditions instructs merge about the condition types that
    45  // adhere to a "normal-false" or "abnormal-true" pattern, i.e. that conditions
    46  // are present with a value of True whenever something unusual happens.
    47  //
    48  // NOTE: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
    49  func WithNegativePolarityConditions(t ...string) MergeOption {
    50  	return func(c *mergeOptions) {
    51  		c.negativePolarityConditionTypes = t
    52  	}
    53  }
    54  
    55  // WithCounter instructs merge to add a "x of y Type" string to the message, where x is the number of conditions in the
    56  // top group, y is the number of objects in scope, and Type is the top group condition type.
    57  func WithCounter() MergeOption {
    58  	return func(c *mergeOptions) {
    59  		c.addCounter = true
    60  	}
    61  }
    62  
    63  // WithCounterIfOnly ensures a counter is show only if a subset of condition
    64  // exists. This may apply when you want to use a step counter while reconciling
    65  // the resource, but then want to move away from this notation as soon as the
    66  // resource has been reconciled, and e.g. a health check condition is generated.
    67  //
    68  // IMPORTANT: This options requires WithStepCounter or WithStepCounterIf to be set.
    69  func WithCounterIfOnly(t ...string) MergeOption {
    70  	return func(c *mergeOptions) {
    71  		c.addCounterOnlyIfConditionTypes = t
    72  	}
    73  }
    74  
    75  // WithStepCounter instructs merge to add a "x of y completed" string to the message, where x is the number of
    76  // conditions with Status=true and y is the number of conditions in scope.
    77  func WithStepCounter() MergeOption {
    78  	return func(c *mergeOptions) {
    79  		c.addStepCounter = true
    80  	}
    81  }
    82  
    83  // WithStepCounterIf adds a step counter if the value is true.
    84  // This can be used e.g. to add a step counter only if the object is not being
    85  // deleted.
    86  func WithStepCounterIf(value bool) MergeOption {
    87  	return func(c *mergeOptions) {
    88  		c.addStepCounter = value
    89  	}
    90  }
    91  
    92  // WithStepCounterIfOnly ensures a step counter is show only if a subset of condition exists.
    93  // This may apply when you want to use a step counter while reconciling the resource, but then want to move away from
    94  // this notation as soon as the resource has been reconciled, and e.g. a health check condition is generated.
    95  //
    96  // IMPORTANT: This options requires WithStepCounter or WithStepCounterIf to be set.
    97  // IMPORTANT: This option works only while generating the Summary or Aggregated condition.
    98  func WithStepCounterIfOnly(t ...string) MergeOption {
    99  	return func(c *mergeOptions) {
   100  		c.addStepCounterIfOnlyConditionTypes = t
   101  	}
   102  }
   103  
   104  // WithSourceRef instructs merge to add info about the originating object to the target Reason and
   105  // in summaries.
   106  func WithSourceRef() MergeOption {
   107  	return func(c *mergeOptions) {
   108  		c.addSourceRef = true
   109  	}
   110  }
   111  
   112  // WithSourceRefIf ensures a source ref is show only if one of the types in the set exists.
   113  func WithSourceRefIf(t ...string) MergeOption {
   114  	return func(c *mergeOptions) {
   115  		c.addSourceRefIfConditionTypes = t
   116  	}
   117  }
   118  
   119  // WithLatestGeneration instructs merge to consider the conditions with the
   120  // latest observed generation only.
   121  func WithLatestGeneration() MergeOption {
   122  	return func(c *mergeOptions) {
   123  		c.withLatestGeneration = true
   124  	}
   125  }
   126  
   127  // getReason returns the reason to be applied to the condition resulting by merging a set of condition groups.
   128  // The reason is computed according to the given mergeOptions.
   129  func getReason(groups conditionGroups, options *mergeOptions) string {
   130  	return getFirstReason(groups, options.conditionTypes, options.addSourceRef)
   131  }
   132  
   133  // getFirstReason returns the first reason from the ordered list of conditions in the top group.
   134  // If required, the reason gets localized with the source object reference.
   135  func getFirstReason(g conditionGroups, order []string, addSourceRef bool) string {
   136  	if condition := getFirstCondition(g, order); condition != nil {
   137  		reason := condition.Reason
   138  		if addSourceRef {
   139  			return localizeReason(reason, condition.Getter)
   140  		}
   141  		return reason
   142  	}
   143  	return ""
   144  }
   145  
   146  // localizeReason adds info about the originating object to the target Reason.
   147  func localizeReason(reason string, from Getter) string {
   148  	if strings.Contains(reason, "@") {
   149  		return reason
   150  	}
   151  	return fmt.Sprintf("%s @ %s/%s", reason, from.GetObjectKind().GroupVersionKind().Kind, from.GetName())
   152  }
   153  
   154  // getMessage returns the message to be applied to the condition resulting by merging a set of condition groups.
   155  // The message is computed according to the given mergeOptions, but in case of errors or warning a summary of existing
   156  // errors is automatically added.
   157  func getMessage(groups conditionGroups, options *mergeOptions) string {
   158  	if options.addStepCounter {
   159  		return getStepCounterMessage(groups, options.stepCounter)
   160  	}
   161  	if options.addCounter {
   162  		return getCounterMessage(groups, options.stepCounter)
   163  	}
   164  	return getFirstMessage(groups, options.conditionTypes)
   165  }
   166  
   167  // getCounterMessage returns a "x of y <Type>", where x is the number of conditions in the top group, y is the number
   168  // passed to this method and <Type> is the condition type of the top group.
   169  func getCounterMessage(groups conditionGroups, to int) string {
   170  	topGroup := groups.TopGroup()
   171  	if topGroup == nil {
   172  		return fmt.Sprintf("%d of %d", 0, to)
   173  	}
   174  	ct := len(topGroup.conditions)
   175  	return fmt.Sprintf("%d of %d %s", ct, to, topGroup.conditions[0].Type)
   176  }
   177  
   178  // getStepCounterMessage returns a message "x of y completed", where x is the number of conditions with Status=True and
   179  // Polarity=Positive and y is the number passed to this method.
   180  func getStepCounterMessage(groups conditionGroups, to int) string {
   181  	ct := 0
   182  	if trueGroup := groups.TruePositivePolarityGroup(); trueGroup != nil {
   183  		ct = len(trueGroup.conditions)
   184  	}
   185  	return fmt.Sprintf("%d of %d completed", ct, to)
   186  }
   187  
   188  // getFirstMessage returns the message from the ordered list of conditions in the top group.
   189  func getFirstMessage(groups conditionGroups, order []string) string {
   190  	if condition := getFirstCondition(groups, order); condition != nil {
   191  		return condition.Message
   192  	}
   193  	return ""
   194  }
   195  
   196  // getFirstCondition returns a first condition from the ordered list of conditions in the top group.
   197  func getFirstCondition(g conditionGroups, priority []string) *localizedCondition {
   198  	topGroup := g.TopGroup()
   199  	if topGroup == nil {
   200  		return nil
   201  	}
   202  
   203  	switch len(topGroup.conditions) {
   204  	case 0:
   205  		return nil
   206  	case 1:
   207  		return &topGroup.conditions[0]
   208  	default:
   209  		for _, p := range priority {
   210  			for _, c := range topGroup.conditions {
   211  				if c.Type == p {
   212  					return &c
   213  				}
   214  			}
   215  		}
   216  		return &topGroup.conditions[0]
   217  	}
   218  }
   219  

View as plain text