| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package api |
| |
| import ( |
| "fmt" |
| "reflect" |
| "sort" |
| "time" |
| |
| corev1 "k8s.io/api/core/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| ) |
| |
| // Based on https://github.com/knative/pkg/blob/980a33719a10024f45e44320316c5bd35cef18d6/apis/condition_set.go |
| |
| // Status ... |
| // +kubebuilder:object:generate=true |
| type Status struct { |
| // The latest available observations of a resource's current state. |
| // +optional |
| // +patchMergeKey=type |
| // +patchStrategy=merge |
| Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` |
| // The generation observed by the deployment controller. |
| // +optional |
| ObservedGeneration int64 `json:"observedGeneration,omitempty"` |
| } |
| |
| func (s *Status) GetConditions() Conditions { |
| return s.Conditions |
| } |
| |
| // GetCondition finds and returns the Condition that matches the ConditionType |
| // previously set on Conditions. |
| func (s *Status) GetCondition(t ConditionType) *Condition { |
| for _, c := range s.Conditions { |
| if c.Type == t { |
| return &c |
| } |
| } |
| return nil |
| } |
| |
| func (s *Status) String() string { |
| str := "" |
| for _, c := range s.Conditions { |
| str += c.String() + "\n" |
| } |
| return str |
| } |
| |
| func (s *Status) setConditions(c Conditions) { |
| s.Conditions = c |
| } |
| |
| // ConditionsReader gives read capability to Conditions. |
| type ConditionsReader interface { |
| GetConditions() Conditions |
| GetCondition(t ConditionType) *Condition |
| // setConditions overwrite the conditions in the Status instance. |
| // Private to not expose to client code. Writing to the conditions should be done via ConditionsManager |
| setConditions(c Conditions) |
| } |
| |
| // ConditionsAccessor describes access methods that every Status based struct implements. |
| type ConditionsAccessor interface { |
| ConditionsReader |
| GetTopLevelConditionType() ConditionType |
| IsReady() bool |
| GetTopLevelCondition() *Condition |
| } |
| |
| type ConditionsManager interface { |
| ClearCondition(t ConditionType) error |
| MarkTrue(t ConditionType) |
| MarkTrueWithReason(t ConditionType, reason, messageFormat string, messageA ...interface{}) |
| MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{}) |
| MarkFalse(t ConditionType, reason, messageFormat string, messageA ...interface{}) |
| InitializeConditions() |
| } |
| |
| var _ ConditionsManager = &conditionManager{} |
| |
| type conditionManager struct { |
| reader ConditionsReader |
| ready ConditionType |
| dependents []ConditionType |
| } |
| |
| func NewConditionManager(accessor ConditionsReader, ready ConditionType, dependents ...ConditionType) ConditionsManager { |
| return &conditionManager{ |
| reader: accessor, |
| ready: ready, |
| dependents: dependents, |
| } |
| } |
| |
| // setCondition sets or updates the Condition on Conditions for Condition.Type. |
| // If there is an update, Conditions are stored back sorted. |
| func (s *conditionManager) setCondition(cond Condition) { |
| if s.reader == nil { |
| return |
| } |
| t := cond.Type |
| var conditions Conditions |
| for _, c := range s.reader.GetConditions() { |
| if c.Type != t { |
| conditions = append(conditions, c) |
| } else { |
| // If we'd only update the LastTransitionTime, then return. |
| cond.LastUpdateTime = c.LastUpdateTime |
| if reflect.DeepEqual(cond, c) { |
| return |
| } |
| } |
| } |
| cond.LastUpdateTime = metav1.NewTime(time.Now()) |
| conditions = append(conditions, cond) |
| // Sorted for convenience of the consumer, i.e. kubectl. |
| sort.Slice(conditions, func(i, j int) bool { return conditions[i].Type < conditions[j].Type }) |
| s.reader.setConditions(conditions) |
| } |
| |
| func (s *conditionManager) isTerminal(t ConditionType) bool { |
| for _, cond := range s.dependents { |
| if cond == t { |
| return true |
| } |
| } |
| return t == s.ready |
| } |
| |
| // ClearCondition removes the non-terminal condition that matches the ConditionType |
| // Not implemented for terminal conditions |
| func (s *conditionManager) ClearCondition(t ConditionType) error { |
| var conditions Conditions |
| |
| if s.reader == nil { |
| return nil |
| } |
| // Terminal conditions are not handled as they can't be nil |
| if s.isTerminal(t) { |
| return fmt.Errorf("clearing terminal conditions not implemented") |
| } |
| cond := s.reader.GetCondition(t) |
| if cond == nil { |
| return nil |
| } |
| for _, c := range s.reader.GetConditions() { |
| if c.Type != t { |
| conditions = append(conditions, c) |
| } |
| } |
| |
| // Sorted for convenience of the consumer, i.e. kubectl. |
| sort.Slice(conditions, func(i, j int) bool { return conditions[i].Type < conditions[j].Type }) |
| s.reader.setConditions(conditions) |
| |
| return nil |
| } |
| |
| // MarkTrue sets the status of t to true |
| func (s *conditionManager) MarkTrue(t ConditionType) { |
| // Set the specified condition. |
| s.setCondition(Condition{ |
| Type: t, |
| Status: corev1.ConditionTrue, |
| }) |
| } |
| |
| // MarkTrueWithReason sets the status of t to true with the reason |
| func (s *conditionManager) MarkTrueWithReason(t ConditionType, reason, messageFormat string, messageA ...interface{}) { |
| // set the specified condition |
| s.setCondition(Condition{ |
| Type: t, |
| Status: corev1.ConditionTrue, |
| Reason: reason, |
| Message: fmt.Sprintf(messageFormat, messageA...), |
| }) |
| } |
| |
| func (s *conditionManager) findUnreadyDependent() *Condition { |
| // Do not modify the accessors condition order. |
| conditions := s.reader.GetConditions().DeepCopy() |
| |
| // Filter based on terminal status. |
| n := 0 |
| for _, c := range conditions { |
| if c.Type != s.ready { |
| conditions[n] = c |
| n++ |
| } |
| } |
| conditions = conditions[:n] |
| |
| // Sort set conditions by time. |
| sort.Slice(conditions, func(i, j int) bool { |
| return conditions[i].LastUpdateTime.Time.After(conditions[j].LastUpdateTime.Time) |
| }) |
| |
| // First check the conditions with Status == False. |
| for _, c := range conditions { |
| // False conditions trump Unknown. |
| if c.IsFalse() { |
| return &c |
| } |
| } |
| // Second check for conditions with Status == Unknown. |
| for _, c := range conditions { |
| if c.IsUnknown() { |
| return &c |
| } |
| } |
| |
| // If something was not initialized. |
| if len(s.dependents) > len(conditions) { |
| return &Condition{ |
| Status: corev1.ConditionUnknown, |
| } |
| } |
| |
| // All dependents are fine. |
| return nil |
| } |
| |
| // MarkUnknown sets the status of t to Unknown and also sets the ready condition |
| // to Unknown if no other dependent condition is in an error state. |
| func (s *conditionManager) MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{}) { |
| // set the specified condition |
| s.setCondition(Condition{ |
| Type: t, |
| Status: corev1.ConditionUnknown, |
| Reason: reason, |
| Message: fmt.Sprintf(messageFormat, messageA...), |
| }) |
| |
| // check the dependents. |
| isDependent := false |
| for _, cond := range s.dependents { |
| c := s.reader.GetCondition(cond) |
| // Failed conditions trump Unknown conditions |
| if c.IsFalse() { |
| // Double check that the ready condition is also false. |
| ready := s.reader.GetCondition(s.ready) |
| if !ready.IsFalse() { |
| s.MarkFalse(s.ready, reason, messageFormat, messageA...) |
| } |
| return |
| } |
| if cond == t { |
| isDependent = true |
| } |
| } |
| |
| if isDependent { |
| // set the ready condition, if it is one of our dependent subconditions. |
| s.setCondition(Condition{ |
| Type: s.ready, |
| Status: corev1.ConditionUnknown, |
| Reason: reason, |
| Message: fmt.Sprintf(messageFormat, messageA...), |
| }) |
| } |
| } |
| |
| // MarkFalse sets the status of t and the ready condition to False. |
| func (s *conditionManager) MarkFalse(t ConditionType, reason, messageFormat string, messageA ...interface{}) { |
| s.setCondition(Condition{ |
| Type: t, |
| Status: corev1.ConditionFalse, |
| Reason: reason, |
| Message: fmt.Sprintf(messageFormat, messageA...), |
| }) |
| } |
| |
| // InitializeConditions updates all Conditions in the ConditionSet to Unknown |
| // if not set. |
| func (s *conditionManager) InitializeConditions() { |
| ready := s.reader.GetCondition(s.ready) |
| if ready == nil { |
| ready = &Condition{ |
| Type: s.ready, |
| Status: corev1.ConditionUnknown, |
| } |
| s.setCondition(*ready) |
| } |
| // If the ready state is true, it implies that all of the terminal |
| // subconditions must be true, so initialize any unset conditions to |
| // true if our ready condition is true, otherwise unknown. |
| status := corev1.ConditionUnknown |
| if ready.Status == corev1.ConditionTrue { |
| status = corev1.ConditionTrue |
| } |
| for _, t := range s.dependents { |
| s.initializeTerminalCondition(t, status) |
| } |
| } |
| |
| // initializeTerminalCondition initializes a Condition to the given status if unset. |
| func (s *conditionManager) initializeTerminalCondition(t ConditionType, status corev1.ConditionStatus) *Condition { |
| if c := s.reader.GetCondition(t); c != nil { |
| return c |
| } |
| c := Condition{ |
| Type: t, |
| Status: status, |
| } |
| s.setCondition(c) |
| return &c |
| } |