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 }