mirror of https://github.com/knative/pkg.git
Start to lay the groundwork for Status conversion. (#373)
The backbone of our Condition system is our "happy" condition and the semantics governing how other subconditions influence that condition. When looking towards Conversion, it is possible for the set of conditions to vary between `v1alpha1` and `v1beta1`, but the "happy" condition should remain consistent across versions. The main changes: 1. Provide a `ConvertTo` helper for "converting" between `duckv1beta1.Status` types in this "lowest common denominator" sense, where we just copy the happy condition. 2. When `InitializeConditions()` (plural) is called, seed the initial state of sub-conditions from the initial state of the "happy" condition, if True. This change enables us to completely change the condition space across versions, while maintaining the consistency of the happy condition. A couple peripheral changes: 1. Add `context.Context` to the `apis.Convertible` interface. 2. Drop `InitializeCondition()` (singular) from the `ConditionsManager` interface (I don't see any usage outside of this file).
This commit is contained in:
parent
f55c11c3ce
commit
31649c272a
|
@ -74,9 +74,6 @@ type ConditionManager interface {
|
|||
// InitializeConditions updates all Conditions in the ConditionSet to Unknown
|
||||
// if not set.
|
||||
InitializeConditions()
|
||||
|
||||
// InitializeCondition updates a Condition to Unknown if not set.
|
||||
InitializeCondition(t ConditionType)
|
||||
}
|
||||
|
||||
// NewLivingConditionSet returns a ConditionSet to hold the conditions for the
|
||||
|
@ -302,19 +299,37 @@ func (r conditionsImpl) MarkFalse(t ConditionType, reason, messageFormat string,
|
|||
// InitializeConditions updates all Conditions in the ConditionSet to Unknown
|
||||
// if not set.
|
||||
func (r conditionsImpl) InitializeConditions() {
|
||||
for _, t := range r.dependents {
|
||||
r.InitializeCondition(t)
|
||||
happy := r.GetCondition(r.happy)
|
||||
if happy == nil {
|
||||
happy = &Condition{
|
||||
Type: r.happy,
|
||||
Status: corev1.ConditionUnknown,
|
||||
Severity: ConditionSeverityError,
|
||||
}
|
||||
r.SetCondition(*happy)
|
||||
}
|
||||
// If the happy state is true, it implies that all of the terminal
|
||||
// subconditions must be true, so initialize any unset conditions to
|
||||
// true if our happy condition is true, otherwise unknown.
|
||||
status := corev1.ConditionUnknown
|
||||
if happy.Status == corev1.ConditionTrue {
|
||||
status = corev1.ConditionTrue
|
||||
}
|
||||
for _, t := range r.dependents {
|
||||
r.initializeTerminalCondition(t, status)
|
||||
}
|
||||
r.InitializeCondition(r.happy)
|
||||
}
|
||||
|
||||
// InitializeCondition updates a Condition to Unknown if not set.
|
||||
func (r conditionsImpl) InitializeCondition(t ConditionType) {
|
||||
if c := r.GetCondition(t); c == nil {
|
||||
r.SetCondition(Condition{
|
||||
// initializeTerminalCondition initializes a Condition to the given status if unset.
|
||||
func (r conditionsImpl) initializeTerminalCondition(t ConditionType, status corev1.ConditionStatus) *Condition {
|
||||
if c := r.GetCondition(t); c != nil {
|
||||
return c
|
||||
}
|
||||
c := Condition{
|
||||
Type: t,
|
||||
Status: corev1.ConditionUnknown,
|
||||
Severity: r.severity(t),
|
||||
})
|
||||
Status: status,
|
||||
Severity: ConditionSeverityError,
|
||||
}
|
||||
r.SetCondition(c)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -805,3 +805,38 @@ func TestInitializeConditions(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminalInitialization(t *testing.T) {
|
||||
set := NewLivingConditionSet("Foo")
|
||||
status := &TestStatus{}
|
||||
|
||||
manager := set.Manage(status)
|
||||
manager.InitializeConditions()
|
||||
|
||||
if got, want := len(status.c), 2; got != want {
|
||||
t.Errorf("InitializeConditions() = %v, wanted %v", got, want)
|
||||
}
|
||||
|
||||
manager.MarkTrue("Foo")
|
||||
if !manager.IsHappy() {
|
||||
t.Error("IsHappy() = false, wanted true")
|
||||
}
|
||||
|
||||
// Add a new condition "Bar" to simulate the addition of conditions.
|
||||
set = NewLivingConditionSet("Foo", "Bar")
|
||||
|
||||
// Create a new manager for the new set and re-initialize to simulate
|
||||
// Reconcile() with the new conditions.
|
||||
manager = set.Manage(status)
|
||||
manager.InitializeConditions()
|
||||
|
||||
if got, want := len(status.c), 3; got != want {
|
||||
t.Errorf("InitializeConditions() = %v, wanted %v", got, want)
|
||||
}
|
||||
|
||||
if c := manager.GetCondition("Bar"); c == nil {
|
||||
t.Error("GetCondition(Bar) = nil, wanted True")
|
||||
} else if got, want := c.Status, corev1.ConditionTrue; got != want {
|
||||
t.Errorf("GetCondition(Bar) = %s, wanted %s", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package v1beta1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -96,6 +97,19 @@ func (s *Status) GetCondition(t apis.ConditionType) *apis.Condition {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ConvertTo helps implement apis.Convertible for types embedding this Status.
|
||||
func (source *Status) ConvertTo(ctx context.Context, sink *Status) {
|
||||
for _, c := range source.Conditions {
|
||||
switch c.Type {
|
||||
// Copy over the "happy" condition, which is the only condition that
|
||||
// we can reliably transfer.
|
||||
case apis.ConditionReady, apis.ConditionSucceeded:
|
||||
sink.SetConditions(apis.Conditions{c})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate implements duck.Populatable
|
||||
func (t *KResource) Populate() {
|
||||
t.Status.ObservedGeneration = 42
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package v1beta1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
@ -85,6 +86,15 @@ func TestConditionSet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
s2 := &Status{}
|
||||
s.ConvertTo(context.Background(), s2)
|
||||
if condSet.Manage(s2).IsHappy() {
|
||||
t.Error("s2.IsHappy() = true, wanted false")
|
||||
}
|
||||
if got, want := len(s2.Conditions), 1; got != want {
|
||||
t.Errorf("len(s2.Conditions) = %d, wanted %d", got, want)
|
||||
}
|
||||
|
||||
for _, c := range []apis.ConditionType{"Foo"} {
|
||||
mgr.MarkFalse(c, "bad", "for business")
|
||||
}
|
||||
|
@ -97,6 +107,15 @@ func TestConditionSet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
s2 = &Status{}
|
||||
s.ConvertTo(context.Background(), s2)
|
||||
if condSet.Manage(s2).IsHappy() {
|
||||
t.Error("s2.IsHappy() = true, wanted false")
|
||||
}
|
||||
if got, want := len(s2.Conditions), 1; got != want {
|
||||
t.Errorf("len(s2.Conditions) = %d, wanted %d", got, want)
|
||||
}
|
||||
|
||||
for _, c := range []apis.ConditionType{"Foo"} {
|
||||
mgr.MarkTrue(c)
|
||||
}
|
||||
|
@ -108,4 +127,13 @@ func TestConditionSet(t *testing.T) {
|
|||
t.Errorf("GetCondition(%q) = %v, wanted %v", c, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
s2 = &Status{}
|
||||
s.ConvertTo(context.Background(), s2)
|
||||
if !condSet.Manage(s2).IsHappy() {
|
||||
t.Error("s2.IsHappy() = false, wanted true")
|
||||
}
|
||||
if got, want := len(s2.Conditions), 1; got != want {
|
||||
t.Errorf("len(s2.Conditions) = %d, wanted %d", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,10 +38,10 @@ type Validatable interface {
|
|||
// "higher" versions of the same type.
|
||||
type Convertible interface {
|
||||
// ConvertUp up-converts the receiver into `to`.
|
||||
ConvertUp(to Convertible) error
|
||||
ConvertUp(ctx context.Context, to Convertible) error
|
||||
|
||||
// ConvertDown down-converts from `from` into the receiver.
|
||||
ConvertDown(from Convertible) error
|
||||
ConvertDown(ctx context.Context, from Convertible) error
|
||||
}
|
||||
|
||||
// Immutable indicates that a particular type has fields that should
|
||||
|
|
Loading…
Reference in New Issue