...

Source file src/edge-infra.dev/pkg/k8s/runtime/conditions/getter_test.go

Documentation: edge-infra.dev/pkg/k8s/runtime/conditions

     1  package conditions
     2  
     3  import (
     4  	"testing"
     5  
     6  	fuzz "github.com/AdaLogics/go-fuzz-headers"
     7  	. "github.com/onsi/gomega" //nolint:revive // TODO(aw185176): remove
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  
    10  	"edge-infra.dev/pkg/k8s/meta/status"
    11  	"edge-infra.dev/pkg/k8s/object/fobject"
    12  )
    13  
    14  var (
    15  	nil1            *metav1.Condition
    16  	true1           = TrueCondition("true1", "reason true1", "message true1")
    17  	unknown1        = UnknownCondition("unknown1", "reason unknown1", "message unknown1")
    18  	false1          = FalseCondition("false1", "reason false1", "message false1")
    19  	readyTrue       = TrueCondition(status.ReadyCondition, "reason readyTrue", "message readyTrue")
    20  	readyFalse      = FalseCondition(status.ReadyCondition, "reason readyFalse", "message readyFalse")
    21  	stalledTrue     = TrueCondition(status.StalledCondition, "reason stalledTrue", "message stalledTrue")
    22  	stalledFalse    = FalseCondition(status.StalledCondition, "reason stalledFalse", "message stalledFalse")
    23  	reconcilingTrue = TrueCondition(status.ReconcilingCondition, "reason reconcilingTrue", "message reconcilingTrue")
    24  )
    25  
    26  func TestGetAndHas(t *testing.T) {
    27  	g := NewWithT(t)
    28  
    29  	obj := &fobject.Fake{}
    30  
    31  	g.Expect(Has(obj, "conditionBaz")).To(BeFalse())
    32  	g.Expect(Get(obj, "conditionBaz")).To(BeNil())
    33  
    34  	obj.SetConditions(conditionList(TrueCondition("conditionBaz", "", "")))
    35  
    36  	g.Expect(Has(obj, "conditionBaz")).To(BeTrue())
    37  	g.Expect(Get(obj, "conditionBaz")).To(HaveSameStateOf(TrueCondition("conditionBaz", "", "")))
    38  
    39  	Set(obj, FalseCondition("conditionFoo", "", ""))
    40  	g.Expect(HasAny(obj, []string{"conditionFoo", "conditionX"})).To(BeTrue())
    41  	g.Expect(HasAny(obj, []string{"conditionY", "conditionBaz"})).To(BeTrue())
    42  	g.Expect(HasAny(obj, []string{"conditionX", "conditionY"})).To(BeFalse())
    43  }
    44  
    45  func TestIsMethods(t *testing.T) {
    46  	g := NewWithT(t)
    47  
    48  	false2 := false1.DeepCopy()
    49  	false2.Type = "false2"
    50  	false2.ObservedGeneration = 1
    51  
    52  	obj := getterWithConditions(nil1, true1, unknown1, false1, false2)
    53  
    54  	// test isTrue
    55  	g.Expect(IsTrue(obj, "nil1")).To(BeFalse())
    56  	g.Expect(IsTrue(obj, "true1")).To(BeTrue())
    57  	g.Expect(IsTrue(obj, "false1")).To(BeFalse())
    58  	g.Expect(IsTrue(obj, "unknown1")).To(BeFalse())
    59  
    60  	// test isFalse
    61  	g.Expect(IsFalse(obj, "nil1")).To(BeFalse())
    62  	g.Expect(IsFalse(obj, "true1")).To(BeFalse())
    63  	g.Expect(IsFalse(obj, "false1")).To(BeTrue())
    64  	g.Expect(IsFalse(obj, "unknown1")).To(BeFalse())
    65  
    66  	// test isUnknown
    67  	g.Expect(IsUnknown(obj, "nil1")).To(BeTrue())
    68  	g.Expect(IsUnknown(obj, "true1")).To(BeFalse())
    69  	g.Expect(IsUnknown(obj, "false1")).To(BeFalse())
    70  	g.Expect(IsUnknown(obj, "unknown1")).To(BeTrue())
    71  
    72  	// test GetReason
    73  	g.Expect(GetReason(obj, "nil1")).To(Equal(""))
    74  	g.Expect(GetReason(obj, "false1")).To(Equal("reason false1"))
    75  
    76  	// test GetMessage
    77  	g.Expect(GetMessage(obj, "nil1")).To(Equal(""))
    78  	g.Expect(GetMessage(obj, "false1")).To(Equal("message false1"))
    79  
    80  	// test GetLastTransitionTime
    81  	g.Expect(GetLastTransitionTime(obj, "nil1")).To(BeNil())
    82  	g.Expect(GetLastTransitionTime(obj, "false1")).ToNot(BeNil())
    83  
    84  	// test GetObservedGeneration
    85  	g.Expect(GetObservedGeneration(obj, "nil1")).To(BeZero())
    86  	g.Expect(GetObservedGeneration(obj, "false2")).ToNot(BeZero())
    87  }
    88  
    89  func TestIsReadyStalledReconciling(t *testing.T) {
    90  	g := NewWithT(t)
    91  
    92  	readyObj := getterWithConditions(readyTrue, stalledFalse)
    93  	stalledObj := getterWithConditions(stalledTrue, readyFalse)
    94  	reconcilingObj := getterWithConditions(reconcilingTrue, stalledFalse)
    95  
    96  	// test IsReady
    97  	g.Expect(IsReady(readyObj)).To(BeTrue())
    98  	g.Expect(IsReady(stalledObj)).To(BeFalse())
    99  	g.Expect(IsReady(reconcilingObj)).To(BeFalse())
   100  
   101  	// test IsStalled
   102  	g.Expect(IsStalled(stalledObj)).To(BeTrue())
   103  	g.Expect(IsStalled(readyObj)).To(BeFalse())
   104  	g.Expect(IsStalled(reconcilingObj)).To(BeFalse())
   105  
   106  	// test IsReconciling
   107  	g.Expect(IsReconciling(reconcilingObj)).To(BeTrue())
   108  	g.Expect(IsReconciling(stalledObj)).To(BeFalse())
   109  	g.Expect(IsReconciling(readyObj)).To(BeFalse())
   110  }
   111  
   112  func TestMirror(t *testing.T) {
   113  	foo := FalseCondition("foo", "reason foo", "message foo")
   114  	ready := TrueCondition(status.ReadyCondition, "reason ready", "message ready")
   115  	readyBar := ready.DeepCopy()
   116  	readyBar.Type = "bar"
   117  
   118  	tests := []struct {
   119  		name string
   120  		from Getter
   121  		t    string
   122  		want *metav1.Condition
   123  	}{
   124  		{
   125  			name: "Returns nil when the ready condition does not exists",
   126  			from: getterWithConditions(foo),
   127  			want: nil,
   128  		},
   129  		{
   130  			name: "Returns ready condition from source",
   131  			from: getterWithConditions(ready, foo),
   132  			t:    "bar",
   133  			want: readyBar,
   134  		},
   135  	}
   136  
   137  	for _, tt := range tests {
   138  		t.Run(tt.name, func(t *testing.T) {
   139  			g := NewWithT(t)
   140  
   141  			got := mirror(tt.from, tt.t)
   142  			if tt.want == nil {
   143  				g.Expect(got).To(BeNil())
   144  				return
   145  			}
   146  			g.Expect(got).To(HaveSameStateOf(tt.want))
   147  		})
   148  	}
   149  }
   150  
   151  func TestSummary(t *testing.T) {
   152  	foo := TrueCondition("foo", "reason trueFoo", "message trueFoo")
   153  	bar := FalseCondition("bar", "reason falseBar", "message falseBar")
   154  	baz := FalseCondition("baz", "reason falseBaz", "message falseBaz")
   155  	existingReady := FalseCondition(status.ReadyCondition, "reason falseReady", "message falseReady") //NB. existing ready has higher priority than other conditions
   156  
   157  	tests := []struct {
   158  		name    string
   159  		from    Getter
   160  		options []MergeOption
   161  		want    *metav1.Condition
   162  	}{
   163  		{
   164  			name: "Returns nil when there are no conditions to summarize",
   165  			from: getterWithConditions(),
   166  			want: nil,
   167  		},
   168  		{
   169  			name: "Returns ready condition with the summary of existing conditions (with default options)",
   170  			from: getterWithConditions(foo, bar),
   171  			want: FalseCondition(status.ReadyCondition, "reason falseBar", "message falseBar"),
   172  		},
   173  		{
   174  			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounter options)",
   175  			from:    getterWithConditions(foo, bar),
   176  			options: []MergeOption{WithStepCounter()},
   177  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "1 of 2 completed"),
   178  		},
   179  		{
   180  			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf options)",
   181  			from:    getterWithConditions(foo, bar),
   182  			options: []MergeOption{WithStepCounterIf(false)},
   183  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "message falseBar"),
   184  		},
   185  		{
   186  			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf options)",
   187  			from:    getterWithConditions(foo, bar),
   188  			options: []MergeOption{WithStepCounterIf(true)},
   189  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "1 of 2 completed"),
   190  		},
   191  		{
   192  			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf and WithStepCounterIfOnly options)",
   193  			from:    getterWithConditions(bar),
   194  			options: []MergeOption{WithStepCounter(), WithStepCounterIfOnly("bar")},
   195  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "0 of 1 completed"),
   196  		},
   197  		{
   198  			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf and WithStepCounterIfOnly options)",
   199  			from:    getterWithConditions(foo, bar),
   200  			options: []MergeOption{WithStepCounter(), WithStepCounterIfOnly("foo")},
   201  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "message falseBar"),
   202  		},
   203  		{
   204  			name:    "Returns ready condition with the summary of selected conditions (using WithConditions options)",
   205  			from:    getterWithConditions(foo, bar),
   206  			options: []MergeOption{WithConditions("foo")}, // bar should be ignored
   207  			want:    TrueCondition(status.ReadyCondition, "reason trueFoo", "message trueFoo"),
   208  		},
   209  		{
   210  			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounter options)",
   211  			from:    getterWithConditions(foo, bar, baz),
   212  			options: []MergeOption{WithConditions("foo", "bar"), WithStepCounter()}, // baz should be ignored, total steps should be 2
   213  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "1 of 2 completed"),
   214  		},
   215  		{
   216  			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnly options)",
   217  			from:    getterWithConditions(bar),
   218  			options: []MergeOption{WithConditions("bar", "baz"), WithStepCounter(), WithStepCounterIfOnly("bar")}, // there is only bar, the step counter should be set and counts only a subset of conditions
   219  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "0 of 1 completed"),
   220  		},
   221  		{
   222  			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnly options - with inconsistent order between the two)",
   223  			from:    getterWithConditions(bar),
   224  			options: []MergeOption{WithConditions("baz", "bar"), WithStepCounter(), WithStepCounterIfOnly("bar", "baz")}, // conditions in WithStepCounterIfOnly could be in different order than in WithConditions
   225  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "0 of 2 completed"),
   226  		},
   227  		{
   228  			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnly options)",
   229  			from:    getterWithConditions(bar, baz),
   230  			options: []MergeOption{WithConditions("bar", "baz"), WithStepCounter(), WithStepCounterIfOnly("bar")}, // there is also baz, so the step counter should not be set
   231  			want:    FalseCondition(status.ReadyCondition, "reason falseBar", "message falseBar"),
   232  		},
   233  		{
   234  			name:    "Ready condition respects merge order",
   235  			from:    getterWithConditions(bar, baz),
   236  			options: []MergeOption{WithConditions("baz", "bar")}, // baz should take precedence on bar
   237  			want:    FalseCondition(status.ReadyCondition, "reason falseBaz", "message falseBaz"),
   238  		},
   239  		{
   240  			name: "Ignores existing Ready condition when computing the summary",
   241  			from: getterWithConditions(existingReady, foo, bar),
   242  			want: FalseCondition(status.ReadyCondition, "reason falseBar", "message falseBar"),
   243  		},
   244  	}
   245  
   246  	for _, tt := range tests {
   247  		t.Run(tt.name, func(t *testing.T) {
   248  			g := NewWithT(t)
   249  
   250  			got := summary(tt.from, status.ReadyCondition, tt.options...)
   251  			if tt.want == nil {
   252  				g.Expect(got).To(BeNil())
   253  				return
   254  			}
   255  			g.Expect(got).To(HaveSameStateOf(tt.want))
   256  		})
   257  	}
   258  }
   259  
   260  func TestAggregate(t *testing.T) {
   261  	ready1 := TrueCondition(status.ReadyCondition, "reason true1", "message true1")
   262  	ready2 := FalseCondition(status.ReadyCondition, "reason false1", "message false1")
   263  	bar := FalseCondition("bar", "reason falseBar1", "message falseBar1") //NB. bar has higher priority than other conditions
   264  
   265  	tests := []struct {
   266  		name string
   267  		from []Getter
   268  		t    string
   269  		opts []MergeOption
   270  		want *metav1.Condition
   271  	}{
   272  		{
   273  			name: "Returns nil when there are no conditions to aggregate",
   274  			from: []Getter{},
   275  			want: nil,
   276  		},
   277  		{
   278  			name: "Returns foo condition with an aggregation of the object's top group conditions",
   279  			from: []Getter{
   280  				getterWithConditions(ready1),
   281  				getterWithConditions(ready1),
   282  				getterWithConditions(ready2, bar),
   283  				getterWithConditions(),
   284  				getterWithConditions(bar),
   285  			},
   286  			t:    "foo",
   287  			want: FalseCondition("foo", "reason false1", "message false1"),
   288  		},
   289  		{
   290  			name: "Returns foo condition with the aggregation of object's subset conditions",
   291  			from: []Getter{
   292  				getterWithConditions(ready1),
   293  				getterWithConditions(ready1),
   294  				getterWithConditions(ready2, bar),
   295  				getterWithConditions(),
   296  				getterWithConditions(bar),
   297  			},
   298  			opts: []MergeOption{
   299  				WithConditions("bar"),
   300  			},
   301  			t:    "foo",
   302  			want: FalseCondition("foo", "reason falseBar1", "message falseBar1"),
   303  		},
   304  		{
   305  			name: "Returns foo condition with the aggregation of object's subset priority conditions",
   306  			from: []Getter{
   307  				getterWithConditions(ready1),
   308  				getterWithConditions(ready1),
   309  				getterWithConditions(ready2, bar),
   310  				getterWithConditions(),
   311  				getterWithConditions(bar),
   312  			},
   313  			opts: []MergeOption{
   314  				WithConditions("bar", status.ReadyCondition),
   315  			},
   316  			t:    "foo",
   317  			want: FalseCondition("foo", "reason falseBar1", "message falseBar1"),
   318  		},
   319  		{
   320  			name: "Returns foo condition with the aggregation of object's subset priority conditions (inverse)",
   321  			from: []Getter{
   322  				getterWithConditions(ready1),
   323  				getterWithConditions(ready1),
   324  				getterWithConditions(ready2, bar),
   325  				getterWithConditions(),
   326  				getterWithConditions(bar),
   327  			},
   328  			opts: []MergeOption{
   329  				WithConditions(status.ReadyCondition, "bar"),
   330  			},
   331  			t:    "foo",
   332  			want: FalseCondition("foo", "reason false1", "message false1"),
   333  		},
   334  		{
   335  			name: "Returns foo condition with source ref",
   336  			from: []Getter{
   337  				getterWithConditions(ready1),
   338  				getterWithConditions(ready1),
   339  				getterWithConditions(ready2, bar),
   340  				getterWithConditions(),
   341  				getterWithConditions(bar),
   342  			},
   343  			opts: []MergeOption{
   344  				WithSourceRefIf(status.ReadyCondition),
   345  			},
   346  			t:    "foo",
   347  			want: FalseCondition("foo", "reason false1 @ /", "message false1"),
   348  		},
   349  	}
   350  
   351  	for _, tt := range tests {
   352  		t.Run(tt.name, func(t *testing.T) {
   353  			g := NewWithT(t)
   354  
   355  			got := aggregate(tt.from, tt.t, tt.opts...)
   356  			if tt.want == nil {
   357  				g.Expect(got).To(BeNil())
   358  				return
   359  			}
   360  			g.Expect(got).To(HaveSameStateOf(tt.want))
   361  		})
   362  	}
   363  }
   364  
   365  func Fuzz_Getter(f *testing.F) {
   366  	f.Fuzz(func(_ *testing.T,
   367  		data []byte) {
   368  		fc := fuzz.NewConsumer(data)
   369  
   370  		// Set a reproduceable amount of conditions.
   371  		noOfConditions, err := fc.GetInt()
   372  		if err != nil {
   373  			return
   374  		}
   375  		maxNoOfConditions := 30
   376  		conditions := make([]metav1.Condition, 0)
   377  
   378  		// Add N conditions in the slice based on a reproduceable
   379  		// state provided by fc.
   380  		for i := 0; i < noOfConditions%maxNoOfConditions; i++ {
   381  			c := metav1.Condition{}
   382  			err = fc.GenerateStruct(&c)
   383  			if err != nil {
   384  				return
   385  			}
   386  			conditions = append(conditions, c)
   387  		}
   388  		obj := &fobject.Fake{}
   389  		obj.SetConditions(conditions)
   390  
   391  		targetCondition, err := fc.GetString()
   392  		if err != nil {
   393  			return
   394  		}
   395  
   396  		SetSummary(obj, targetCondition)
   397  	})
   398  }
   399  
   400  func getterWithConditions(conditions ...*metav1.Condition) Getter {
   401  	obj := &fobject.Fake{}
   402  	obj.SetConditions(conditionList(conditions...))
   403  	return obj
   404  }
   405  
   406  func conditionList(conditions ...*metav1.Condition) []metav1.Condition {
   407  	cs := []metav1.Condition{}
   408  	for _, x := range conditions {
   409  		if x != nil {
   410  			cs = append(cs, *x)
   411  		}
   412  	}
   413  	return cs
   414  }
   415  

View as plain text