1
16
17 package apihelpers
18
19 import (
20 "reflect"
21 "testing"
22 "time"
23
24 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 )
27
28 func TestIsProtectedCommunityGroup(t *testing.T) {
29 tests := []struct {
30 name string
31
32 group string
33 expected bool
34 }{
35 {
36 name: "bare k8s",
37 group: "k8s.io",
38 expected: true,
39 },
40 {
41 name: "bare kube",
42 group: "kubernetes.io",
43 expected: true,
44 },
45 {
46 name: "nested k8s",
47 group: "sigs.k8s.io",
48 expected: true,
49 },
50 {
51 name: "nested kube",
52 group: "sigs.kubernetes.io",
53 expected: true,
54 },
55 {
56 name: "alternative",
57 group: "different.io",
58 expected: false,
59 },
60 }
61
62 for _, test := range tests {
63 t.Run(test.name, func(t *testing.T) {
64 actual := IsProtectedCommunityGroup(test.group)
65
66 if actual != test.expected {
67 t.Fatalf("expected %v, got %v", test.expected, actual)
68 }
69 })
70 }
71 }
72
73 func TestGetAPIApprovalState(t *testing.T) {
74 tests := []struct {
75 name string
76
77 annotations map[string]string
78 expected APIApprovalState
79 }{
80 {
81 name: "bare unapproved",
82 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "unapproved"},
83 expected: APIApprovalBypassed,
84 },
85 {
86 name: "unapproved with message",
87 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "unapproved, experimental-only"},
88 expected: APIApprovalBypassed,
89 },
90 {
91 name: "mismatched case",
92 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "Unapproved"},
93 expected: APIApprovalInvalid,
94 },
95 {
96 name: "empty",
97 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: ""},
98 expected: APIApprovalMissing,
99 },
100 {
101 name: "missing",
102 annotations: map[string]string{},
103 expected: APIApprovalMissing,
104 },
105 {
106 name: "url",
107 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "https://github.com/kubernetes/kubernetes/pull/78458"},
108 expected: APIApproved,
109 },
110 {
111 name: "url - no scheme",
112 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"},
113 expected: APIApprovalInvalid,
114 },
115 {
116 name: "url - no host",
117 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "http:///kubernetes/kubernetes/pull/78458"},
118 expected: APIApprovalInvalid,
119 },
120 {
121 name: "url - just path",
122 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "/"},
123 expected: APIApprovalInvalid,
124 },
125 {
126 name: "missing scheme",
127 annotations: map[string]string{apiextensionsv1.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"},
128 expected: APIApprovalInvalid,
129 },
130 }
131
132 for _, test := range tests {
133 t.Run(test.name, func(t *testing.T) {
134 actual, _ := GetAPIApprovalState(test.annotations)
135
136 if actual != test.expected {
137 t.Fatalf("expected %v, got %v", test.expected, actual)
138 }
139 })
140 }
141 }
142
143 func TestCRDHasFinalizer(t *testing.T) {
144 tests := []struct {
145 name string
146 crd *apiextensionsv1.CustomResourceDefinition
147 finalizerToCheck string
148
149 expected bool
150 }{
151 {
152 name: "missing",
153 crd: &apiextensionsv1.CustomResourceDefinition{
154 ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}},
155 },
156 finalizerToCheck: "it",
157 expected: false,
158 },
159 {
160 name: "present",
161 crd: &apiextensionsv1.CustomResourceDefinition{
162 ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}},
163 },
164 finalizerToCheck: "it",
165 expected: true,
166 },
167 }
168 for _, tc := range tests {
169 actual := CRDHasFinalizer(tc.crd, tc.finalizerToCheck)
170 if tc.expected != actual {
171 t.Errorf("%v expected %v, got %v", tc.name, tc.expected, actual)
172 }
173 }
174 }
175
176 func TestCRDRemoveFinalizer(t *testing.T) {
177 tests := []struct {
178 name string
179 crd *apiextensionsv1.CustomResourceDefinition
180 finalizerToCheck string
181
182 expected []string
183 }{
184 {
185 name: "missing",
186 crd: &apiextensionsv1.CustomResourceDefinition{
187 ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}},
188 },
189 finalizerToCheck: "it",
190 expected: []string{"not-it"},
191 },
192 {
193 name: "present",
194 crd: &apiextensionsv1.CustomResourceDefinition{
195 ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}},
196 },
197 finalizerToCheck: "it",
198 expected: []string{"not-it"},
199 },
200 }
201 for _, tc := range tests {
202 CRDRemoveFinalizer(tc.crd, tc.finalizerToCheck)
203 if !reflect.DeepEqual(tc.expected, tc.crd.Finalizers) {
204 t.Errorf("%v expected %v, got %v", tc.name, tc.expected, tc.crd.Finalizers)
205 }
206 }
207 }
208
209 func TestSetCRDCondition(t *testing.T) {
210 tests := []struct {
211 name string
212 crdCondition []apiextensionsv1.CustomResourceDefinitionCondition
213 newCondition apiextensionsv1.CustomResourceDefinitionCondition
214 expectedcrdCondition []apiextensionsv1.CustomResourceDefinitionCondition
215 }{
216 {
217 name: "test setCRDcondition when one condition",
218 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
219 {
220 Type: apiextensionsv1.Established,
221 Status: apiextensionsv1.ConditionTrue,
222 Reason: "Accepted",
223 Message: "the initial names have been accepted",
224 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
225 },
226 },
227 newCondition: apiextensionsv1.CustomResourceDefinitionCondition{
228 Type: apiextensionsv1.Established,
229 Status: apiextensionsv1.ConditionFalse,
230 Reason: "NotAccepted",
231 Message: "Not accepted",
232 LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
233 },
234 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
235 {
236 Type: apiextensionsv1.Established,
237 Status: apiextensionsv1.ConditionFalse,
238 Reason: "NotAccepted",
239 Message: "Not accepted",
240 LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
241 },
242 },
243 },
244 {
245 name: "test setCRDcondition when two condition",
246 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
247 {
248 Type: apiextensionsv1.Established,
249 Status: apiextensionsv1.ConditionTrue,
250 Reason: "Accepted",
251 Message: "the initial names have been accepted",
252 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
253 },
254 {
255 Type: apiextensionsv1.NamesAccepted,
256 Status: apiextensionsv1.ConditionTrue,
257 Reason: "NoConflicts",
258 Message: "no conflicts found",
259 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
260 },
261 },
262 newCondition: apiextensionsv1.CustomResourceDefinitionCondition{
263 Type: apiextensionsv1.NamesAccepted,
264 Status: apiextensionsv1.ConditionFalse,
265 Reason: "Conflicts",
266 Message: "conflicts found",
267 LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
268 },
269 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
270 {
271 Type: apiextensionsv1.Established,
272 Status: apiextensionsv1.ConditionTrue,
273 Reason: "Accepted",
274 Message: "the initial names have been accepted",
275 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
276 },
277 {
278 Type: apiextensionsv1.NamesAccepted,
279 Status: apiextensionsv1.ConditionFalse,
280 Reason: "Conflicts",
281 Message: "conflicts found",
282 LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
283 },
284 },
285 },
286 {
287 name: "test setCRDcondition when condition needs to be appended",
288 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
289 {
290 Type: apiextensionsv1.Established,
291 Status: apiextensionsv1.ConditionTrue,
292 Reason: "Accepted",
293 Message: "the initial names have been accepted",
294 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
295 },
296 },
297 newCondition: apiextensionsv1.CustomResourceDefinitionCondition{
298 Type: apiextensionsv1.Terminating,
299 Status: apiextensionsv1.ConditionFalse,
300 Reason: "Neverapiextensionsv1.Established",
301 Message: "resource was never apiextensionsv1.Established",
302 LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
303 },
304 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
305 {
306 Type: apiextensionsv1.Established,
307 Status: apiextensionsv1.ConditionTrue,
308 Reason: "Accepted",
309 Message: "the initial names have been accepted",
310 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
311 },
312 {
313 Type: apiextensionsv1.Terminating,
314 Status: apiextensionsv1.ConditionFalse,
315 Reason: "Neverapiextensionsv1.Established",
316 Message: "resource was never apiextensionsv1.Established",
317 LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
318 },
319 },
320 },
321 {
322 name: "set new condition which doesn't have lastTransitionTime set",
323 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
324 {
325 Type: apiextensionsv1.Established,
326 Status: apiextensionsv1.ConditionTrue,
327 Reason: "Accepted",
328 Message: "the initial names have been accepted",
329 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
330 },
331 },
332 newCondition: apiextensionsv1.CustomResourceDefinitionCondition{
333 Type: apiextensionsv1.Established,
334 Status: apiextensionsv1.ConditionFalse,
335 Reason: "NotAccepted",
336 Message: "Not accepted",
337 },
338 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
339 {
340 Type: apiextensionsv1.Established,
341 Status: apiextensionsv1.ConditionFalse,
342 Reason: "NotAccepted",
343 Message: "Not accepted",
344 LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
345 },
346 },
347 },
348 {
349 name: "append new condition which doesn't have lastTransitionTime set",
350 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
351 {
352 Type: apiextensionsv1.Established,
353 Status: apiextensionsv1.ConditionTrue,
354 Reason: "Accepted",
355 Message: "the initial names have been accepted",
356 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
357 },
358 },
359 newCondition: apiextensionsv1.CustomResourceDefinitionCondition{
360 Type: apiextensionsv1.Terminating,
361 Status: apiextensionsv1.ConditionFalse,
362 Reason: "Neverapiextensionsv1.Established",
363 Message: "resource was never apiextensionsv1.Established",
364 },
365 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
366 {
367 Type: apiextensionsv1.Established,
368 Status: apiextensionsv1.ConditionTrue,
369 Reason: "Accepted",
370 Message: "the initial names have been accepted",
371 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
372 },
373 {
374 Type: apiextensionsv1.Terminating,
375 Status: apiextensionsv1.ConditionFalse,
376 Reason: "Neverapiextensionsv1.Established",
377 Message: "resource was never apiextensionsv1.Established",
378 LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
379 },
380 },
381 },
382 }
383 for _, tc := range tests {
384 crd := generateCRDwithCondition(tc.crdCondition)
385 SetCRDCondition(crd, tc.newCondition)
386 if len(tc.expectedcrdCondition) != len(crd.Status.Conditions) {
387 t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
388 }
389 for i := range tc.expectedcrdCondition {
390 if !IsCRDConditionEquivalent(&tc.expectedcrdCondition[i], &crd.Status.Conditions[i]) {
391 t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition[i], crd.Status.Conditions[i])
392 }
393 if crd.Status.Conditions[i].LastTransitionTime.IsZero() {
394 t.Errorf("%q/%d lastTransitionTime should not be null: %v", tc.name, i, crd.Status.Conditions[i])
395 }
396 }
397 }
398 }
399
400 func TestRemoveCRDCondition(t *testing.T) {
401 tests := []struct {
402 name string
403 crdCondition []apiextensionsv1.CustomResourceDefinitionCondition
404 conditionType apiextensionsv1.CustomResourceDefinitionConditionType
405 expectedcrdCondition []apiextensionsv1.CustomResourceDefinitionCondition
406 }{
407 {
408 name: "test remove CRDCondition when the conditionType meets",
409 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
410 {
411 Type: apiextensionsv1.Established,
412 Status: apiextensionsv1.ConditionTrue,
413 Reason: "Accepted",
414 Message: "the initial names have been accepted",
415 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
416 },
417 {
418 Type: apiextensionsv1.NamesAccepted,
419 Status: apiextensionsv1.ConditionTrue,
420 Reason: "NoConflicts",
421 Message: "no conflicts found",
422 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
423 },
424 },
425 conditionType: apiextensionsv1.NamesAccepted,
426 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
427 {
428 Type: apiextensionsv1.Established,
429 Status: apiextensionsv1.ConditionTrue,
430 Reason: "Accepted",
431 Message: "the initial names have been accepted",
432 LastTransitionTime: metav1.Date(2011, 1, 2, 0, 0, 0, 0, time.UTC),
433 },
434 },
435 },
436 {
437 name: "test remove CRDCondition when the conditionType not meets",
438 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
439 {
440 Type: apiextensionsv1.Established,
441 Status: apiextensionsv1.ConditionTrue,
442 Reason: "Accepted",
443 Message: "the initial names have been accepted",
444 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
445 },
446 {
447 Type: apiextensionsv1.NamesAccepted,
448 Status: apiextensionsv1.ConditionTrue,
449 Reason: "NoConflicts",
450 Message: "no conflicts found",
451 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
452 },
453 },
454 conditionType: apiextensionsv1.Terminating,
455 expectedcrdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
456 {
457 Type: apiextensionsv1.Established,
458 Status: apiextensionsv1.ConditionTrue,
459 Reason: "Accepted",
460 Message: "the initial names have been accepted",
461 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
462 },
463 {
464 Type: apiextensionsv1.NamesAccepted,
465 Status: apiextensionsv1.ConditionTrue,
466 Reason: "NoConflicts",
467 Message: "no conflicts found",
468 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
469 },
470 },
471 },
472 }
473 for _, tc := range tests {
474 crd := generateCRDwithCondition(tc.crdCondition)
475 RemoveCRDCondition(crd, tc.conditionType)
476 if len(tc.expectedcrdCondition) != len(crd.Status.Conditions) {
477 t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
478 }
479 for i := range tc.expectedcrdCondition {
480 if !IsCRDConditionEquivalent(&tc.expectedcrdCondition[i], &crd.Status.Conditions[i]) {
481 t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
482 }
483 }
484 }
485 }
486
487 func TestIsCRDConditionPresentAndEqual(t *testing.T) {
488 tests := []struct {
489 name string
490 crdCondition []apiextensionsv1.CustomResourceDefinitionCondition
491 conditionType apiextensionsv1.CustomResourceDefinitionConditionType
492 status apiextensionsv1.ConditionStatus
493 expectresult bool
494 }{
495 {
496 name: "test CRDCondition is not Present",
497 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
498 {
499 Type: apiextensionsv1.Established,
500 Status: apiextensionsv1.ConditionTrue,
501 Reason: "Accepted",
502 Message: "the initial names have been accepted",
503 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
504 },
505 {
506 Type: apiextensionsv1.NamesAccepted,
507 Status: apiextensionsv1.ConditionTrue,
508 Reason: "NoConflicts",
509 Message: "no conflicts found",
510 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
511 },
512 },
513 conditionType: apiextensionsv1.Terminating,
514 status: apiextensionsv1.ConditionTrue,
515 expectresult: false,
516 },
517 {
518 name: "test CRDCondition is Present but not Equal",
519 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
520 {
521 Type: apiextensionsv1.Established,
522 Status: apiextensionsv1.ConditionTrue,
523 Reason: "Accepted",
524 Message: "the initial names have been accepted",
525 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
526 },
527 {
528 Type: apiextensionsv1.NamesAccepted,
529 Status: apiextensionsv1.ConditionTrue,
530 Reason: "NoConflicts",
531 Message: "no conflicts found",
532 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
533 },
534 },
535 conditionType: apiextensionsv1.Established,
536 status: apiextensionsv1.ConditionFalse,
537 expectresult: false,
538 },
539 {
540 name: "test CRDCondition is Present and Equal",
541 crdCondition: []apiextensionsv1.CustomResourceDefinitionCondition{
542 {
543 Type: apiextensionsv1.Established,
544 Status: apiextensionsv1.ConditionTrue,
545 Reason: "Accepted",
546 Message: "the initial names have been accepted",
547 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
548 },
549 {
550 Type: apiextensionsv1.NamesAccepted,
551 Status: apiextensionsv1.ConditionTrue,
552 Reason: "NoConflicts",
553 Message: "no conflicts found",
554 LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
555 },
556 },
557 conditionType: apiextensionsv1.NamesAccepted,
558 status: apiextensionsv1.ConditionTrue,
559 expectresult: true,
560 },
561 }
562 for _, tc := range tests {
563 crd := generateCRDwithCondition(tc.crdCondition)
564 res := IsCRDConditionPresentAndEqual(crd, tc.conditionType, tc.status)
565 if res != tc.expectresult {
566 t.Errorf("%v expected %t, got %t", tc.name, tc.expectresult, res)
567 }
568 }
569 }
570
571 func generateCRDwithCondition(conditions []apiextensionsv1.CustomResourceDefinitionCondition) *apiextensionsv1.CustomResourceDefinition {
572 testCRDObjectMeta := metav1.ObjectMeta{
573 Name: "plural.group.com",
574 ResourceVersion: "12",
575 }
576 testCRDSpec := apiextensionsv1.CustomResourceDefinitionSpec{
577 Group: "group.com",
578 Names: apiextensionsv1.CustomResourceDefinitionNames{
579 Plural: "plural",
580 Singular: "singular",
581 Kind: "kind",
582 ListKind: "listkind",
583 },
584 }
585 testCRDAcceptedNames := apiextensionsv1.CustomResourceDefinitionNames{
586 Plural: "plural",
587 Singular: "singular",
588 Kind: "kind",
589 ListKind: "listkind",
590 }
591 return &apiextensionsv1.CustomResourceDefinition{
592 ObjectMeta: testCRDObjectMeta,
593 Spec: testCRDSpec,
594 Status: apiextensionsv1.CustomResourceDefinitionStatus{
595 AcceptedNames: testCRDAcceptedNames,
596 Conditions: conditions,
597 },
598 }
599 }
600
View as plain text