blob: b72ccb9e40002335f2774886f7389e324992c4b8 [file] [log] [blame]
Serge Bazanskie6030f62020-06-03 17:52:59 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package reconciler
18
19import (
20 "context"
21 "fmt"
22 "testing"
23
Serge Bazanskie6030f62020-06-03 17:52:59 +020024 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
25)
26
Serge Bazanski216fe7b2021-05-21 18:36:16 +020027// TestExpectedLabeledCorrectly ensures that all the Expected objects of all
28// resource types have a Kubernetes metadata label that signifies it's a
29// builtin object, to be retrieved afterwards. This contract must be met in
30// order for the reconciler to not keep overwriting objects (and possibly
31// failing), when a newly created object is not then retrievable using a
Jan Schär7f727482024-03-25 13:03:51 +010032// selector corresponding to this label.
Serge Bazanskie6030f62020-06-03 17:52:59 +020033func TestExpectedLabeledCorrectly(t *testing.T) {
34 for reconciler, r := range allResources(nil) {
35 for outer, v := range r.Expected() {
Jan Schär7f727482024-03-25 13:03:51 +010036 if data := v.GetLabels()[BuiltinLabelKey]; data != BuiltinLabelValue {
Serge Bazanskie6030f62020-06-03 17:52:59 +020037 t.Errorf("reconciler %q, object %q: %q=%q, wanted =%q", reconciler, outer, BuiltinLabelKey, data, BuiltinLabelValue)
38 continue
39 }
40 }
41 }
42}
43
Jan Schär7f727482024-03-25 13:03:51 +010044// testObject is the object type managed by testResource.
45type testObject struct {
46 meta.ObjectMeta
47}
48
49func makeTestObject(name string) *testObject {
50 return &testObject{
51 ObjectMeta: meta.ObjectMeta{
52 Name: name,
53 Labels: builtinLabels(nil),
54 },
55 }
56}
57
58// testResource is a resource type used for testing. It simulates a target
59// (ie. k8s apiserver mock) that always acts nominally (all resources are
60// created, deleted as requested, and the state is consistent with requests).
Serge Bazanskie6030f62020-06-03 17:52:59 +020061type testResource struct {
62 // current is the simulated state of resources in the target.
Jan Schär7f727482024-03-25 13:03:51 +010063 current map[string]*testObject
Serge Bazanskie6030f62020-06-03 17:52:59 +020064 // expected is what this type will report as the Expected() resources.
Jan Schär7f727482024-03-25 13:03:51 +010065 expected map[string]*testObject
Serge Bazanskie6030f62020-06-03 17:52:59 +020066}
67
Jan Schär7f727482024-03-25 13:03:51 +010068func (r *testResource) List(ctx context.Context) ([]meta.Object, error) {
69 var cur []meta.Object
70 for _, v := range r.current {
71 v_copy := *v
72 cur = append(cur, &v_copy)
Serge Bazanskie6030f62020-06-03 17:52:59 +020073 }
Jan Schär7f727482024-03-25 13:03:51 +010074 return cur, nil
Serge Bazanskie6030f62020-06-03 17:52:59 +020075}
76
Jan Schär7f727482024-03-25 13:03:51 +010077func (r *testResource) Create(ctx context.Context, el meta.Object) error {
78 r.current[el.GetName()] = el.(*testObject)
Serge Bazanskie6030f62020-06-03 17:52:59 +020079 return nil
80}
81
82func (r *testResource) Delete(ctx context.Context, name string) error {
83 delete(r.current, name)
84 return nil
85}
86
Jan Schär7f727482024-03-25 13:03:51 +010087func (r *testResource) Expected() []meta.Object {
88 var exp []meta.Object
89 for _, v := range r.expected {
90 v_copy := *v
91 exp = append(exp, &v_copy)
Serge Bazanskie6030f62020-06-03 17:52:59 +020092 }
93 return exp
94}
95
Jan Schär7f727482024-03-25 13:03:51 +010096// newTestResource creates a test resource with a list of expected objects.
97func newTestResource(want ...*testObject) *testResource {
98 expected := make(map[string]*testObject)
Serge Bazanskie6030f62020-06-03 17:52:59 +020099 for _, w := range want {
Jan Schär7f727482024-03-25 13:03:51 +0100100 expected[w.GetName()] = w
Serge Bazanskie6030f62020-06-03 17:52:59 +0200101 }
102 return &testResource{
Jan Schär7f727482024-03-25 13:03:51 +0100103 current: make(map[string]*testObject),
Serge Bazanskie6030f62020-06-03 17:52:59 +0200104 expected: expected,
105 }
106}
107
Jan Schär7f727482024-03-25 13:03:51 +0100108// currentDiff returns a human-readable string showing the difference between
109// the current state and the given objects. If no difference is
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200110// present, the returned string is empty.
Jan Schär7f727482024-03-25 13:03:51 +0100111func (r *testResource) currentDiff(want ...*testObject) string {
112 expected := make(map[string]*testObject)
Serge Bazanskie6030f62020-06-03 17:52:59 +0200113 for _, w := range want {
Jan Schär7f727482024-03-25 13:03:51 +0100114 if _, ok := r.current[w.GetName()]; !ok {
115 return fmt.Sprintf("%q missing in current", w.GetName())
Serge Bazanskie6030f62020-06-03 17:52:59 +0200116 }
Jan Schär7f727482024-03-25 13:03:51 +0100117 expected[w.GetName()] = w
Serge Bazanskie6030f62020-06-03 17:52:59 +0200118 }
119 for _, g := range r.current {
Jan Schär7f727482024-03-25 13:03:51 +0100120 if _, ok := expected[g.GetName()]; !ok {
121 return fmt.Sprintf("%q spurious in current", g.GetName())
Serge Bazanskie6030f62020-06-03 17:52:59 +0200122 }
123 }
124 return ""
125}
126
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200127// TestBasicReconciliation ensures that the reconcile function does manipulate
128// a target state based on a set of expected resources.
Serge Bazanskie6030f62020-06-03 17:52:59 +0200129func TestBasicReconciliation(t *testing.T) {
130 ctx := context.Background()
Jan Schär7f727482024-03-25 13:03:51 +0100131 r := newTestResource(makeTestObject("foo"), makeTestObject("bar"), makeTestObject("baz"))
Serge Bazanskie6030f62020-06-03 17:52:59 +0200132
133 // nothing should have happened yet (testing the test)
134 if diff := r.currentDiff(); diff != "" {
135 t.Fatalf("wrong state after creation: %s", diff)
136 }
137
138 if err := reconcile(ctx, r); err != nil {
139 t.Fatalf("reconcile: %v", err)
140 }
141 // everything requested should have been created
Jan Schär7f727482024-03-25 13:03:51 +0100142 if diff := r.currentDiff(makeTestObject("foo"), makeTestObject("bar"), makeTestObject("baz")); diff != "" {
Serge Bazanskie6030f62020-06-03 17:52:59 +0200143 t.Fatalf("wrong state after reconciliation: %s", diff)
144 }
145
146 delete(r.expected, "foo")
147 if err := reconcile(ctx, r); err != nil {
148 t.Fatalf("reconcile: %v", err)
149 }
Jan Schär7f727482024-03-25 13:03:51 +0100150 // foo should now be missing
151 if diff := r.currentDiff(makeTestObject("bar"), makeTestObject("baz")); diff != "" {
Serge Bazanskie6030f62020-06-03 17:52:59 +0200152 t.Fatalf("wrong state after deleting foo: %s", diff)
153 }
154}