mirror of https://github.com/knative/pkg.git
1170 lines
29 KiB
Go
1170 lines
29 KiB
Go
/*
|
|
Copyright 2019 The Knative Authors
|
|
|
|
Licensed 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 apis
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
// TestStatus is to validate ConditionAccessor interface works
|
|
type TestStatus struct {
|
|
c Conditions
|
|
}
|
|
|
|
func (t *TestStatus) GetConditions() Conditions {
|
|
return t.c
|
|
}
|
|
|
|
func (t *TestStatus) SetConditions(conditions Conditions) {
|
|
t.c = conditions
|
|
}
|
|
|
|
var ignoreFields = cmpopts.IgnoreFields(Condition{}, "LastTransitionTime", "Severity")
|
|
|
|
func TestGetCondition(t *testing.T) {
|
|
condSet := NewLivingConditionSet()
|
|
cases := []struct {
|
|
name string
|
|
status ConditionsAccessor
|
|
get ConditionType
|
|
expect *Condition
|
|
}{{
|
|
name: "simple",
|
|
status: &TestStatus{c: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}}},
|
|
get: ConditionReady,
|
|
expect: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}, {
|
|
name: "nil",
|
|
status: nil,
|
|
get: ConditionReady,
|
|
expect: nil,
|
|
}, {
|
|
name: "missing",
|
|
status: &TestStatus{c: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}}},
|
|
get: "Missing",
|
|
expect: nil,
|
|
}}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
e, a := tc.expect, condSet.Manage(tc.status).GetCondition(tc.get)
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetCondition(t *testing.T) {
|
|
condSet := NewLivingConditionSet()
|
|
cases := []struct {
|
|
name string
|
|
status ConditionsAccessor
|
|
set Condition
|
|
expect *Condition
|
|
}{{
|
|
name: "simple",
|
|
status: &TestStatus{c: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}}},
|
|
set: Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
expect: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}, {
|
|
name: "nil",
|
|
status: nil,
|
|
set: Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
expect: nil,
|
|
}, {
|
|
name: "empty",
|
|
status: &TestStatus{},
|
|
set: Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
expect: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
condSet.Manage(tc.status).SetCondition(tc.set)
|
|
e, a := tc.expect, condSet.Manage(tc.status).GetCondition(tc.set.Type)
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsHappy(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
status ConditionsAccessor
|
|
condSet ConditionSet
|
|
isHappy bool
|
|
}{{
|
|
name: "empty accessor should not be ready",
|
|
status: &TestStatus{
|
|
c: Conditions(nil),
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}, {
|
|
name: "Different condition type should not be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}, {
|
|
name: "False condition accessor should not be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}, {
|
|
name: "Unknown condition accessor should not be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}, {
|
|
name: "Missing condition accessor should not be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: ConditionReady,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}, {
|
|
name: "True condition accessor should be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: true,
|
|
}, {
|
|
name: "Multiple conditions with ready accessor should be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: true,
|
|
}, {
|
|
name: "Multiple conditions with ready accessor false should not be ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}, {
|
|
name: "Multiple conditions with mixed ready accessor, some don't matter, ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionFalse,
|
|
}, {
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: true,
|
|
}, {
|
|
name: "Multiple conditions with mixed ready accessor, some don't matter, not ready",
|
|
status: &TestStatus{
|
|
c: Conditions{{
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
},
|
|
condSet: NewLivingConditionSet(),
|
|
isHappy: false,
|
|
}}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if e, a := tc.isHappy, tc.condSet.Manage(tc.status).IsHappy(); e != a {
|
|
t.Errorf("%q expected: %v got: %v", tc.name, e, a)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateLastTransitionTime(t *testing.T) {
|
|
condSet := NewLivingConditionSet()
|
|
|
|
cases := []struct {
|
|
name string
|
|
conditions Conditions
|
|
condition Condition
|
|
update bool
|
|
}{{
|
|
name: "LastTransitionTime should be set",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
|
|
condition: Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
update: true,
|
|
}, {
|
|
name: "LastTransitionTime should update",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
LastTransitionTime: VolatileTime{metav1.NewTime(time.Unix(1337, 0))},
|
|
}},
|
|
condition: Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
update: true,
|
|
}, {
|
|
name: "if LastTransitionTime is the only chance, don't do it",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
LastTransitionTime: VolatileTime{metav1.NewTime(time.Unix(1337, 0))},
|
|
}},
|
|
|
|
condition: Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
update: false,
|
|
}}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
conds := &TestStatus{c: tc.conditions}
|
|
|
|
was := condSet.Manage(conds).GetCondition(tc.condition.Type)
|
|
condSet.Manage(conds).SetCondition(tc.condition)
|
|
now := condSet.Manage(conds).GetCondition(tc.condition.Type)
|
|
|
|
if e, a := tc.condition.Status, now.Status; e != a {
|
|
t.Errorf("%q expected: %v to match %v", tc.name, e, a)
|
|
}
|
|
|
|
if tc.update {
|
|
if e, a := was.LastTransitionTime, now.LastTransitionTime; e == a {
|
|
t.Errorf("%q expected: %v to not match %v", tc.name, e, a)
|
|
}
|
|
} else {
|
|
if e, a := was.LastTransitionTime, now.LastTransitionTime; e != a {
|
|
t.Errorf("%q expected: %v to match %v", tc.name, e, a)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResourceConditions(t *testing.T) {
|
|
condSet := NewLivingConditionSet()
|
|
|
|
status := &TestStatus{}
|
|
|
|
foo := Condition{
|
|
Type: "Foo",
|
|
Status: "True",
|
|
}
|
|
bar := Condition{
|
|
Type: "Bar",
|
|
Status: "True",
|
|
}
|
|
|
|
// Add a new condition.
|
|
condSet.Manage(status).SetCondition(foo)
|
|
|
|
if got, want := len(status.c), 1; got != want {
|
|
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
|
|
}
|
|
|
|
// Add a second condition.
|
|
condSet.Manage(status).SetCondition(bar)
|
|
|
|
if got, want := len(status.c), 2; got != want {
|
|
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestConditionSeverity(t *testing.T) {
|
|
condSet := NewLivingConditionSet("Foo")
|
|
status := &TestStatus{}
|
|
|
|
// Add a new condition.
|
|
condSet.Manage(status).InitializeConditions()
|
|
|
|
if got, want := len(status.c), 2; got != want {
|
|
t.Errorf("Unexpected number of conditions: %d, wanted %d", got, want)
|
|
}
|
|
|
|
condSet.Manage(status).MarkFalse("Bar", "", "")
|
|
|
|
if got, want := len(status.c), 3; got != want {
|
|
t.Errorf("Unexpected number of conditions: %d, wanted %d", got, want)
|
|
}
|
|
|
|
if got, want := condSet.Manage(status).GetCondition("Ready").Severity, ConditionSeverityError; got != want {
|
|
t.Errorf("GetCondition(%q).Severity = %v, wanted %v", "Ready", got, want)
|
|
}
|
|
|
|
if got, want := condSet.Manage(status).GetCondition("Foo").Severity, ConditionSeverityError; got != want {
|
|
t.Errorf("GetCondition(%q).Severity = %v, wanted %v", "Foo", got, want)
|
|
}
|
|
|
|
if got, want := condSet.Manage(status).GetCondition("Bar").Severity, ConditionSeverityInfo; got != want {
|
|
t.Errorf("GetCondition(%q).Severity = %v, wanted %v", "Bar", got, want)
|
|
}
|
|
}
|
|
|
|
// getTypes is a small helped to strip out the used ConditionTypes from Conditions
|
|
func getTypes(conds Conditions) []ConditionType {
|
|
types := make([]ConditionType, 0, len(conds))
|
|
for _, c := range conds {
|
|
types = append(types, c.Type)
|
|
}
|
|
return types
|
|
}
|
|
|
|
type ConditionMarkTrueTest struct {
|
|
name string
|
|
conditions Conditions
|
|
conditionTypes []ConditionType
|
|
mark ConditionType
|
|
happy bool
|
|
happyWant *Condition
|
|
}
|
|
|
|
func doTestMarkTrueAccessor(t *testing.T, cases []ConditionMarkTrueTest) {
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
conditionTypes := tc.conditionTypes
|
|
if conditionTypes == nil {
|
|
conditionTypes = getTypes(tc.conditions)
|
|
}
|
|
condSet := NewLivingConditionSet(conditionTypes...)
|
|
status := &TestStatus{c: tc.conditions}
|
|
condSet.Manage(status).InitializeConditions()
|
|
|
|
condSet.Manage(status).MarkTrue(tc.mark)
|
|
|
|
if e, a := tc.happy, condSet.Manage(status).IsHappy(); e != a {
|
|
t.Errorf("%q expected: %v got: %v", tc.name, e, a)
|
|
} else if !e && tc.happyWant != nil {
|
|
e, a := tc.happyWant, condSet.Manage(status).GetTopLevelCondition()
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
}
|
|
|
|
if tc.mark == condSet.happy {
|
|
// Skip validation the happy condition because we can't be sure
|
|
// marking it true was correct. Use tc.happyWant to test that case.
|
|
return
|
|
}
|
|
|
|
expected := &Condition{
|
|
Type: tc.mark,
|
|
Status: corev1.ConditionTrue,
|
|
}
|
|
|
|
e, a := expected, condSet.Manage(status).GetCondition(tc.mark)
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
})
|
|
// Run same test with MarkTrueWithReason
|
|
t.Run(tc.name+" with reason", func(t *testing.T) {
|
|
conditionTypes := tc.conditionTypes
|
|
if conditionTypes == nil {
|
|
conditionTypes = getTypes(tc.conditions)
|
|
}
|
|
condSet := NewLivingConditionSet(conditionTypes...)
|
|
status := &TestStatus{c: tc.conditions}
|
|
condSet.Manage(status).InitializeConditions()
|
|
|
|
condSet.Manage(status).MarkTrueWithReason(tc.mark, "UnitTest", "calm down, just testing")
|
|
|
|
if e, a := tc.happy, condSet.Manage(status).IsHappy(); e != a {
|
|
t.Errorf("%q expected: %v got: %v", tc.name, e, a)
|
|
} else if !e && tc.happyWant != nil {
|
|
e, a := tc.happyWant, condSet.Manage(status).GetTopLevelCondition()
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
}
|
|
|
|
if tc.mark == condSet.happy {
|
|
// Skip validation the happy condition because we can't be sure
|
|
// marking it true was correct. Use tc.happyWant to test that case.
|
|
return
|
|
}
|
|
|
|
expected := &Condition{
|
|
Type: tc.mark,
|
|
Status: corev1.ConditionTrue,
|
|
Reason: "UnitTest",
|
|
Message: "calm down, just testing",
|
|
}
|
|
|
|
e, a := expected, condSet.Manage(status).GetCondition(tc.mark)
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarkTrue(t *testing.T) {
|
|
cases := []ConditionMarkTrueTest{{
|
|
name: "no deps",
|
|
mark: ConditionReady,
|
|
happy: true,
|
|
}, {
|
|
name: "existing conditions, turns happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: ConditionReady,
|
|
happy: true,
|
|
}, {
|
|
name: "with deps, happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: ConditionReady,
|
|
happy: true,
|
|
}, {
|
|
name: "with deps, not happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "ReadyReason",
|
|
Message: "ReadyMsg",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}},
|
|
mark: ConditionReady,
|
|
happy: false,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
},
|
|
}, {
|
|
name: "update dep, turns happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: "Foo",
|
|
happy: true,
|
|
}, {
|
|
name: "update dep, happy was unknown, turns happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: "Foo",
|
|
happy: true,
|
|
}, {
|
|
name: "update dep 1/2, still not happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
}},
|
|
mark: "Foo",
|
|
happy: false,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
},
|
|
}, {
|
|
name: "update dep 1/3, mixed status, still not happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
}, {
|
|
Type: "Baz",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "BazReason",
|
|
Message: "BazMsg",
|
|
}},
|
|
mark: "Foo",
|
|
happy: false,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "BazReason",
|
|
Message: "BazMsg",
|
|
},
|
|
}, {
|
|
name: "update dep 1/3, unknown status, still not happy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
}, {
|
|
Type: "Baz",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BazReason",
|
|
Message: "BazMsg",
|
|
}},
|
|
mark: "Foo",
|
|
happy: false,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
},
|
|
}, {
|
|
name: "update dep 1/3, unknown status because nil",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}},
|
|
mark: "Foo",
|
|
conditionTypes: []ConditionType{"Foo", "Bar", "Baz"},
|
|
happy: false,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
},
|
|
}, {
|
|
name: "update deps all happy with extra cruft",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
Severity: "FYI",
|
|
}, {
|
|
Type: "Baz",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BazReason",
|
|
Message: "BazMsg",
|
|
Severity: "LOLJK",
|
|
}},
|
|
mark: "Foo",
|
|
conditionTypes: []ConditionType{"Foo"},
|
|
happy: true,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}, {
|
|
name: "with no dependents with extra cruft",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "LongStory",
|
|
Message: "Set manually",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "FooReason",
|
|
Message: "FooMsg",
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
Severity: "FYI",
|
|
}, {
|
|
Type: "Baz",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BazReason",
|
|
Message: "BazMsg",
|
|
Severity: "LOLJK",
|
|
}},
|
|
mark: "Foo",
|
|
conditionTypes: []ConditionType{},
|
|
happy: true,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}, {
|
|
name: "happy dependents with extra cruft",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "LongStory",
|
|
Message: "Set manually",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BarReason",
|
|
Message: "BarMsg",
|
|
Severity: "FYI",
|
|
}, {
|
|
Type: "Baz",
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "BazReason",
|
|
Message: "BazMsg",
|
|
Severity: "LOLJK",
|
|
}},
|
|
mark: "Bar",
|
|
conditionTypes: []ConditionType{"Foo"},
|
|
happy: true,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}, {
|
|
name: "all happy but not cover all dependents",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "LongStory",
|
|
Message: "Set manually",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: "Foo",
|
|
conditionTypes: []ConditionType{"Foo", "Bar"}, // dependents is more than conditions.
|
|
happy: false,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
},
|
|
}, {
|
|
name: "all happy and cover all dependents",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "LongStory",
|
|
Message: "Set manually",
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "NewCondition",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: "Foo",
|
|
conditionTypes: []ConditionType{"Foo"}, // dependents is less than conditions.
|
|
happy: true,
|
|
happyWant: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}}
|
|
doTestMarkTrueAccessor(t, cases)
|
|
}
|
|
|
|
type ConditionMarkFalseTest struct {
|
|
name string
|
|
conditions Conditions
|
|
mark ConditionType
|
|
unhappy bool
|
|
}
|
|
|
|
func doTestMarkFalseAccessor(t *testing.T, cases []ConditionMarkFalseTest) {
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
condSet := NewLivingConditionSet(getTypes(tc.conditions)...)
|
|
status := &TestStatus{c: tc.conditions}
|
|
condSet.Manage(status).InitializeConditions()
|
|
|
|
condSet.Manage(status).MarkFalse(tc.mark, "UnitTest", "calm down, just testing")
|
|
|
|
if e, a := !tc.unhappy, condSet.Manage(status).IsHappy(); e != a {
|
|
t.Errorf("%q expected: %v got: %v", tc.name, e, a)
|
|
}
|
|
|
|
expected := &Condition{
|
|
Type: tc.mark,
|
|
Status: corev1.ConditionFalse,
|
|
Reason: "UnitTest",
|
|
Message: "calm down, just testing",
|
|
}
|
|
|
|
e, a := expected, condSet.Manage(status).GetCondition(tc.mark)
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarkFalse(t *testing.T) {
|
|
cases := []ConditionMarkFalseTest{{
|
|
name: "no deps",
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
}, {
|
|
name: "existing conditions, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
}, {
|
|
name: "with deps, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
}, {
|
|
name: "with deps, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
}, {
|
|
name: "update dep, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
}, {
|
|
name: "update dep, happy was unknown, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
}, {
|
|
name: "update dep 1/2, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
}}
|
|
doTestMarkFalseAccessor(t, cases)
|
|
}
|
|
|
|
type ConditionMarkUnknownTest struct {
|
|
name string
|
|
conditions Conditions
|
|
mark ConditionType
|
|
unhappy bool
|
|
happyIs corev1.ConditionStatus
|
|
}
|
|
|
|
func doTestMarkUnknownAccessor(t *testing.T, cases []ConditionMarkUnknownTest) {
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
condSet := NewLivingConditionSet(getTypes(tc.conditions)...)
|
|
status := &TestStatus{c: tc.conditions}
|
|
|
|
condSet.Manage(status).MarkUnknown(tc.mark, "UnitTest", "idk, just testing")
|
|
|
|
if e, a := !tc.unhappy, condSet.Manage(status).IsHappy(); e != a {
|
|
t.Errorf("%q expected IsHappy: %v got: %v", tc.name, e, a)
|
|
}
|
|
|
|
if e, a := tc.happyIs, condSet.Manage(status).GetCondition(ConditionReady).Status; e != a {
|
|
t.Errorf("%q expected ConditionReady: %v got: %v", tc.name, e, a)
|
|
}
|
|
|
|
expected := &Condition{
|
|
Type: tc.mark,
|
|
Status: corev1.ConditionUnknown,
|
|
Reason: "UnitTest",
|
|
Message: "idk, just testing",
|
|
}
|
|
|
|
e, a := expected, condSet.Manage(status).GetCondition(tc.mark)
|
|
if diff := cmp.Diff(e, a, ignoreFields); diff != "" {
|
|
t.Errorf("%s (-want, +got) = %v", tc.name, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarkUnknown(t *testing.T) {
|
|
cases := []ConditionMarkUnknownTest{{
|
|
name: "no deps",
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionUnknown,
|
|
}, {
|
|
name: "existing conditions, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionUnknown,
|
|
}, {
|
|
name: "with deps, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: ConditionReady,
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionUnknown,
|
|
}, {
|
|
name: "with deps that are false, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionFalse,
|
|
}, {
|
|
name: "update dep, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionUnknown,
|
|
}, {
|
|
name: "update dep, happy was unknown, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionFalse,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionUnknown,
|
|
}, {
|
|
name: "update dep 1/2, turns unhappy",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Foo",
|
|
Status: corev1.ConditionTrue,
|
|
}, {
|
|
Type: "Bar",
|
|
Status: corev1.ConditionTrue,
|
|
}},
|
|
mark: "Foo",
|
|
unhappy: true,
|
|
happyIs: corev1.ConditionUnknown,
|
|
}}
|
|
doTestMarkUnknownAccessor(t, cases)
|
|
}
|
|
|
|
func TestInitializeConditions(t *testing.T) {
|
|
condSet := NewLivingConditionSet()
|
|
|
|
cases := []struct {
|
|
name string
|
|
conditions Conditions
|
|
want *Condition
|
|
}{{
|
|
name: "no conditions is initialized",
|
|
want: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
Severity: ConditionSeverityError,
|
|
},
|
|
}, {
|
|
name: "initialization is idempotent",
|
|
conditions: Conditions{{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
Severity: ConditionSeverityError,
|
|
}},
|
|
want: &Condition{
|
|
Type: ConditionReady,
|
|
Status: corev1.ConditionUnknown,
|
|
Severity: ConditionSeverityError,
|
|
},
|
|
// TODO(#357): Uncomment once fixed.
|
|
// }, {
|
|
// name: "initialization overwrites existing",
|
|
// conditions: Conditions{{
|
|
// Type: ConditionReady,
|
|
// Status: corev1.ConditionFalse,
|
|
// Severity: ConditionSeverityError,
|
|
// }},
|
|
// want: &Condition{
|
|
// Type: ConditionReady,
|
|
// Status: corev1.ConditionUnknown,
|
|
// Severity: ConditionSeverityError,
|
|
// },
|
|
}}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
status := &TestStatus{c: tc.conditions}
|
|
condSet.Manage(status).InitializeConditions()
|
|
if e, a := tc.want, condSet.Manage(status).GetCondition(ConditionReady); !equality.Semantic.DeepEqual(e, a) {
|
|
t.Errorf("accessor, %q expected: %v got: %v", tc.name, e, a)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func TestRemoveNonTerminalConditions(t *testing.T) {
|
|
set := NewLivingConditionSet("Foo")
|
|
status := &TestStatus{}
|
|
|
|
manager := set.Manage(status)
|
|
manager.MarkTrue("Foo")
|
|
manager.MarkTrue("Bar")
|
|
|
|
if got, want := len(status.c), 3; got != want {
|
|
t.Errorf("Marking true() = %v, wanted %v", got, want)
|
|
}
|
|
|
|
if !manager.IsHappy() {
|
|
t.Error("IsHappy() = false, wanted true")
|
|
}
|
|
|
|
err := manager.ClearCondition("Bar")
|
|
if err != nil {
|
|
t.Error("Clear condition should not return err", err)
|
|
}
|
|
if got, want := len(status.c), 2; got != want {
|
|
t.Errorf("Marking true() = %v, wanted %v", got, want)
|
|
}
|
|
|
|
if !manager.IsHappy() {
|
|
t.Error("IsHappy() = false, wanted true")
|
|
}
|
|
}
|
|
|
|
func TestClearConditionWithNilManager(t *testing.T) {
|
|
set := NewLivingConditionSet("Foo")
|
|
manager := set.Manage(nil)
|
|
|
|
err := manager.ClearCondition("Bar")
|
|
if err != nil {
|
|
t.Error("ClearCondition() expected to return nil if status is nil, got", err)
|
|
}
|
|
}
|