1
16
17 package customresource
18
19 import (
20 "context"
21 "reflect"
22 "testing"
23
24 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
25 v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
26 apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28 "k8s.io/apimachinery/pkg/fields"
29 utilfeature "k8s.io/apiserver/pkg/util/feature"
30 featuregatetesting "k8s.io/component-base/featuregate/testing"
31 )
32
33 func generation1() map[string]interface{} {
34 return map[string]interface{}{
35 "generation": int64(1),
36 }
37 }
38
39 func generation2() map[string]interface{} {
40 return map[string]interface{}{
41 "generation": int64(2),
42 }
43 }
44
45 func TestStrategyPrepareForUpdate(t *testing.T) {
46 strategy := customResourceStrategy{}
47 tcs := []struct {
48 name string
49 old *unstructured.Unstructured
50 obj *unstructured.Unstructured
51 statusEnabled bool
52 expected *unstructured.Unstructured
53 }{
54 {
55 name: "/status is enabled, spec changes increment generation",
56 statusEnabled: true,
57 old: &unstructured.Unstructured{
58 Object: map[string]interface{}{
59 "metadata": generation1(),
60 "spec": "old",
61 "status": "old",
62 },
63 },
64 obj: &unstructured.Unstructured{
65 Object: map[string]interface{}{
66 "metadata": generation1(),
67 "spec": "new",
68 "status": "old",
69 },
70 },
71 expected: &unstructured.Unstructured{
72 Object: map[string]interface{}{
73 "metadata": generation2(),
74 "spec": "new",
75 "status": "old",
76 },
77 },
78 },
79 {
80 name: "/status is enabled, status changes do not increment generation, status changes removed",
81 statusEnabled: true,
82 old: &unstructured.Unstructured{
83 Object: map[string]interface{}{
84 "metadata": generation1(),
85 "spec": "old",
86 "status": "old",
87 },
88 },
89 obj: &unstructured.Unstructured{
90 Object: map[string]interface{}{
91 "metadata": generation1(),
92 "spec": "old",
93 "status": "new",
94 },
95 },
96 expected: &unstructured.Unstructured{
97 Object: map[string]interface{}{
98 "metadata": generation1(),
99 "spec": "old",
100 "status": "old",
101 },
102 },
103 },
104 {
105 name: "/status is enabled, metadata changes do not increment generation",
106 statusEnabled: true,
107 old: &unstructured.Unstructured{
108 Object: map[string]interface{}{
109 "metadata": map[string]interface{}{
110 "generation": int64(1),
111 "other": "old",
112 },
113 "spec": "old",
114 "status": "old",
115 },
116 },
117 obj: &unstructured.Unstructured{
118 Object: map[string]interface{}{
119 "metadata": map[string]interface{}{
120 "generation": int64(1),
121 "other": "new",
122 },
123 "spec": "old",
124 "status": "old",
125 },
126 },
127 expected: &unstructured.Unstructured{
128 Object: map[string]interface{}{
129 "metadata": map[string]interface{}{
130 "generation": int64(1),
131 "other": "new",
132 },
133 "spec": "old",
134 "status": "old",
135 },
136 },
137 },
138 {
139 name: "/status is disabled, spec changes increment generation",
140 statusEnabled: false,
141 old: &unstructured.Unstructured{
142 Object: map[string]interface{}{
143 "metadata": generation1(),
144 "spec": "old",
145 "status": "old",
146 },
147 },
148 obj: &unstructured.Unstructured{
149 Object: map[string]interface{}{
150 "metadata": generation1(),
151 "spec": "new",
152 "status": "old",
153 },
154 },
155 expected: &unstructured.Unstructured{
156 Object: map[string]interface{}{
157 "metadata": generation2(),
158 "spec": "new",
159 "status": "old",
160 },
161 },
162 },
163 {
164 name: "/status is disabled, status changes increment generation",
165 statusEnabled: false,
166 old: &unstructured.Unstructured{
167 Object: map[string]interface{}{
168 "metadata": generation1(),
169 "spec": "old",
170 "status": "old",
171 },
172 },
173 obj: &unstructured.Unstructured{
174 Object: map[string]interface{}{
175 "metadata": generation1(),
176 "spec": "old",
177 "status": "new",
178 },
179 },
180 expected: &unstructured.Unstructured{
181 Object: map[string]interface{}{
182 "metadata": generation2(),
183 "spec": "old",
184 "status": "new",
185 },
186 },
187 },
188 {
189 name: "/status is disabled, other top-level field changes increment generation",
190 statusEnabled: false,
191 old: &unstructured.Unstructured{
192 Object: map[string]interface{}{
193 "metadata": generation1(),
194 "spec": "old",
195 "image": "old",
196 "status": "old",
197 },
198 },
199 obj: &unstructured.Unstructured{
200 Object: map[string]interface{}{
201 "metadata": generation1(),
202 "spec": "old",
203 "image": "new",
204 "status": "old",
205 },
206 },
207 expected: &unstructured.Unstructured{
208 Object: map[string]interface{}{
209 "metadata": generation2(),
210 "spec": "old",
211 "image": "new",
212 "status": "old",
213 },
214 },
215 },
216 {
217 name: "/status is disabled, metadata changes do not increment generation",
218 statusEnabled: false,
219 old: &unstructured.Unstructured{
220 Object: map[string]interface{}{
221 "metadata": map[string]interface{}{
222 "generation": int64(1),
223 "other": "old",
224 },
225 "spec": "old",
226 "status": "old",
227 },
228 },
229 obj: &unstructured.Unstructured{
230 Object: map[string]interface{}{
231 "metadata": map[string]interface{}{
232 "generation": int64(1),
233 "other": "new",
234 },
235 "spec": "old",
236 "status": "old",
237 },
238 },
239 expected: &unstructured.Unstructured{
240 Object: map[string]interface{}{
241 "metadata": map[string]interface{}{
242 "generation": int64(1),
243 "other": "new",
244 },
245 "spec": "old",
246 "status": "old",
247 },
248 },
249 },
250 }
251 for _, tc := range tcs {
252 if tc.statusEnabled {
253 strategy.status = &apiextensions.CustomResourceSubresourceStatus{}
254 } else {
255 strategy.status = nil
256 }
257 strategy.PrepareForUpdate(context.TODO(), tc.obj, tc.old)
258 if !reflect.DeepEqual(tc.obj, tc.expected) {
259 t.Errorf("test %q failed: expected: %v, got %v", tc.name, tc.expected, tc.obj)
260 }
261 }
262 }
263
264 func TestSelectableFields(t *testing.T) {
265 tcs := []struct {
266 name string
267 selectableFields []v1.SelectableField
268 obj *unstructured.Unstructured
269 expectFields fields.Set
270 }{
271 {
272 name: "valid path",
273 selectableFields: []v1.SelectableField{
274 {JSONPath: ".spec.foo"},
275 },
276 obj: &unstructured.Unstructured{
277 Object: map[string]interface{}{
278 "metadata": map[string]interface{}{
279 "name": "example",
280 "generation": int64(1),
281 "other": "new",
282 },
283 "spec": map[string]interface{}{
284 "foo": "x",
285 },
286 },
287 },
288 expectFields: map[string]string{"spec.foo": "x", "metadata.name": "example"},
289 },
290 {
291 name: "missing value",
292 selectableFields: []v1.SelectableField{
293 {JSONPath: ".spec.foo"},
294 },
295 obj: &unstructured.Unstructured{
296 Object: map[string]interface{}{
297 "metadata": map[string]interface{}{
298 "name": "example",
299 "generation": int64(1),
300 "other": "new",
301 },
302 "spec": map[string]interface{}{},
303 },
304 },
305 expectFields: map[string]string{"spec.foo": "", "metadata.name": "example"},
306 },
307 }
308
309 for _, tc := range tcs {
310 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceFieldSelectors, true)()
311 t.Run(tc.name, func(t *testing.T) {
312 strategy := customResourceStrategy{selectableFieldSet: prepareSelectableFields(tc.selectableFields)}
313
314 _, fields, err := strategy.GetAttrs(tc.obj)
315 if err != nil {
316 t.Fatal(err)
317 }
318 if !reflect.DeepEqual(tc.expectFields, fields) {
319 t.Errorf("Expected fields '%+#v' but got '%+#v'", tc.expectFields, fields)
320 }
321 })
322 }
323 }
324
View as plain text