knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/condition_set.go (about)

     1  /*
     2  Copyright 2019 The Knative 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 apis
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"time"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  )
    29  
    30  // ConditionsAccessor is the interface for a Resource that implements the getter and
    31  // setter for accessing a Condition collection.
    32  // +k8s:deepcopy-gen=true
    33  type ConditionsAccessor interface {
    34  	GetConditions() Conditions
    35  	SetConditions(Conditions)
    36  }
    37  
    38  // ConditionAccessor is used to access a condition through it's type
    39  type ConditionAccessor interface {
    40  	// GetCondition finds and returns the Condition that matches the ConditionType
    41  	// It should return nil if the condition type is not present
    42  	GetCondition(t ConditionType) *Condition
    43  }
    44  
    45  // ConditionSet is an abstract collection of the possible ConditionType values
    46  // that a particular resource might expose.  It also holds the "happy condition"
    47  // for that resource, which we define to be one of Ready or Succeeded depending
    48  // on whether it is a Living or Batch process respectively.
    49  // +k8s:deepcopy-gen=false
    50  type ConditionSet struct {
    51  	happy      ConditionType
    52  	dependents []ConditionType
    53  }
    54  
    55  // ConditionManager allows a resource to operate on its Conditions using higher
    56  // order operations.
    57  type ConditionManager interface {
    58  	ConditionAccessor
    59  
    60  	// IsHappy looks at the happy condition and returns true if that condition is
    61  	// set to true.
    62  	IsHappy() bool
    63  
    64  	// GetTopLevelCondition finds and returns the top level Condition (happy Condition).
    65  	GetTopLevelCondition() *Condition
    66  
    67  	// SetCondition sets or updates the Condition on Conditions for Condition.Type.
    68  	// If there is an update, Conditions are stored back sorted.
    69  	SetCondition(new Condition)
    70  
    71  	// ClearCondition removes the non terminal condition that matches the ConditionType
    72  	ClearCondition(t ConditionType) error
    73  
    74  	// MarkTrue sets the status of t to true, and then marks the happy condition to
    75  	// true if all dependents are true.
    76  	MarkTrue(t ConditionType)
    77  
    78  	// MarkTrueWithReason sets the status of t to true with the reason, and then marks the happy
    79  	// condition to true if all dependents are true.
    80  	MarkTrueWithReason(t ConditionType, reason, messageFormat string, messageA ...interface{})
    81  
    82  	// MarkUnknown sets the status of t to Unknown and also sets the happy condition
    83  	// to Unknown if no other dependent condition is in an error state.
    84  	MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{})
    85  
    86  	// MarkFalse sets the status of t and the happy condition to False.
    87  	MarkFalse(t ConditionType, reason, messageFormat string, messageA ...interface{})
    88  
    89  	// InitializeConditions updates all Conditions in the ConditionSet to Unknown
    90  	// if not set.
    91  	InitializeConditions()
    92  }
    93  
    94  // NewLivingConditionSet returns a ConditionSet to hold the conditions for the
    95  // living resource. ConditionReady is used as the happy condition.
    96  // The set of condition types provided are those of the terminal subconditions.
    97  func NewLivingConditionSet(d ...ConditionType) ConditionSet {
    98  	return newConditionSet(ConditionReady, d...)
    99  }
   100  
   101  // NewBatchConditionSet returns a ConditionSet to hold the conditions for the
   102  // batch resource. ConditionSucceeded is used as the happy condition.
   103  // The set of condition types provided are those of the terminal subconditions.
   104  func NewBatchConditionSet(d ...ConditionType) ConditionSet {
   105  	return newConditionSet(ConditionSucceeded, d...)
   106  }
   107  
   108  // newConditionSet returns a ConditionSet to hold the conditions that are
   109  // important for the caller. The first ConditionType is the overarching status
   110  // for that will be used to signal the resources' status is Ready or Succeeded.
   111  func newConditionSet(happy ConditionType, dependents ...ConditionType) ConditionSet {
   112  	deps := make([]ConditionType, 0, len(dependents))
   113  	for _, d := range dependents {
   114  		// Skip duplicates
   115  		if d == happy || contains(deps, d) {
   116  			continue
   117  		}
   118  		deps = append(deps, d)
   119  	}
   120  	return ConditionSet{
   121  		happy:      happy,
   122  		dependents: deps,
   123  	}
   124  }
   125  
   126  func contains(ct []ConditionType, t ConditionType) bool {
   127  	for _, c := range ct {
   128  		if c == t {
   129  			return true
   130  		}
   131  	}
   132  	return false
   133  }
   134  
   135  // Check that conditionsImpl implements ConditionManager.
   136  var _ ConditionManager = (*conditionsImpl)(nil)
   137  
   138  // conditionsImpl implements the helper methods for evaluating Conditions.
   139  // +k8s:deepcopy-gen=false
   140  type conditionsImpl struct {
   141  	ConditionSet
   142  	accessor ConditionsAccessor
   143  }
   144  
   145  // GetTopLevelConditionType is an accessor for the top-level happy condition.
   146  func (r ConditionSet) GetTopLevelConditionType() ConditionType {
   147  	return r.happy
   148  }
   149  
   150  // Manage creates a ConditionManager from an accessor object using the original
   151  // ConditionSet as a reference. Status must be a pointer to a struct.
   152  func (r ConditionSet) Manage(status ConditionsAccessor) ConditionManager {
   153  	return conditionsImpl{
   154  		accessor:     status,
   155  		ConditionSet: r,
   156  	}
   157  }
   158  
   159  // IsHappy looks at the top level Condition (happy Condition) and returns true if that condition is
   160  // set to true.
   161  func (r conditionsImpl) IsHappy() bool {
   162  	return r.GetTopLevelCondition().IsTrue()
   163  }
   164  
   165  // GetTopLevelCondition finds and returns the top level Condition (happy Condition).
   166  func (r conditionsImpl) GetTopLevelCondition() *Condition {
   167  	return r.GetCondition(r.happy)
   168  }
   169  
   170  // GetCondition finds and returns the Condition that matches the ConditionType
   171  // previously set on Conditions.
   172  func (r conditionsImpl) GetCondition(t ConditionType) *Condition {
   173  	if r.accessor == nil {
   174  		return nil
   175  	}
   176  
   177  	for _, c := range r.accessor.GetConditions() {
   178  		if c.Type == t {
   179  			return &c
   180  		}
   181  	}
   182  	return nil
   183  }
   184  
   185  // SetCondition sets or updates the Condition on Conditions for Condition.Type.
   186  // If there is an update, Conditions are stored back sorted.
   187  func (r conditionsImpl) SetCondition(cond Condition) {
   188  	if r.accessor == nil {
   189  		return
   190  	}
   191  	t := cond.Type
   192  	var conditions Conditions
   193  	for _, c := range r.accessor.GetConditions() {
   194  		if c.Type != t {
   195  			conditions = append(conditions, c)
   196  		} else {
   197  			// If we'd only update the LastTransitionTime, then return.
   198  			cond.LastTransitionTime = c.LastTransitionTime
   199  			if reflect.DeepEqual(cond, c) {
   200  				return
   201  			}
   202  		}
   203  	}
   204  	cond.LastTransitionTime = VolatileTime{Inner: metav1.NewTime(time.Now())}
   205  	conditions = append(conditions, cond)
   206  	// Sorted for convenience of the consumer, i.e. kubectl.
   207  	sort.Slice(conditions, func(i, j int) bool { return conditions[i].Type < conditions[j].Type })
   208  	r.accessor.SetConditions(conditions)
   209  }
   210  
   211  func (r conditionsImpl) isTerminal(t ConditionType) bool {
   212  	for _, cond := range r.dependents {
   213  		if cond == t {
   214  			return true
   215  		}
   216  	}
   217  	return t == r.happy
   218  }
   219  
   220  func (r conditionsImpl) severity(t ConditionType) ConditionSeverity {
   221  	if r.isTerminal(t) {
   222  		return ConditionSeverityError
   223  	}
   224  	return ConditionSeverityInfo
   225  }
   226  
   227  // RemoveCondition removes the non terminal condition that matches the ConditionType
   228  // Not implemented for terminal conditions
   229  func (r conditionsImpl) ClearCondition(t ConditionType) error {
   230  	var conditions Conditions
   231  
   232  	if r.accessor == nil {
   233  		return nil
   234  	}
   235  	// Terminal conditions are not handled as they can't be nil
   236  	if r.isTerminal(t) {
   237  		return errors.New("clearing terminal conditions not implemented")
   238  	}
   239  	cond := r.GetCondition(t)
   240  	if cond == nil {
   241  		return nil
   242  	}
   243  	for _, c := range r.accessor.GetConditions() {
   244  		if c.Type != t {
   245  			conditions = append(conditions, c)
   246  		}
   247  	}
   248  
   249  	// Sorted for convenience of the consumer, i.e. kubectl.
   250  	sort.Slice(conditions, func(i, j int) bool { return conditions[i].Type < conditions[j].Type })
   251  	r.accessor.SetConditions(conditions)
   252  
   253  	return nil
   254  }
   255  
   256  // MarkTrue sets the status of t to true, and then marks the happy condition to
   257  // true if all other dependents are also true.
   258  func (r conditionsImpl) MarkTrue(t ConditionType) {
   259  	// Set the specified condition.
   260  	r.SetCondition(Condition{
   261  		Type:     t,
   262  		Status:   corev1.ConditionTrue,
   263  		Severity: r.severity(t),
   264  	})
   265  	r.recomputeHappiness(t)
   266  }
   267  
   268  // MarkTrueWithReason sets the status of t to true with the reason, and then marks the happy condition to
   269  // true if all other dependents are also true.
   270  func (r conditionsImpl) MarkTrueWithReason(t ConditionType, reason, messageFormat string, messageA ...interface{}) {
   271  	// set the specified condition
   272  	r.SetCondition(Condition{
   273  		Type:     t,
   274  		Status:   corev1.ConditionTrue,
   275  		Reason:   reason,
   276  		Message:  fmt.Sprintf(messageFormat, messageA...),
   277  		Severity: r.severity(t),
   278  	})
   279  	r.recomputeHappiness(t)
   280  }
   281  
   282  // recomputeHappiness marks the happy condition to true if all other dependents are also true.
   283  func (r conditionsImpl) recomputeHappiness(t ConditionType) {
   284  	if c := r.findUnhappyDependent(); c != nil {
   285  		// Propagate unhappy dependent to happy condition.
   286  		r.SetCondition(Condition{
   287  			Type:     r.happy,
   288  			Status:   c.Status,
   289  			Reason:   c.Reason,
   290  			Message:  c.Message,
   291  			Severity: r.severity(r.happy),
   292  		})
   293  	} else if t != r.happy {
   294  		// Set the happy condition to true.
   295  		r.SetCondition(Condition{
   296  			Type:     r.happy,
   297  			Status:   corev1.ConditionTrue,
   298  			Severity: r.severity(r.happy),
   299  		})
   300  	}
   301  }
   302  
   303  func (r conditionsImpl) findUnhappyDependent() *Condition {
   304  	// This only works if there are dependents.
   305  	if len(r.dependents) == 0 {
   306  		return nil
   307  	}
   308  
   309  	// Do not modify the accessors condition order.
   310  	conditions := r.accessor.GetConditions().DeepCopy()
   311  
   312  	// Filter based on terminal status.
   313  	n := 0
   314  	for _, c := range conditions {
   315  		if c.Severity == ConditionSeverityError && c.Type != r.happy {
   316  			conditions[n] = c
   317  			n++
   318  		}
   319  	}
   320  	conditions = conditions[:n]
   321  
   322  	// Sort set conditions by time.
   323  	sort.Slice(conditions, func(i, j int) bool {
   324  		return conditions[i].LastTransitionTime.Inner.Time.After(conditions[j].LastTransitionTime.Inner.Time)
   325  	})
   326  
   327  	// First check the conditions with Status == False.
   328  	for _, c := range conditions {
   329  		// False conditions trump Unknown.
   330  		if c.IsFalse() {
   331  			return &c
   332  		}
   333  	}
   334  	// Second check for conditions with Status == Unknown.
   335  	for _, c := range conditions {
   336  		if c.IsUnknown() {
   337  			return &c
   338  		}
   339  	}
   340  
   341  	// If something was not initialized.
   342  	if len(r.dependents) > len(conditions) {
   343  		return &Condition{
   344  			Status: corev1.ConditionUnknown,
   345  		}
   346  	}
   347  
   348  	// All dependents are fine.
   349  	return nil
   350  }
   351  
   352  // MarkUnknown sets the status of t to Unknown and also sets the happy condition
   353  // to Unknown if no other dependent condition is in an error state.
   354  func (r conditionsImpl) MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{}) {
   355  	// set the specified condition
   356  	r.SetCondition(Condition{
   357  		Type:     t,
   358  		Status:   corev1.ConditionUnknown,
   359  		Reason:   reason,
   360  		Message:  fmt.Sprintf(messageFormat, messageA...),
   361  		Severity: r.severity(t),
   362  	})
   363  
   364  	// check the dependents.
   365  	isDependent := false
   366  	for _, cond := range r.dependents {
   367  		c := r.GetCondition(cond)
   368  		// Failed conditions trump Unknown conditions
   369  		if c.IsFalse() {
   370  			// Double check that the happy condition is also false.
   371  			happy := r.GetCondition(r.happy)
   372  			if !happy.IsFalse() {
   373  				r.MarkFalse(r.happy, reason, messageFormat, messageA...)
   374  			}
   375  			return
   376  		}
   377  		if cond == t {
   378  			isDependent = true
   379  		}
   380  	}
   381  
   382  	if isDependent {
   383  		// set the happy condition, if it is one of our dependent subconditions.
   384  		r.SetCondition(Condition{
   385  			Type:     r.happy,
   386  			Status:   corev1.ConditionUnknown,
   387  			Reason:   reason,
   388  			Message:  fmt.Sprintf(messageFormat, messageA...),
   389  			Severity: r.severity(r.happy),
   390  		})
   391  	}
   392  }
   393  
   394  // MarkFalse sets the status of t and the happy condition to False.
   395  func (r conditionsImpl) MarkFalse(t ConditionType, reason, messageFormat string, messageA ...interface{}) {
   396  	types := []ConditionType{t}
   397  	for _, cond := range r.dependents {
   398  		if cond == t {
   399  			types = append(types, r.happy)
   400  		}
   401  	}
   402  
   403  	for _, t := range types {
   404  		r.SetCondition(Condition{
   405  			Type:     t,
   406  			Status:   corev1.ConditionFalse,
   407  			Reason:   reason,
   408  			Message:  fmt.Sprintf(messageFormat, messageA...),
   409  			Severity: r.severity(t),
   410  		})
   411  	}
   412  }
   413  
   414  // InitializeConditions updates all Conditions in the ConditionSet to Unknown
   415  // if not set.
   416  func (r conditionsImpl) InitializeConditions() {
   417  	happy := r.GetCondition(r.happy)
   418  	if happy == nil {
   419  		happy = &Condition{
   420  			Type:     r.happy,
   421  			Status:   corev1.ConditionUnknown,
   422  			Severity: ConditionSeverityError,
   423  		}
   424  		r.SetCondition(*happy)
   425  	}
   426  	// If the happy state is true, it implies that all of the terminal
   427  	// subconditions must be true, so initialize any unset conditions to
   428  	// true if our happy condition is true, otherwise unknown.
   429  	status := corev1.ConditionUnknown
   430  	if happy.Status == corev1.ConditionTrue {
   431  		status = corev1.ConditionTrue
   432  	}
   433  	for _, t := range r.dependents {
   434  		r.initializeTerminalCondition(t, status)
   435  	}
   436  }
   437  
   438  // initializeTerminalCondition initializes a Condition to the given status if unset.
   439  func (r conditionsImpl) initializeTerminalCondition(t ConditionType, status corev1.ConditionStatus) *Condition {
   440  	if c := r.GetCondition(t); c != nil {
   441  		return c
   442  	}
   443  	c := Condition{
   444  		Type:     t,
   445  		Status:   status,
   446  		Severity: ConditionSeverityError,
   447  	}
   448  	r.SetCondition(c)
   449  	return &c
   450  }