...

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

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

     1  package conditions
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"time"
     7  
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  
    10  	"edge-infra.dev/pkg/k8s/meta/status"
    11  )
    12  
    13  // Setter is an interface that defines methods a Kubernetes object should
    14  // implement in order to use the conditions package for setting conditions.
    15  type Setter interface {
    16  	Getter
    17  	SetConditions([]metav1.Condition)
    18  }
    19  
    20  func UniqueConditions(conditions []metav1.Condition) []metav1.Condition {
    21  	uniqueConditions := make([]metav1.Condition, 0)
    22  	keys := make(map[string]bool)
    23  	for _, condition := range conditions {
    24  		if _, exists := keys[condition.Type]; !exists {
    25  			keys[condition.Type] = true
    26  			uniqueConditions = append(uniqueConditions, condition)
    27  		}
    28  	}
    29  	return uniqueConditions
    30  }
    31  
    32  // Set sets the given condition.
    33  //
    34  // NOTE: If a condition already exists, the LastTransitionTime is updated only
    35  // if a change is detected in any of the following fields: Status, Reason, and
    36  // Message. The ObservedGeneration is always updated.
    37  func Set(to Setter, condition *metav1.Condition) {
    38  	if to == nil || condition == nil {
    39  		return
    40  	}
    41  
    42  	// Always set the observed generation on the condition.
    43  	condition.ObservedGeneration = to.GetGeneration()
    44  
    45  	// Check if the new conditions already exists, and change it only if there is
    46  	// a status transition, so current last transition time is preserved
    47  	conditions := to.GetConditions()
    48  	exists := false
    49  	for i := range conditions {
    50  		existingCondition := conditions[i]
    51  		if existingCondition.Type == condition.Type {
    52  			exists = true
    53  			if !hasSameState(&existingCondition, condition) {
    54  				condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))
    55  				conditions[i] = *condition
    56  				break
    57  			}
    58  			condition.LastTransitionTime = existingCondition.LastTransitionTime
    59  			// For new observed generations, update the condition to have the
    60  			// new generation, preserving the last transition time.
    61  			if existingCondition.ObservedGeneration != condition.ObservedGeneration {
    62  				conditions[i] = *condition
    63  			}
    64  			break
    65  		}
    66  	}
    67  
    68  	// If the condition does not exist, add it, setting the transition time only
    69  	// if not already set
    70  	if !exists {
    71  		if condition.LastTransitionTime.IsZero() {
    72  			condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))
    73  		}
    74  		conditions = append(conditions, *condition)
    75  	}
    76  
    77  	// Sort conditions for convenience of the consumer, i.e. kubectl.
    78  	sort.Slice(conditions, func(i, j int) bool {
    79  		return lexicographicLess(&conditions[i], &conditions[j])
    80  	})
    81  
    82  	to.SetConditions(conditions)
    83  }
    84  
    85  // TrueCondition returns a condition with Status=True and the given type, reason
    86  // and message.
    87  func TrueCondition(t, reason, messageFormat string, messageArgs ...interface{}) *metav1.Condition {
    88  	return &metav1.Condition{
    89  		Type:    t,
    90  		Status:  metav1.ConditionTrue,
    91  		Reason:  reason,
    92  		Message: fmt.Sprintf(messageFormat, messageArgs...),
    93  	}
    94  }
    95  
    96  // FalseCondition returns a condition with Status=False and the given type,
    97  // reason and message.
    98  func FalseCondition(t, reason, messageFormat string, messageArgs ...interface{}) *metav1.Condition {
    99  	return &metav1.Condition{
   100  		Type:    t,
   101  		Status:  metav1.ConditionFalse,
   102  		Reason:  reason,
   103  		Message: fmt.Sprintf(messageFormat, messageArgs...),
   104  	}
   105  }
   106  
   107  // UnknownCondition returns a condition with Status=Unknown and the given type,
   108  // reason and message.
   109  func UnknownCondition(t, reason, messageFormat string, messageArgs ...interface{}) *metav1.Condition {
   110  	return &metav1.Condition{
   111  		Type:    t,
   112  		Status:  metav1.ConditionUnknown,
   113  		Reason:  reason,
   114  		Message: fmt.Sprintf(messageFormat, messageArgs...),
   115  	}
   116  }
   117  
   118  // MarkTrue sets Status=True for the condition with the given type, reason and
   119  // message.
   120  func MarkTrue(to Setter, t, reason, messageFormat string, messageArgs ...interface{}) {
   121  	Set(to, TrueCondition(t, reason, messageFormat, messageArgs...))
   122  }
   123  
   124  // MarkUnknown sets Status=Unknown for the condition with the given type, reason
   125  // and message.
   126  func MarkUnknown(to Setter, t, reason, messageFormat string, messageArgs ...interface{}) {
   127  	Set(to, UnknownCondition(t, reason, messageFormat, messageArgs...))
   128  }
   129  
   130  // MarkFalse sets Status=False for the condition with the given type, reason and
   131  // message.
   132  func MarkFalse(to Setter, t, reason, messageFormat string, messageArgs ...interface{}) {
   133  	Set(to, FalseCondition(t, reason, messageFormat, messageArgs...))
   134  }
   135  
   136  // MarkReconciling sets status.ReconcilingCondition=True with the given reason and
   137  // message, and deletes the status.StalledCondition. This is normally called at
   138  // the beginning of a reconcile run for an object.
   139  //
   140  // For more information about the condition types, see the kstatus spec:
   141  // https://github.com/kubernetes-sigs/cli-utils/blob/e351b2bc43cec2107ba1d874c3dec54fd0956c59/pkg/kstatus/README.md#conditions
   142  func MarkReconciling(to Setter, reason, messageFormat string, messageArgs ...interface{}) {
   143  	Delete(to, status.StalledCondition)
   144  	MarkTrue(to, status.ReconcilingCondition, reason, messageFormat, messageArgs...)
   145  }
   146  
   147  // MarkStalled sets status.StalledCondition=True with the given reason and message,
   148  // and deletes the status.ReconcilingCondition. This is normally deferred and
   149  // conditionally called at the end of a reconcile run for an object. A common
   150  // approach is to mark the object stalled if the object is not requeued as a
   151  // reconcile result.
   152  //
   153  // For more information about the condition types, see the kstatus spec:
   154  // https://github.com/kubernetes-sigs/cli-utils/blob/e351b2bc43cec2107ba1d874c3dec54fd0956c59/pkg/kstatus/README.md#conditions
   155  func MarkStalled(to Setter, reason, messageFormat string, messageArgs ...interface{}) {
   156  	Delete(to, status.ReconcilingCondition)
   157  	MarkTrue(to, status.StalledCondition, reason, messageFormat, messageArgs...)
   158  }
   159  
   160  // SetSummary creates a new summary condition with the summary of all the
   161  // conditions existing on an object. If the object does not have other conditions,
   162  // no summary condition is generated.
   163  func SetSummary(to Setter, targetCondition string, options ...MergeOption) {
   164  	Set(to, summary(to, targetCondition, options...))
   165  }
   166  
   167  // SetMirror creates a new condition by mirroring the the Ready condition from a
   168  // dependent object; if the Ready condition does not exists in the source object,
   169  // no target conditions is generated.
   170  func SetMirror(to Setter, targetCondition string, from Getter, options ...MirrorOptions) {
   171  	Set(to, mirror(from, targetCondition, options...))
   172  }
   173  
   174  // SetAggregate creates a new condition with the aggregation of all the
   175  // conditions from a list of dependency objects, or a subset using WithConditions;
   176  // if none of the source objects have a condition within the scope of the merge
   177  // operation, no target condition is generated.
   178  func SetAggregate(to Setter, targetCondition string, from []Getter, options ...MergeOption) {
   179  	Set(to, aggregate(from, targetCondition, options...))
   180  }
   181  
   182  // Delete deletes the condition with the given type.
   183  func Delete(to Setter, t string) {
   184  	if to == nil {
   185  		return
   186  	}
   187  
   188  	conditions := to.GetConditions()
   189  	newConditions := make([]metav1.Condition, 0, len(conditions))
   190  	for _, condition := range conditions {
   191  		if condition.Type != t {
   192  			newConditions = append(newConditions, condition)
   193  		}
   194  	}
   195  	to.SetConditions(newConditions)
   196  }
   197  
   198  // conditionWeights defines the weight of condition types that have priority in
   199  // lexicographicLess.
   200  var conditionWeights = map[string]int{
   201  	status.StalledCondition:     0,
   202  	status.ReconcilingCondition: 1,
   203  	status.ReadyCondition:       2,
   204  }
   205  
   206  // lexicographicLess returns true if a condition is less than another in regard
   207  // to the order of conditions designed for convenience of the consumer,
   208  // i.e. kubectl. The condition types in conditionWeights always go first, sorted
   209  // by their defined weight, followed by all the other conditions sorted by
   210  // highest observedGeneration and lexicographically by Type.
   211  func lexicographicLess(i, j *metav1.Condition) bool {
   212  	w1, ok1 := conditionWeights[i.Type]
   213  	w2, ok2 := conditionWeights[j.Type]
   214  	switch {
   215  	case ok1 && ok2:
   216  		return w1 < w2
   217  	case ok1, ok2:
   218  		return !ok2
   219  	case i.ObservedGeneration == j.ObservedGeneration:
   220  		return i.Type < j.Type
   221  	default:
   222  		return i.ObservedGeneration > j.ObservedGeneration
   223  	}
   224  }
   225  
   226  // hasSameState returns true if a condition has the same state of another; state
   227  // is defined by the union of following fields: Type, Status, Reason, and
   228  // Message (it excludes LastTransitionTime and ObservedGeneration).
   229  func hasSameState(i, j *metav1.Condition) bool {
   230  	return i.Type == j.Type &&
   231  		i.Status == j.Status &&
   232  		i.Reason == j.Reason &&
   233  		i.Message == j.Message
   234  }
   235  

View as plain text