1
16
17 package validation
18
19 import (
20 "strings"
21 "testing"
22
23 "k8s.io/apimachinery/pkg/util/validation/field"
24 "k8s.io/kubernetes/pkg/apis/apiserverinternal"
25 "k8s.io/utils/pointer"
26 )
27
28 func TestValidateServerStorageVersion(t *testing.T) {
29 cases := []struct {
30 ssv apiserverinternal.ServerStorageVersion
31 expectedErr string
32 }{{
33 ssv: apiserverinternal.ServerStorageVersion{
34 APIServerID: "-fea",
35 EncodingVersion: "v1alpha1",
36 DecodableVersions: []string{"v1alpha1", "v1"},
37 ServedVersions: []string{"v1alpha1", "v1"},
38 },
39 expectedErr: "apiServerID: Invalid value",
40 }, {
41 ssv: apiserverinternal.ServerStorageVersion{
42 APIServerID: "fea",
43 EncodingVersion: "v1alpha1",
44 DecodableVersions: []string{"v1beta1", "v1"},
45 ServedVersions: []string{"v1beta1", "v1"},
46 },
47 expectedErr: "decodableVersions must include encodingVersion",
48 }, {
49 ssv: apiserverinternal.ServerStorageVersion{
50 APIServerID: "fea",
51 EncodingVersion: "v1alpha1",
52 DecodableVersions: []string{"v1alpha1", "v1", "-fea"},
53 ServedVersions: []string{"v1alpha1", "v1", "-fea"},
54 },
55 expectedErr: "decodableVersions[2]: Invalid value",
56 }, {
57 ssv: apiserverinternal.ServerStorageVersion{
58 APIServerID: "fea",
59 EncodingVersion: "v1alpha1",
60 DecodableVersions: []string{"v1alpha1", "v1"},
61 ServedVersions: []string{"v1alpha1", "v1"},
62 },
63 expectedErr: "",
64 }, {
65 ssv: apiserverinternal.ServerStorageVersion{
66 APIServerID: "fea",
67 EncodingVersion: "v1alpha1",
68 DecodableVersions: []string{"v1alpha1", "v1"},
69 ServedVersions: []string{"v1alpha1", "v1"},
70 },
71 expectedErr: "",
72 }, {
73 ssv: apiserverinternal.ServerStorageVersion{
74 APIServerID: "fea",
75 EncodingVersion: "mygroup.com/v2",
76 DecodableVersions: []string{"v1alpha1", "v1", "mygroup.com/v2"},
77 ServedVersions: []string{"v1alpha1", "v1", "mygroup.com/v2"},
78 },
79 expectedErr: "",
80 }, {
81 ssv: apiserverinternal.ServerStorageVersion{
82 APIServerID: "fea",
83 EncodingVersion: "v1alpha1",
84 DecodableVersions: []string{"v1alpha1", "v1"},
85 ServedVersions: []string{"/v3"},
86 },
87 expectedErr: `[].servedVersions[0]: Invalid value: "/v3": group part: must be non-empty`,
88 }, {
89 ssv: apiserverinternal.ServerStorageVersion{
90 APIServerID: "fea",
91 EncodingVersion: "mygroup.com/v2",
92 DecodableVersions: []string{"mygroup.com/v2", "/v3"},
93 ServedVersions: []string{"mygroup.com/v2", "/v3"},
94 },
95 expectedErr: `[].decodableVersions[1]: Invalid value: "/v3": group part: must be non-empty`,
96 }, {
97 ssv: apiserverinternal.ServerStorageVersion{
98 APIServerID: "fea",
99 EncodingVersion: "mygroup.com/v2",
100 DecodableVersions: []string{"mygroup.com/v2", "/v3"},
101 ServedVersions: []string{"mygroup.com/"},
102 },
103 expectedErr: `[].servedVersions[0]: Invalid value: "mygroup.com/": version part: must be non-empty`,
104 }, {
105 ssv: apiserverinternal.ServerStorageVersion{
106 APIServerID: "fea",
107 EncodingVersion: "mygroup.com/v2",
108 DecodableVersions: []string{"mygroup.com/v2", "mygroup.com/"},
109 ServedVersions: []string{"mygroup.com/v2", "mygroup.com/"},
110 },
111 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/": version part: must be non-empty`,
112 }, {
113 ssv: apiserverinternal.ServerStorageVersion{
114 APIServerID: "fea",
115 EncodingVersion: "/v3",
116 DecodableVersions: []string{"mygroup.com/v2", "/v3"},
117 ServedVersions: []string{"mygroup.com/v2", "/v3"},
118 },
119 expectedErr: `[].encodingVersion: Invalid value: "/v3": group part: must be non-empty`,
120 }, {
121 ssv: apiserverinternal.ServerStorageVersion{
122 APIServerID: "fea",
123 EncodingVersion: "v1",
124 DecodableVersions: []string{"v1", "mygroup_com/v2"},
125 ServedVersions: []string{"v1", "mygroup_com/v2"},
126 },
127 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup_com/v2": group part: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`,
128 }, {
129 ssv: apiserverinternal.ServerStorageVersion{
130 APIServerID: "fea",
131 EncodingVersion: "v1",
132 DecodableVersions: []string{"v1", "mygroup.com/v2"},
133 ServedVersions: []string{"v1", "mygroup_com/v2"},
134 },
135 expectedErr: `[].servedVersions[1]: Invalid value: "mygroup_com/v2": group part: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`,
136 }, {
137 ssv: apiserverinternal.ServerStorageVersion{
138 APIServerID: "fea",
139 EncodingVersion: "v1",
140 DecodableVersions: []string{"v1", "mygroup.com/v2_"},
141 ServedVersions: []string{"v1", "mygroup.com/v2_"},
142 },
143 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2_": version part: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`,
144 }, {
145 ssv: apiserverinternal.ServerStorageVersion{
146 APIServerID: "fea",
147 EncodingVersion: "v1",
148 DecodableVersions: []string{"v1", "mygroup.com/v2"},
149 ServedVersions: []string{"v1", "mygroup.com/v2_"},
150 },
151 expectedErr: `[].servedVersions[1]: Invalid value: "mygroup.com/v2_": version part: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')`,
152 }, {
153 ssv: apiserverinternal.ServerStorageVersion{
154 APIServerID: "fea",
155 EncodingVersion: "v1",
156 DecodableVersions: []string{"v1", "mygroup.com/v2/myresource"},
157 ServedVersions: []string{"v1", "mygroup.com/v2/myresource"},
158 },
159 expectedErr: `[].decodableVersions[1]: Invalid value: "mygroup.com/v2/myresource": an apiVersion is a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')`,
160 }, {
161 ssv: apiserverinternal.ServerStorageVersion{
162 APIServerID: "fea",
163 EncodingVersion: "v1",
164 DecodableVersions: []string{"v1", "mygroup.com/v2"},
165 ServedVersions: []string{"v1", "mygroup.com/v2/myresource"},
166 },
167 expectedErr: `[].servedVersions[1]: Invalid value: "mygroup.com/v2/myresource": an apiVersion is a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')`,
168 }, {
169 ssv: apiserverinternal.ServerStorageVersion{
170 APIServerID: "fea",
171 EncodingVersion: "v1alpha1",
172 DecodableVersions: []string{"v1alpha1", "v1"},
173 ServedVersions: []string{"v2"},
174 },
175 expectedErr: `[].servedVersions[0]: Invalid value: "v2": individual served version : v2 must be included in decodableVersions : [v1alpha1 v1]`,
176 }}
177
178 for _, tc := range cases {
179 err := validateServerStorageVersion(tc.ssv, field.NewPath("")).ToAggregate()
180 if err == nil && len(tc.expectedErr) == 0 {
181 continue
182 }
183 if err != nil && len(tc.expectedErr) == 0 {
184 t.Errorf("unexpected error %v", err)
185 continue
186 }
187 if err == nil && len(tc.expectedErr) != 0 {
188 t.Errorf("unexpected empty error")
189 continue
190 }
191 if !strings.Contains(err.Error(), tc.expectedErr) {
192 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err)
193 }
194 }
195 }
196
197 func TestValidateStorageVersionStatus(t *testing.T) {
198 cases := []struct {
199 svs apiserverinternal.StorageVersionStatus
200 expectedErr string
201 }{{
202 svs: apiserverinternal.StorageVersionStatus{
203 StorageVersions: []apiserverinternal.ServerStorageVersion{{
204 APIServerID: "1",
205 EncodingVersion: "v1alpha1",
206 DecodableVersions: []string{"v1alpha1", "v1"},
207 }, {
208 APIServerID: "2",
209 EncodingVersion: "v1alpha1",
210 DecodableVersions: []string{"v1alpha1", "v1"},
211 }},
212 CommonEncodingVersion: pointer.String("v1alpha1"),
213 },
214 expectedErr: "",
215 }, {
216 svs: apiserverinternal.StorageVersionStatus{
217 StorageVersions: []apiserverinternal.ServerStorageVersion{{
218 APIServerID: "1",
219 EncodingVersion: "v1alpha1",
220 DecodableVersions: []string{"v1alpha1", "v1"},
221 }, {
222 APIServerID: "1",
223 EncodingVersion: "v1beta1",
224 DecodableVersions: []string{"v1alpha1", "v1"},
225 }},
226 CommonEncodingVersion: pointer.String("v1alpha1"),
227 },
228 expectedErr: "storageVersions[1].apiServerID: Duplicate value: \"1\"",
229 }}
230
231 for _, tc := range cases {
232 err := validateStorageVersionStatus(tc.svs, field.NewPath("")).ToAggregate()
233 if err == nil && len(tc.expectedErr) == 0 {
234 continue
235 }
236 if err != nil && len(tc.expectedErr) == 0 {
237 t.Errorf("unexpected error %v", err)
238 continue
239 }
240 if err == nil && len(tc.expectedErr) != 0 {
241 t.Errorf("unexpected empty error")
242 continue
243 }
244 if !strings.Contains(err.Error(), tc.expectedErr) {
245 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err)
246 }
247 }
248 }
249
250 func TestValidateCommonVersion(t *testing.T) {
251 cases := []struct {
252 status apiserverinternal.StorageVersionStatus
253 expectedErr string
254 }{{
255 status: apiserverinternal.StorageVersionStatus{
256 StorageVersions: []apiserverinternal.ServerStorageVersion{},
257 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(),
258 },
259 expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet",
260 }, {
261 status: apiserverinternal.StorageVersionStatus{
262 StorageVersions: []apiserverinternal.ServerStorageVersion{{
263 APIServerID: "1",
264 EncodingVersion: "v1alpha1",
265 }, {
266 APIServerID: "2",
267 EncodingVersion: "v1",
268 }},
269 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(),
270 },
271 expectedErr: "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet",
272 }, {
273 status: apiserverinternal.StorageVersionStatus{
274 StorageVersions: []apiserverinternal.ServerStorageVersion{{
275 APIServerID: "1",
276 EncodingVersion: "v1alpha1",
277 }, {
278 APIServerID: "2",
279 EncodingVersion: "v1alpha1",
280 }},
281 CommonEncodingVersion: nil,
282 },
283 expectedErr: "Invalid value: \"null\": the common encoding version is v1alpha1",
284 }, {
285 status: apiserverinternal.StorageVersionStatus{
286 StorageVersions: []apiserverinternal.ServerStorageVersion{{
287 APIServerID: "1",
288 EncodingVersion: "v1alpha1",
289 }, {
290 APIServerID: "2",
291 EncodingVersion: "v1alpha1",
292 }},
293 CommonEncodingVersion: func() *string { a := "v1"; return &a }(),
294 },
295 expectedErr: "Invalid value: \"v1\": the actual common encoding version is v1alpha1",
296 }, {
297 status: apiserverinternal.StorageVersionStatus{
298 StorageVersions: []apiserverinternal.ServerStorageVersion{{
299 APIServerID: "1",
300 EncodingVersion: "v1alpha1",
301 }, {
302 APIServerID: "2",
303 EncodingVersion: "v1alpha1",
304 }},
305 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(),
306 },
307 expectedErr: "",
308 }, {
309 status: apiserverinternal.StorageVersionStatus{
310 StorageVersions: []apiserverinternal.ServerStorageVersion{{
311 APIServerID: "1",
312 EncodingVersion: "v1alpha1",
313 }},
314 CommonEncodingVersion: func() *string { a := "v1alpha1"; return &a }(),
315 },
316 expectedErr: "",
317 }}
318 for _, tc := range cases {
319 err := validateCommonVersion(tc.status, field.NewPath(""))
320 if err == nil && len(tc.expectedErr) == 0 {
321 continue
322 }
323 if err != nil && len(tc.expectedErr) == 0 {
324 t.Errorf("unexpected error %v", err)
325 continue
326 }
327 if err == nil && len(tc.expectedErr) != 0 {
328 t.Errorf("unexpected empty error")
329 continue
330 }
331 if !strings.Contains(err.Error(), tc.expectedErr) {
332 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err)
333 }
334 }
335 }
336
337 func TestValidateStorageVersionCondition(t *testing.T) {
338 cases := []struct {
339 conditions []apiserverinternal.StorageVersionCondition
340 expectedErr string
341 }{{
342 conditions: []apiserverinternal.StorageVersionCondition{{
343 Type: "-fea",
344 Status: "True",
345 Reason: "unknown",
346 Message: "unknown",
347 }},
348 expectedErr: "type: Invalid value",
349 }, {
350 conditions: []apiserverinternal.StorageVersionCondition{{
351 Type: "fea",
352 Status: "-True",
353 Reason: "unknown",
354 Message: "unknown",
355 }},
356 expectedErr: "status: Invalid value",
357 }, {
358 conditions: []apiserverinternal.StorageVersionCondition{{
359 Type: "fea",
360 Status: "True",
361 Message: "unknown",
362 }},
363 expectedErr: "Required value: reason cannot be empty",
364 }, {
365 conditions: []apiserverinternal.StorageVersionCondition{{
366 Type: "fea",
367 Status: "True",
368 Reason: "unknown",
369 }},
370 expectedErr: "Required value: message cannot be empty",
371 }, {
372 conditions: []apiserverinternal.StorageVersionCondition{{
373 Type: "fea",
374 Status: "True",
375 Reason: "unknown",
376 Message: "unknown",
377 }, {
378 Type: "fea",
379 Status: "True",
380 Reason: "unknown",
381 Message: "unknown",
382 }},
383 expectedErr: `"fea": the type of the condition is not unique, it also appears in conditions[0]`,
384 }, {
385 conditions: []apiserverinternal.StorageVersionCondition{{
386 Type: "fea",
387 Status: "True",
388 Reason: "unknown",
389 Message: "unknown",
390 }},
391 expectedErr: "",
392 }}
393 for _, tc := range cases {
394 err := validateStorageVersionCondition(tc.conditions, field.NewPath("")).ToAggregate()
395 if err == nil && len(tc.expectedErr) == 0 {
396 continue
397 }
398 if err != nil && len(tc.expectedErr) == 0 {
399 t.Errorf("unexpected error %v", err)
400 continue
401 }
402 if err == nil && len(tc.expectedErr) != 0 {
403 t.Errorf("unexpected empty error")
404 continue
405 }
406 if !strings.Contains(err.Error(), tc.expectedErr) {
407 t.Errorf("expected error to contain %s, got %s", tc.expectedErr, err)
408 }
409 }
410 }
411
412 func TestValidateStorageVersionName(t *testing.T) {
413 cases := []struct {
414 name string
415 expectedErr string
416 }{{
417 name: "",
418 expectedErr: `name must be in the form of <group>.<resource>`,
419 }, {
420 name: "pods",
421 expectedErr: `name must be in the form of <group>.<resource>`,
422 }, {
423 name: "core.pods",
424 expectedErr: "",
425 }, {
426 name: "authentication.k8s.io.tokenreviews",
427 expectedErr: "",
428 }, {
429 name: strings.Repeat("x", 253) + ".tokenreviews",
430 expectedErr: "",
431 }, {
432 name: strings.Repeat("x", 254) + ".tokenreviews",
433 expectedErr: `the group segment must be no more than 253 characters`,
434 }, {
435 name: "authentication.k8s.io." + strings.Repeat("x", 63),
436 expectedErr: "",
437 }, {
438 name: "authentication.k8s.io." + strings.Repeat("x", 64),
439 expectedErr: `the resource segment must be no more than 63 characters`,
440 }}
441 for _, tc := range cases {
442 errs := ValidateStorageVersionName(tc.name, false)
443 if errs == nil && len(tc.expectedErr) == 0 {
444 continue
445 }
446 if errs != nil && len(tc.expectedErr) == 0 {
447 t.Errorf("unexpected error %v", errs)
448 continue
449 }
450 if errs == nil && len(tc.expectedErr) != 0 {
451 t.Errorf("unexpected empty error")
452 continue
453 }
454 found := false
455 for _, msg := range errs {
456 if msg == tc.expectedErr {
457 found = true
458 }
459 }
460 if !found {
461 t.Errorf("expected error to contain %s, got %v", tc.expectedErr, errs)
462 }
463 }
464 }
465
View as plain text