1
16
17 package validation
18
19 import (
20 "bytes"
21 "fmt"
22 "math"
23 "reflect"
24 "runtime"
25 "strings"
26 "testing"
27 "time"
28
29 "github.com/google/go-cmp/cmp"
30 "github.com/google/go-cmp/cmp/cmpopts"
31 "github.com/stretchr/testify/assert"
32 "github.com/stretchr/testify/require"
33 "google.golang.org/protobuf/proto"
34 v1 "k8s.io/api/core/v1"
35 "k8s.io/apimachinery/pkg/api/resource"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/util/intstr"
38 "k8s.io/apimachinery/pkg/util/sets"
39 "k8s.io/apimachinery/pkg/util/validation"
40 "k8s.io/apimachinery/pkg/util/validation/field"
41 utilfeature "k8s.io/apiserver/pkg/util/feature"
42 "k8s.io/component-base/featuregate"
43 featuregatetesting "k8s.io/component-base/featuregate/testing"
44 kubeletapis "k8s.io/kubelet/pkg/apis"
45 "k8s.io/kubernetes/pkg/apis/core"
46 "k8s.io/kubernetes/pkg/capabilities"
47 "k8s.io/kubernetes/pkg/features"
48 utilpointer "k8s.io/utils/pointer"
49 "k8s.io/utils/ptr"
50 )
51
52 const (
53 dnsLabelErrMsg = "a lowercase RFC 1123 label must consist of"
54 dnsSubdomainLabelErrMsg = "a lowercase RFC 1123 subdomain"
55 envVarNameErrMsg = "a valid environment variable name must consist of"
56 relaxedEnvVarNameFmtErrMsg string = "a valid environment variable name must consist only of printable ASCII characters other than '='"
57 defaultGracePeriod = int64(30)
58 noUserNamespace = false
59 )
60
61 var (
62 containerRestartPolicyAlways = core.ContainerRestartPolicyAlways
63 containerRestartPolicyOnFailure = core.ContainerRestartPolicy("OnFailure")
64 containerRestartPolicyNever = core.ContainerRestartPolicy("Never")
65 containerRestartPolicyInvalid = core.ContainerRestartPolicy("invalid")
66 containerRestartPolicyEmpty = core.ContainerRestartPolicy("")
67 )
68
69 type topologyPair struct {
70 key string
71 value string
72 }
73
74 func line() string {
75 _, _, line, ok := runtime.Caller(1)
76 var s string
77 if ok {
78 s = fmt.Sprintf("%d", line)
79 } else {
80 s = "<??>"
81 }
82 return s
83 }
84
85 func prettyErrorList(errs field.ErrorList) string {
86 var s string
87 for _, e := range errs {
88 s += fmt.Sprintf("\t%s\n", e)
89 }
90 return s
91 }
92
93 func newHostPathType(pathType string) *core.HostPathType {
94 hostPathType := new(core.HostPathType)
95 *hostPathType = core.HostPathType(pathType)
96 return hostPathType
97 }
98
99 func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *core.PersistentVolume {
100 objMeta := metav1.ObjectMeta{Name: name}
101 if namespace != "" {
102 objMeta.Namespace = namespace
103 }
104
105 return &core.PersistentVolume{
106 ObjectMeta: objMeta,
107 Spec: spec,
108 }
109 }
110
111 func TestValidatePersistentVolumes(t *testing.T) {
112 validMode := core.PersistentVolumeFilesystem
113 invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
114 scenarios := map[string]struct {
115 isExpectedFailure bool
116 enableVolumeAttributesClass bool
117 volume *core.PersistentVolume
118 }{
119 "good-volume": {
120 isExpectedFailure: false,
121 volume: testVolume("foo", "", core.PersistentVolumeSpec{
122 Capacity: core.ResourceList{
123 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
124 },
125 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
126 PersistentVolumeSource: core.PersistentVolumeSource{
127 HostPath: &core.HostPathVolumeSource{
128 Path: "/foo",
129 Type: newHostPathType(string(core.HostPathDirectory)),
130 },
131 },
132 }),
133 },
134 "good-volume-with-capacity-unit": {
135 isExpectedFailure: false,
136 volume: testVolume("foo", "", core.PersistentVolumeSpec{
137 Capacity: core.ResourceList{
138 core.ResourceName(core.ResourceStorage): resource.MustParse("10Gi"),
139 },
140 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
141 PersistentVolumeSource: core.PersistentVolumeSource{
142 HostPath: &core.HostPathVolumeSource{
143 Path: "/foo",
144 Type: newHostPathType(string(core.HostPathDirectory)),
145 },
146 },
147 }),
148 },
149 "good-volume-without-capacity-unit": {
150 isExpectedFailure: false,
151 volume: testVolume("foo", "", core.PersistentVolumeSpec{
152 Capacity: core.ResourceList{
153 core.ResourceName(core.ResourceStorage): resource.MustParse("10"),
154 },
155 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
156 PersistentVolumeSource: core.PersistentVolumeSource{
157 HostPath: &core.HostPathVolumeSource{
158 Path: "/foo",
159 Type: newHostPathType(string(core.HostPathDirectory)),
160 },
161 },
162 }),
163 },
164 "good-volume-with-storage-class": {
165 isExpectedFailure: false,
166 volume: testVolume("foo", "", core.PersistentVolumeSpec{
167 Capacity: core.ResourceList{
168 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
169 },
170 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
171 PersistentVolumeSource: core.PersistentVolumeSource{
172 HostPath: &core.HostPathVolumeSource{
173 Path: "/foo",
174 Type: newHostPathType(string(core.HostPathDirectory)),
175 },
176 },
177 StorageClassName: "valid",
178 }),
179 },
180 "good-volume-with-retain-policy": {
181 isExpectedFailure: false,
182 volume: testVolume("foo", "", core.PersistentVolumeSpec{
183 Capacity: core.ResourceList{
184 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
185 },
186 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
187 PersistentVolumeSource: core.PersistentVolumeSource{
188 HostPath: &core.HostPathVolumeSource{
189 Path: "/foo",
190 Type: newHostPathType(string(core.HostPathDirectory)),
191 },
192 },
193 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRetain,
194 }),
195 },
196 "good-volume-with-volume-mode": {
197 isExpectedFailure: false,
198 volume: testVolume("foo", "", core.PersistentVolumeSpec{
199 Capacity: core.ResourceList{
200 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
201 },
202 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
203 PersistentVolumeSource: core.PersistentVolumeSource{
204 HostPath: &core.HostPathVolumeSource{
205 Path: "/foo",
206 Type: newHostPathType(string(core.HostPathDirectory)),
207 },
208 },
209 VolumeMode: &validMode,
210 }),
211 },
212 "invalid-accessmode": {
213 isExpectedFailure: true,
214 volume: testVolume("foo", "", core.PersistentVolumeSpec{
215 Capacity: core.ResourceList{
216 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
217 },
218 AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
219 PersistentVolumeSource: core.PersistentVolumeSource{
220 HostPath: &core.HostPathVolumeSource{
221 Path: "/foo",
222 Type: newHostPathType(string(core.HostPathDirectory)),
223 },
224 },
225 }),
226 },
227 "invalid-reclaimpolicy": {
228 isExpectedFailure: true,
229 volume: testVolume("foo", "", core.PersistentVolumeSpec{
230 Capacity: core.ResourceList{
231 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
232 },
233 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
234 PersistentVolumeSource: core.PersistentVolumeSource{
235 HostPath: &core.HostPathVolumeSource{
236 Path: "/foo",
237 Type: newHostPathType(string(core.HostPathDirectory)),
238 },
239 },
240 PersistentVolumeReclaimPolicy: "fakeReclaimPolicy",
241 }),
242 },
243 "invalid-volume-mode": {
244 isExpectedFailure: true,
245 volume: testVolume("foo", "", core.PersistentVolumeSpec{
246 Capacity: core.ResourceList{
247 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
248 },
249 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
250 PersistentVolumeSource: core.PersistentVolumeSource{
251 HostPath: &core.HostPathVolumeSource{
252 Path: "/foo",
253 Type: newHostPathType(string(core.HostPathDirectory)),
254 },
255 },
256 VolumeMode: &invalidMode,
257 }),
258 },
259 "with-read-write-once-pod": {
260 isExpectedFailure: false,
261 volume: testVolume("foo", "", core.PersistentVolumeSpec{
262 Capacity: core.ResourceList{
263 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
264 },
265 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
266 PersistentVolumeSource: core.PersistentVolumeSource{
267 HostPath: &core.HostPathVolumeSource{
268 Path: "/foo",
269 Type: newHostPathType(string(core.HostPathDirectory)),
270 },
271 },
272 }),
273 },
274 "with-read-write-once-pod-and-others": {
275 isExpectedFailure: true,
276 volume: testVolume("foo", "", core.PersistentVolumeSpec{
277 Capacity: core.ResourceList{
278 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
279 },
280 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
281 PersistentVolumeSource: core.PersistentVolumeSource{
282 HostPath: &core.HostPathVolumeSource{
283 Path: "/foo",
284 Type: newHostPathType(string(core.HostPathDirectory)),
285 },
286 },
287 }),
288 },
289 "unexpected-namespace": {
290 isExpectedFailure: true,
291 volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{
292 Capacity: core.ResourceList{
293 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
294 },
295 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
296 PersistentVolumeSource: core.PersistentVolumeSource{
297 HostPath: &core.HostPathVolumeSource{
298 Path: "/foo",
299 Type: newHostPathType(string(core.HostPathDirectory)),
300 },
301 },
302 }),
303 },
304 "missing-volume-source": {
305 isExpectedFailure: true,
306 volume: testVolume("foo", "", core.PersistentVolumeSpec{
307 Capacity: core.ResourceList{
308 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
309 },
310 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
311 }),
312 },
313 "bad-name": {
314 isExpectedFailure: true,
315 volume: testVolume("123*Bad(Name", "unexpected-namespace", core.PersistentVolumeSpec{
316 Capacity: core.ResourceList{
317 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
318 },
319 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
320 PersistentVolumeSource: core.PersistentVolumeSource{
321 HostPath: &core.HostPathVolumeSource{
322 Path: "/foo",
323 Type: newHostPathType(string(core.HostPathDirectory)),
324 },
325 },
326 }),
327 },
328 "missing-name": {
329 isExpectedFailure: true,
330 volume: testVolume("", "", core.PersistentVolumeSpec{
331 Capacity: core.ResourceList{
332 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
333 },
334 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
335 PersistentVolumeSource: core.PersistentVolumeSource{
336 HostPath: &core.HostPathVolumeSource{
337 Path: "/foo",
338 Type: newHostPathType(string(core.HostPathDirectory)),
339 },
340 },
341 }),
342 },
343 "missing-capacity": {
344 isExpectedFailure: true,
345 volume: testVolume("foo", "", core.PersistentVolumeSpec{
346 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
347 PersistentVolumeSource: core.PersistentVolumeSource{
348 HostPath: &core.HostPathVolumeSource{
349 Path: "/foo",
350 Type: newHostPathType(string(core.HostPathDirectory)),
351 },
352 },
353 }),
354 },
355 "bad-volume-zero-capacity": {
356 isExpectedFailure: true,
357 volume: testVolume("foo", "", core.PersistentVolumeSpec{
358 Capacity: core.ResourceList{
359 core.ResourceName(core.ResourceStorage): resource.MustParse("0"),
360 },
361 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
362 PersistentVolumeSource: core.PersistentVolumeSource{
363 HostPath: &core.HostPathVolumeSource{
364 Path: "/foo",
365 Type: newHostPathType(string(core.HostPathDirectory)),
366 },
367 },
368 }),
369 },
370 "missing-accessmodes": {
371 isExpectedFailure: true,
372 volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{
373 Capacity: core.ResourceList{
374 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
375 },
376 PersistentVolumeSource: core.PersistentVolumeSource{
377 HostPath: &core.HostPathVolumeSource{
378 Path: "/foo",
379 Type: newHostPathType(string(core.HostPathDirectory)),
380 },
381 },
382 }),
383 },
384 "too-many-sources": {
385 isExpectedFailure: true,
386 volume: testVolume("foo", "", core.PersistentVolumeSpec{
387 Capacity: core.ResourceList{
388 core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
389 },
390 PersistentVolumeSource: core.PersistentVolumeSource{
391 HostPath: &core.HostPathVolumeSource{
392 Path: "/foo",
393 Type: newHostPathType(string(core.HostPathDirectory)),
394 },
395 GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"},
396 },
397 }),
398 },
399 "host mount of / with recycle reclaim policy": {
400 isExpectedFailure: true,
401 volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
402 Capacity: core.ResourceList{
403 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
404 },
405 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
406 PersistentVolumeSource: core.PersistentVolumeSource{
407 HostPath: &core.HostPathVolumeSource{
408 Path: "/",
409 Type: newHostPathType(string(core.HostPathDirectory)),
410 },
411 },
412 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
413 }),
414 },
415 "host mount of / with recycle reclaim policy 2": {
416 isExpectedFailure: true,
417 volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
418 Capacity: core.ResourceList{
419 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
420 },
421 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
422 PersistentVolumeSource: core.PersistentVolumeSource{
423 HostPath: &core.HostPathVolumeSource{
424 Path: "/a/..",
425 Type: newHostPathType(string(core.HostPathDirectory)),
426 },
427 },
428 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
429 }),
430 },
431 "invalid-storage-class-name": {
432 isExpectedFailure: true,
433 volume: testVolume("invalid-storage-class-name", "", core.PersistentVolumeSpec{
434 Capacity: core.ResourceList{
435 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
436 },
437 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
438 PersistentVolumeSource: core.PersistentVolumeSource{
439 HostPath: &core.HostPathVolumeSource{
440 Path: "/foo",
441 Type: newHostPathType(string(core.HostPathDirectory)),
442 },
443 },
444 StorageClassName: "-invalid-",
445 }),
446 },
447 "bad-hostpath-volume-backsteps": {
448 isExpectedFailure: true,
449 volume: testVolume("foo", "", core.PersistentVolumeSpec{
450 Capacity: core.ResourceList{
451 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
452 },
453 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
454 PersistentVolumeSource: core.PersistentVolumeSource{
455 HostPath: &core.HostPathVolumeSource{
456 Path: "/foo/..",
457 Type: newHostPathType(string(core.HostPathDirectory)),
458 },
459 },
460 StorageClassName: "backstep-hostpath",
461 }),
462 },
463 "volume-node-affinity": {
464 isExpectedFailure: false,
465 volume: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
466 },
467 "volume-empty-node-affinity": {
468 isExpectedFailure: true,
469 volume: testVolumeWithNodeAffinity(&core.VolumeNodeAffinity{}),
470 },
471 "volume-bad-node-affinity": {
472 isExpectedFailure: true,
473 volume: testVolumeWithNodeAffinity(
474 &core.VolumeNodeAffinity{
475 Required: &core.NodeSelector{
476 NodeSelectorTerms: []core.NodeSelectorTerm{{
477 MatchExpressions: []core.NodeSelectorRequirement{{
478 Operator: core.NodeSelectorOpIn,
479 Values: []string{"test-label-value"},
480 }},
481 }},
482 },
483 }),
484 },
485 "invalid-volume-attributes-class-name": {
486 isExpectedFailure: true,
487 enableVolumeAttributesClass: true,
488 volume: testVolume("invalid-volume-attributes-class-name", "", core.PersistentVolumeSpec{
489 Capacity: core.ResourceList{
490 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
491 },
492 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
493 PersistentVolumeSource: core.PersistentVolumeSource{
494 HostPath: &core.HostPathVolumeSource{
495 Path: "/foo",
496 Type: newHostPathType(string(core.HostPathDirectory)),
497 },
498 },
499 StorageClassName: "invalid",
500 VolumeAttributesClassName: ptr.To("-invalid-"),
501 }),
502 },
503 "invalid-empty-volume-attributes-class-name": {
504 isExpectedFailure: true,
505 enableVolumeAttributesClass: true,
506 volume: testVolume("invalid-empty-volume-attributes-class-name", "", core.PersistentVolumeSpec{
507 Capacity: core.ResourceList{
508 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
509 },
510 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
511 PersistentVolumeSource: core.PersistentVolumeSource{
512 HostPath: &core.HostPathVolumeSource{
513 Path: "/foo",
514 Type: newHostPathType(string(core.HostPathDirectory)),
515 },
516 },
517 StorageClassName: "invalid",
518 VolumeAttributesClassName: ptr.To(""),
519 }),
520 },
521 "volume-with-good-volume-attributes-class-and-matched-volume-resource-when-feature-gate-is-on": {
522 isExpectedFailure: false,
523 enableVolumeAttributesClass: true,
524 volume: testVolume("foo", "", core.PersistentVolumeSpec{
525 Capacity: core.ResourceList{
526 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
527 },
528 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
529 PersistentVolumeSource: core.PersistentVolumeSource{
530 CSI: &core.CSIPersistentVolumeSource{
531 Driver: "test-driver",
532 VolumeHandle: "test-123",
533 },
534 },
535 StorageClassName: "valid",
536 VolumeAttributesClassName: ptr.To("valid"),
537 }),
538 },
539 "volume-with-good-volume-attributes-class-and-mismatched-volume-resource-when-feature-gate-is-on": {
540 isExpectedFailure: true,
541 enableVolumeAttributesClass: true,
542 volume: testVolume("foo", "", core.PersistentVolumeSpec{
543 Capacity: core.ResourceList{
544 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
545 },
546 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
547 PersistentVolumeSource: core.PersistentVolumeSource{
548 HostPath: &core.HostPathVolumeSource{
549 Path: "/foo",
550 Type: newHostPathType(string(core.HostPathDirectory)),
551 },
552 },
553 StorageClassName: "valid",
554 VolumeAttributesClassName: ptr.To("valid"),
555 }),
556 },
557 }
558
559 for name, scenario := range scenarios {
560 t.Run(name, func(t *testing.T) {
561 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
562
563 opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
564 errs := ValidatePersistentVolume(scenario.volume, opts)
565 if len(errs) == 0 && scenario.isExpectedFailure {
566 t.Errorf("Unexpected success for scenario: %s", name)
567 }
568 if len(errs) > 0 && !scenario.isExpectedFailure {
569 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
570 }
571 })
572 }
573
574 }
575
576 func TestValidatePersistentVolumeSpec(t *testing.T) {
577 fsmode := core.PersistentVolumeFilesystem
578 blockmode := core.PersistentVolumeBlock
579 scenarios := map[string]struct {
580 isExpectedFailure bool
581 isInlineSpec bool
582 pvSpec *core.PersistentVolumeSpec
583 }{
584 "pv-pvspec-valid": {
585 isExpectedFailure: false,
586 isInlineSpec: false,
587 pvSpec: &core.PersistentVolumeSpec{
588 Capacity: core.ResourceList{
589 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
590 },
591 StorageClassName: "testclass",
592 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
593 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
594 PersistentVolumeSource: core.PersistentVolumeSource{
595 HostPath: &core.HostPathVolumeSource{
596 Path: "/foo",
597 Type: newHostPathType(string(core.HostPathDirectory)),
598 },
599 },
600 VolumeMode: &fsmode,
601 NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
602 },
603 },
604 "inline-pvspec-with-capacity": {
605 isExpectedFailure: true,
606 isInlineSpec: true,
607 pvSpec: &core.PersistentVolumeSpec{
608 Capacity: core.ResourceList{
609 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
610 },
611 PersistentVolumeSource: core.PersistentVolumeSource{
612 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
613 },
614 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
615 },
616 },
617 "inline-pvspec-with-podSec": {
618 isExpectedFailure: true,
619 isInlineSpec: true,
620 pvSpec: &core.PersistentVolumeSpec{
621 PersistentVolumeSource: core.PersistentVolumeSource{
622 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
623 },
624 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
625 StorageClassName: "testclass",
626 },
627 },
628 "inline-pvspec-with-non-fs-volume-mode": {
629 isExpectedFailure: true,
630 isInlineSpec: true,
631 pvSpec: &core.PersistentVolumeSpec{
632 PersistentVolumeSource: core.PersistentVolumeSource{
633 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
634 },
635 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
636 VolumeMode: &blockmode,
637 },
638 },
639 "inline-pvspec-with-non-retain-reclaim-policy": {
640 isExpectedFailure: true,
641 isInlineSpec: true,
642 pvSpec: &core.PersistentVolumeSpec{
643 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
644 PersistentVolumeSource: core.PersistentVolumeSource{
645 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
646 },
647 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
648 },
649 },
650 "inline-pvspec-with-node-affinity": {
651 isExpectedFailure: true,
652 isInlineSpec: true,
653 pvSpec: &core.PersistentVolumeSpec{
654 PersistentVolumeSource: core.PersistentVolumeSource{
655 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
656 },
657 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
658 NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
659 },
660 },
661 "inline-pvspec-with-non-csi-source": {
662 isExpectedFailure: true,
663 isInlineSpec: true,
664 pvSpec: &core.PersistentVolumeSpec{
665 PersistentVolumeSource: core.PersistentVolumeSource{
666 HostPath: &core.HostPathVolumeSource{
667 Path: "/foo",
668 Type: newHostPathType(string(core.HostPathDirectory)),
669 },
670 },
671 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
672 },
673 },
674 "inline-pvspec-valid-with-access-modes-and-mount-options": {
675 isExpectedFailure: false,
676 isInlineSpec: true,
677 pvSpec: &core.PersistentVolumeSpec{
678 PersistentVolumeSource: core.PersistentVolumeSource{
679 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
680 },
681 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
682 MountOptions: []string{"soft", "read-write"},
683 },
684 },
685 "inline-pvspec-valid-with-access-modes": {
686 isExpectedFailure: false,
687 isInlineSpec: true,
688 pvSpec: &core.PersistentVolumeSpec{
689 PersistentVolumeSource: core.PersistentVolumeSource{
690 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
691 },
692 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
693 },
694 },
695 "inline-pvspec-with-missing-acess-modes": {
696 isExpectedFailure: true,
697 isInlineSpec: true,
698 pvSpec: &core.PersistentVolumeSpec{
699 PersistentVolumeSource: core.PersistentVolumeSource{
700 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
701 },
702 MountOptions: []string{"soft", "read-write"},
703 },
704 },
705 }
706 for name, scenario := range scenarios {
707 opts := PersistentVolumeSpecValidationOptions{}
708 errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"), opts)
709 if len(errs) == 0 && scenario.isExpectedFailure {
710 t.Errorf("Unexpected success for scenario: %s", name)
711 }
712 if len(errs) > 0 && !scenario.isExpectedFailure {
713 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
714 }
715 }
716 }
717
718 func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
719 validVolume := testVolume("foo", "", core.PersistentVolumeSpec{
720 Capacity: core.ResourceList{
721 core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
722 },
723 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
724 PersistentVolumeSource: core.PersistentVolumeSource{
725 HostPath: &core.HostPathVolumeSource{
726 Path: "/foo",
727 Type: newHostPathType(string(core.HostPathDirectory)),
728 },
729 },
730 StorageClassName: "valid",
731 })
732 validPvSourceNoUpdate := validVolume.DeepCopy()
733 invalidPvSourceUpdateType := validVolume.DeepCopy()
734 invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
735 FlexVolume: &core.FlexPersistentVolumeSource{
736 Driver: "kubernetes.io/blue",
737 FSType: "ext4",
738 },
739 }
740 invalidPvSourceUpdateDeep := validVolume.DeepCopy()
741 invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
742 HostPath: &core.HostPathVolumeSource{
743 Path: "/updated",
744 Type: newHostPathType(string(core.HostPathDirectory)),
745 },
746 }
747
748 validCSIVolume := testVolume("csi-volume", "", core.PersistentVolumeSpec{
749 Capacity: core.ResourceList{
750 core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
751 },
752 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
753 PersistentVolumeSource: core.PersistentVolumeSource{
754 CSI: &core.CSIPersistentVolumeSource{
755 Driver: "come.google.gcepd",
756 VolumeHandle: "foobar",
757 },
758 },
759 StorageClassName: "gp2",
760 })
761
762 expandSecretRef := &core.SecretReference{
763 Name: "expansion-secret",
764 Namespace: "default",
765 }
766
767
768 shortSecretName := "key-name"
769 shortSecretRef := &core.SecretReference{
770 Name: shortSecretName,
771 Namespace: "default",
772 }
773
774
775 longSecretName := "key-name.example.com"
776 longSecretRef := &core.SecretReference{
777 Name: longSecretName,
778 Namespace: "default",
779 }
780
781
782 inValidSecretRef := &core.SecretReference{
783 Name: "",
784 Namespace: "",
785 }
786 invalidSecretRefmissingName := &core.SecretReference{
787 Name: "",
788 Namespace: "default",
789 }
790 invalidSecretRefmissingNamespace := &core.SecretReference{
791 Name: "invalidnamespace",
792 Namespace: "",
793 }
794
795 scenarios := map[string]struct {
796 isExpectedFailure bool
797 oldVolume *core.PersistentVolume
798 newVolume *core.PersistentVolume
799 }{
800 "condition-no-update": {
801 isExpectedFailure: false,
802 oldVolume: validVolume,
803 newVolume: validPvSourceNoUpdate,
804 },
805 "condition-update-source-type": {
806 isExpectedFailure: true,
807 oldVolume: validVolume,
808 newVolume: invalidPvSourceUpdateType,
809 },
810 "condition-update-source-deep": {
811 isExpectedFailure: true,
812 oldVolume: validVolume,
813 newVolume: invalidPvSourceUpdateDeep,
814 },
815 "csi-expansion-enabled-with-pv-secret": {
816 isExpectedFailure: false,
817 oldVolume: validCSIVolume,
818 newVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"),
819 },
820 "csi-expansion-enabled-with-old-pv-secret": {
821 isExpectedFailure: true,
822 oldVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"),
823 newVolume: getCSIVolumeWithSecret(validCSIVolume, &core.SecretReference{
824 Name: "foo-secret",
825 Namespace: "default",
826 }, "controllerExpand"),
827 },
828 "csi-expansion-enabled-with-shortSecretRef": {
829 isExpectedFailure: false,
830 oldVolume: validCSIVolume,
831 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
832 },
833 "csi-expansion-enabled-with-longSecretRef": {
834 isExpectedFailure: false,
835 oldVolume: validCSIVolume,
836 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
837 },
838 "csi-expansion-enabled-from-shortSecretRef-to-shortSecretRef": {
839 isExpectedFailure: false,
840 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
841 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
842 },
843 "csi-expansion-enabled-from-shortSecretRef-to-longSecretRef": {
844 isExpectedFailure: true,
845 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
846 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
847 },
848 "csi-expansion-enabled-from-longSecretRef-to-longSecretRef": {
849 isExpectedFailure: false,
850 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
851 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
852 },
853 "csi-cntrlpublish-enabled-with-shortSecretRef": {
854 isExpectedFailure: true,
855 oldVolume: validCSIVolume,
856 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
857 },
858 "csi-cntrlpublish-enabled-with-longSecretRef": {
859 isExpectedFailure: true,
860 oldVolume: validCSIVolume,
861 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
862 },
863 "csi-cntrlpublish-enabled-from-shortSecretRef-to-shortSecretRef": {
864 isExpectedFailure: false,
865 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
866 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
867 },
868 "csi-cntrlpublish-enabled-from-shortSecretRef-to-longSecretRef": {
869 isExpectedFailure: true,
870 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
871 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
872 },
873 "csi-cntrlpublish-enabled-from-longSecretRef-to-longSecretRef": {
874 isExpectedFailure: false,
875 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
876 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
877 },
878 "csi-nodepublish-enabled-with-shortSecretRef": {
879 isExpectedFailure: true,
880 oldVolume: validCSIVolume,
881 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
882 },
883 "csi-nodepublish-enabled-with-longSecretRef": {
884 isExpectedFailure: true,
885 oldVolume: validCSIVolume,
886 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
887 },
888 "csi-nodepublish-enabled-from-shortSecretRef-to-shortSecretRef": {
889 isExpectedFailure: false,
890 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
891 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
892 },
893 "csi-nodepublish-enabled-from-shortSecretRef-to-longSecretRef": {
894 isExpectedFailure: true,
895 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
896 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
897 },
898 "csi-nodepublish-enabled-from-longSecretRef-to-longSecretRef": {
899 isExpectedFailure: false,
900 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
901 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
902 },
903 "csi-nodestage-enabled-with-shortSecretRef": {
904 isExpectedFailure: true,
905 oldVolume: validCSIVolume,
906 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
907 },
908 "csi-nodestage-enabled-with-longSecretRef": {
909 isExpectedFailure: true,
910 oldVolume: validCSIVolume,
911 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
912 },
913 "csi-nodestage-enabled-from-shortSecretRef-to-longSecretRef": {
914 isExpectedFailure: true,
915 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
916 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
917 },
918
919
920
921
922
923 "csi-nodestage-enabled-from-invalidSecretRef-to-invalidSecretRef": {
924 isExpectedFailure: false,
925 oldVolume: getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"),
926 newVolume: getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"),
927 },
928 "csi-nodestage-enabled-from-invalidSecretRefmissingname-to-invalidSecretRefmissingname": {
929 isExpectedFailure: false,
930 oldVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"),
931 newVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"),
932 },
933 "csi-nodestage-enabled-from-invalidSecretRefmissingnamespace-to-invalidSecretRefmissingnamespace": {
934 isExpectedFailure: false,
935 oldVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"),
936 newVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"),
937 },
938 "csi-nodestage-enabled-from-shortSecretRef-to-shortSecretRef": {
939 isExpectedFailure: false,
940 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
941 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
942 },
943 "csi-nodestage-enabled-from-longSecretRef-to-longSecretRef": {
944 isExpectedFailure: false,
945 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
946 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
947 },
948 }
949 for name, scenario := range scenarios {
950 opts := ValidationOptionsForPersistentVolume(scenario.newVolume, scenario.oldVolume)
951 errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume, opts)
952 if len(errs) == 0 && scenario.isExpectedFailure {
953 t.Errorf("Unexpected success for scenario: %s", name)
954 }
955 if len(errs) > 0 && !scenario.isExpectedFailure {
956 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
957 }
958 }
959 }
960
961 func TestValidationOptionsForPersistentVolume(t *testing.T) {
962 tests := map[string]struct {
963 oldPv *core.PersistentVolume
964 enableVolumeAttributesClass bool
965 expectValidationOpts PersistentVolumeSpecValidationOptions
966 }{
967 "nil old pv": {
968 oldPv: nil,
969 expectValidationOpts: PersistentVolumeSpecValidationOptions{},
970 },
971 "nil old pv and feature-gate VolumeAttrributesClass is on": {
972 oldPv: nil,
973 enableVolumeAttributesClass: true,
974 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
975 },
976 "nil old pv and feature-gate VolumeAttrributesClass is off": {
977 oldPv: nil,
978 enableVolumeAttributesClass: false,
979 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: false},
980 },
981 "old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is on": {
982 oldPv: &core.PersistentVolume{
983 Spec: core.PersistentVolumeSpec{
984 VolumeAttributesClassName: ptr.To("foo"),
985 },
986 },
987 enableVolumeAttributesClass: true,
988 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
989 },
990 "old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is off": {
991 oldPv: &core.PersistentVolume{
992 Spec: core.PersistentVolumeSpec{
993 VolumeAttributesClassName: ptr.To("foo"),
994 },
995 },
996 enableVolumeAttributesClass: false,
997 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
998 },
999 }
1000
1001 for name, tc := range tests {
1002 t.Run(name, func(t *testing.T) {
1003 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
1004
1005 opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv)
1006 if opts != tc.expectValidationOpts {
1007 t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
1008 }
1009 })
1010 }
1011 }
1012
1013 func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference, secretfield string) *core.PersistentVolume {
1014 pvCopy := pv.DeepCopy()
1015 switch secretfield {
1016 case "controllerExpand":
1017 pvCopy.Spec.CSI.ControllerExpandSecretRef = secret
1018 case "controllerPublish":
1019 pvCopy.Spec.CSI.ControllerPublishSecretRef = secret
1020 case "nodePublish":
1021 pvCopy.Spec.CSI.NodePublishSecretRef = secret
1022 case "nodeStage":
1023 pvCopy.Spec.CSI.NodeStageSecretRef = secret
1024 default:
1025 panic("unknown string")
1026 }
1027
1028 return pvCopy
1029 }
1030
1031 func pvcWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaim {
1032 return &core.PersistentVolumeClaim{
1033 Spec: core.PersistentVolumeClaimSpec{
1034 VolumeAttributesClassName: vacName,
1035 },
1036 }
1037 }
1038
1039 func pvcWithDataSource(dataSource *core.TypedLocalObjectReference) *core.PersistentVolumeClaim {
1040 return &core.PersistentVolumeClaim{
1041 Spec: core.PersistentVolumeClaimSpec{
1042 DataSource: dataSource,
1043 },
1044 }
1045 }
1046 func pvcWithDataSourceRef(ref *core.TypedObjectReference) *core.PersistentVolumeClaim {
1047 return &core.PersistentVolumeClaim{
1048 Spec: core.PersistentVolumeClaimSpec{
1049 DataSourceRef: ref,
1050 },
1051 }
1052 }
1053
1054 func pvcTemplateWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimTemplate {
1055 return &core.PersistentVolumeClaimTemplate{
1056 Spec: core.PersistentVolumeClaimSpec{
1057 VolumeAttributesClassName: vacName,
1058 },
1059 }
1060 }
1061
1062 func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
1063 return core.PersistentVolumeSpec{
1064 Capacity: core.ResourceList{
1065 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1066 },
1067 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
1068 PersistentVolumeSource: core.PersistentVolumeSource{
1069 Local: &core.LocalVolumeSource{
1070 Path: path,
1071 },
1072 },
1073 NodeAffinity: affinity,
1074 StorageClassName: "test-storage-class",
1075 }
1076 }
1077
1078 func TestValidateLocalVolumes(t *testing.T) {
1079 scenarios := map[string]struct {
1080 isExpectedFailure bool
1081 volume *core.PersistentVolume
1082 }{
1083 "alpha invalid local volume nil annotations": {
1084 isExpectedFailure: true,
1085 volume: testVolume(
1086 "invalid-local-volume-nil-annotations",
1087 "",
1088 testLocalVolume("/foo", nil)),
1089 },
1090 "valid local volume": {
1091 isExpectedFailure: false,
1092 volume: testVolume("valid-local-volume", "",
1093 testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
1094 },
1095 "invalid local volume no node affinity": {
1096 isExpectedFailure: true,
1097 volume: testVolume("invalid-local-volume-no-node-affinity", "",
1098 testLocalVolume("/foo", nil)),
1099 },
1100 "invalid local volume empty path": {
1101 isExpectedFailure: true,
1102 volume: testVolume("invalid-local-volume-empty-path", "",
1103 testLocalVolume("", simpleVolumeNodeAffinity("foo", "bar"))),
1104 },
1105 "invalid-local-volume-backsteps": {
1106 isExpectedFailure: true,
1107 volume: testVolume("foo", "",
1108 testLocalVolume("/foo/..", simpleVolumeNodeAffinity("foo", "bar"))),
1109 },
1110 "valid-local-volume-relative-path": {
1111 isExpectedFailure: false,
1112 volume: testVolume("foo", "",
1113 testLocalVolume("foo", simpleVolumeNodeAffinity("foo", "bar"))),
1114 },
1115 }
1116
1117 for name, scenario := range scenarios {
1118 opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
1119 errs := ValidatePersistentVolume(scenario.volume, opts)
1120 if len(errs) == 0 && scenario.isExpectedFailure {
1121 t.Errorf("Unexpected success for scenario: %s", name)
1122 }
1123 if len(errs) > 0 && !scenario.isExpectedFailure {
1124 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
1125 }
1126 }
1127 }
1128
1129 func testVolumeWithVolumeAttributesClass(vacName *string) *core.PersistentVolume {
1130 return testVolume("test-volume-with-volume-attributes-class", "",
1131 core.PersistentVolumeSpec{
1132 Capacity: core.ResourceList{
1133 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1134 },
1135 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
1136 PersistentVolumeSource: core.PersistentVolumeSource{
1137 CSI: &core.CSIPersistentVolumeSource{
1138 Driver: "test-driver",
1139 VolumeHandle: "test-123",
1140 },
1141 },
1142 StorageClassName: "test-storage-class",
1143 VolumeAttributesClassName: vacName,
1144 })
1145 }
1146
1147 func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume {
1148 return testVolume("test-affinity-volume", "",
1149 core.PersistentVolumeSpec{
1150 Capacity: core.ResourceList{
1151 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1152 },
1153 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
1154 PersistentVolumeSource: core.PersistentVolumeSource{
1155 GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
1156 PDName: "foo",
1157 },
1158 },
1159 StorageClassName: "test-storage-class",
1160 NodeAffinity: affinity,
1161 })
1162 }
1163
1164 func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity {
1165 return &core.VolumeNodeAffinity{
1166 Required: &core.NodeSelector{
1167 NodeSelectorTerms: []core.NodeSelectorTerm{{
1168 MatchExpressions: []core.NodeSelectorRequirement{{
1169 Key: key,
1170 Operator: core.NodeSelectorOpIn,
1171 Values: []string{value},
1172 }},
1173 }},
1174 },
1175 }
1176 }
1177
1178 func multipleVolumeNodeAffinity(terms [][]topologyPair) *core.VolumeNodeAffinity {
1179 nodeSelectorTerms := []core.NodeSelectorTerm{}
1180 for _, term := range terms {
1181 matchExpressions := []core.NodeSelectorRequirement{}
1182 for _, topology := range term {
1183 matchExpressions = append(matchExpressions, core.NodeSelectorRequirement{
1184 Key: topology.key,
1185 Operator: core.NodeSelectorOpIn,
1186 Values: []string{topology.value},
1187 })
1188 }
1189 nodeSelectorTerms = append(nodeSelectorTerms, core.NodeSelectorTerm{
1190 MatchExpressions: matchExpressions,
1191 })
1192 }
1193
1194 return &core.VolumeNodeAffinity{
1195 Required: &core.NodeSelector{
1196 NodeSelectorTerms: nodeSelectorTerms,
1197 },
1198 }
1199 }
1200
1201 func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
1202 scenarios := map[string]struct {
1203 isExpectedFailure bool
1204 oldPV *core.PersistentVolume
1205 newPV *core.PersistentVolume
1206 }{
1207 "nil-nothing-changed": {
1208 isExpectedFailure: false,
1209 oldPV: testVolumeWithNodeAffinity(nil),
1210 newPV: testVolumeWithNodeAffinity(nil),
1211 },
1212 "affinity-nothing-changed": {
1213 isExpectedFailure: false,
1214 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1215 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1216 },
1217 "affinity-changed": {
1218 isExpectedFailure: true,
1219 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1220 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar2")),
1221 },
1222 "affinity-non-beta-label-changed": {
1223 isExpectedFailure: true,
1224 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1225 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo2", "bar")),
1226 },
1227 "affinity-zone-beta-unchanged": {
1228 isExpectedFailure: false,
1229 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
1230 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
1231 },
1232 "affinity-zone-beta-label-to-GA": {
1233 isExpectedFailure: false,
1234 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
1235 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")),
1236 },
1237 "affinity-zone-beta-label-to-non-GA": {
1238 isExpectedFailure: true,
1239 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
1240 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1241 },
1242 "affinity-zone-GA-label-changed": {
1243 isExpectedFailure: true,
1244 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")),
1245 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
1246 },
1247 "affinity-region-beta-unchanged": {
1248 isExpectedFailure: false,
1249 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
1250 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
1251 },
1252 "affinity-region-beta-label-to-GA": {
1253 isExpectedFailure: false,
1254 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
1255 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")),
1256 },
1257 "affinity-region-beta-label-to-non-GA": {
1258 isExpectedFailure: true,
1259 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
1260 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1261 },
1262 "affinity-region-GA-label-changed": {
1263 isExpectedFailure: true,
1264 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")),
1265 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
1266 },
1267 "affinity-os-beta-label-unchanged": {
1268 isExpectedFailure: false,
1269 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
1270 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
1271 },
1272 "affinity-os-beta-label-to-GA": {
1273 isExpectedFailure: false,
1274 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
1275 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")),
1276 },
1277 "affinity-os-beta-label-to-non-GA": {
1278 isExpectedFailure: true,
1279 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
1280 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1281 },
1282 "affinity-os-GA-label-changed": {
1283 isExpectedFailure: true,
1284 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")),
1285 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
1286 },
1287 "affinity-arch-beta-label-unchanged": {
1288 isExpectedFailure: false,
1289 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
1290 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
1291 },
1292 "affinity-arch-beta-label-to-GA": {
1293 isExpectedFailure: false,
1294 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
1295 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")),
1296 },
1297 "affinity-arch-beta-label-to-non-GA": {
1298 isExpectedFailure: true,
1299 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
1300 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1301 },
1302 "affinity-arch-GA-label-changed": {
1303 isExpectedFailure: true,
1304 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")),
1305 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
1306 },
1307 "affinity-instanceType-beta-label-unchanged": {
1308 isExpectedFailure: false,
1309 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
1310 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
1311 },
1312 "affinity-instanceType-beta-label-to-GA": {
1313 isExpectedFailure: false,
1314 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
1315 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")),
1316 },
1317 "affinity-instanceType-beta-label-to-non-GA": {
1318 isExpectedFailure: true,
1319 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
1320 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1321 },
1322 "affinity-instanceType-GA-label-changed": {
1323 isExpectedFailure: true,
1324 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")),
1325 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
1326 },
1327 "affinity-same-terms-expressions-length-beta-to-GA-partially-changed": {
1328 isExpectedFailure: false,
1329 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1330 topologyPair{"foo", "bar"},
1331 }, {
1332 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1333 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
1334 }, {
1335 topologyPair{kubeletapis.LabelOS, "bar"},
1336 topologyPair{kubeletapis.LabelArch, "bar"},
1337 topologyPair{v1.LabelInstanceType, "bar"},
1338 },
1339 })),
1340 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1341 topologyPair{"foo", "bar"},
1342 }, {
1343 topologyPair{v1.LabelTopologyZone, "bar"},
1344 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
1345 }, {
1346 topologyPair{kubeletapis.LabelOS, "bar"},
1347 topologyPair{v1.LabelArchStable, "bar"},
1348 topologyPair{v1.LabelInstanceTypeStable, "bar"},
1349 },
1350 })),
1351 },
1352 "affinity-same-terms-expressions-length-beta-to-non-GA-partially-changed": {
1353 isExpectedFailure: true,
1354 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1355 topologyPair{"foo", "bar"},
1356 }, {
1357 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1358 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
1359 },
1360 })),
1361 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1362 topologyPair{"foo", "bar"},
1363 }, {
1364 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1365 topologyPair{"foo", "bar"},
1366 },
1367 })),
1368 },
1369 "affinity-same-terms-expressions-length-GA-partially-changed": {
1370 isExpectedFailure: true,
1371 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1372 topologyPair{"foo", "bar"},
1373 }, {
1374 topologyPair{v1.LabelTopologyZone, "bar"},
1375 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1376 topologyPair{v1.LabelOSStable, "bar"},
1377 },
1378 })),
1379 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1380 topologyPair{"foo", "bar"},
1381 }, {
1382 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1383 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1384 topologyPair{v1.LabelOSStable, "bar"},
1385 },
1386 })),
1387 },
1388 "affinity-same-terms-expressions-length-beta-fully-changed": {
1389 isExpectedFailure: false,
1390 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1391 topologyPair{"foo", "bar"},
1392 }, {
1393 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1394 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
1395 }, {
1396 topologyPair{kubeletapis.LabelOS, "bar"},
1397 topologyPair{kubeletapis.LabelArch, "bar"},
1398 topologyPair{v1.LabelInstanceType, "bar"},
1399 },
1400 })),
1401 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1402 topologyPair{"foo", "bar"},
1403 }, {
1404 topologyPair{v1.LabelTopologyZone, "bar"},
1405 topologyPair{v1.LabelTopologyRegion, "bar"},
1406 }, {
1407 topologyPair{v1.LabelOSStable, "bar"},
1408 topologyPair{v1.LabelArchStable, "bar"},
1409 topologyPair{v1.LabelInstanceTypeStable, "bar"},
1410 },
1411 })),
1412 },
1413 "affinity-same-terms-expressions-length-beta-GA-mixed-fully-changed": {
1414 isExpectedFailure: true,
1415 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1416 topologyPair{"foo", "bar"},
1417 }, {
1418 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1419 topologyPair{v1.LabelTopologyZone, "bar"},
1420 },
1421 })),
1422 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1423 topologyPair{"foo", "bar"},
1424 }, {
1425 topologyPair{v1.LabelTopologyZone, "bar"},
1426 topologyPair{v1.LabelFailureDomainBetaZone, "bar2"},
1427 },
1428 })),
1429 },
1430 "affinity-same-terms-length-different-expressions-length-beta-changed": {
1431 isExpectedFailure: true,
1432 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1433 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1434 },
1435 })),
1436 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1437 topologyPair{v1.LabelTopologyZone, "bar"},
1438 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
1439 },
1440 })),
1441 },
1442 "affinity-different-terms-expressions-length-beta-changed": {
1443 isExpectedFailure: true,
1444 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1445 topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
1446 },
1447 })),
1448 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
1449 topologyPair{v1.LabelTopologyZone, "bar"},
1450 }, {
1451 topologyPair{v1.LabelArchStable, "bar"},
1452 },
1453 })),
1454 },
1455 "nil-to-obj": {
1456 isExpectedFailure: false,
1457 oldPV: testVolumeWithNodeAffinity(nil),
1458 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1459 },
1460 "obj-to-nil": {
1461 isExpectedFailure: true,
1462 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
1463 newPV: testVolumeWithNodeAffinity(nil),
1464 },
1465 }
1466
1467 for name, scenario := range scenarios {
1468 originalNewPV := scenario.newPV.DeepCopy()
1469 originalOldPV := scenario.oldPV.DeepCopy()
1470 opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
1471 errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
1472 if len(errs) == 0 && scenario.isExpectedFailure {
1473 t.Errorf("Unexpected success for scenario: %s", name)
1474 }
1475 if len(errs) > 0 && !scenario.isExpectedFailure {
1476 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
1477 }
1478 if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
1479 t.Errorf("newPV was modified: %s", diff)
1480 }
1481 if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
1482 t.Errorf("oldPV was modified: %s", diff)
1483 }
1484 }
1485 }
1486
1487 func TestValidatePeristentVolumeAttributesClassUpdate(t *testing.T) {
1488 scenarios := map[string]struct {
1489 isExpectedFailure bool
1490 enableVolumeAttributesClass bool
1491 oldPV *core.PersistentVolume
1492 newPV *core.PersistentVolume
1493 }{
1494 "nil-nothing-changed": {
1495 isExpectedFailure: false,
1496 enableVolumeAttributesClass: true,
1497 oldPV: testVolumeWithVolumeAttributesClass(nil),
1498 newPV: testVolumeWithVolumeAttributesClass(nil),
1499 },
1500 "vac-nothing-changed": {
1501 isExpectedFailure: false,
1502 enableVolumeAttributesClass: true,
1503 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1504 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1505 },
1506 "vac-changed": {
1507 isExpectedFailure: false,
1508 enableVolumeAttributesClass: true,
1509 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1510 newPV: testVolumeWithVolumeAttributesClass(ptr.To("bar")),
1511 },
1512 "nil-to-string": {
1513 isExpectedFailure: false,
1514 enableVolumeAttributesClass: true,
1515 oldPV: testVolumeWithVolumeAttributesClass(nil),
1516 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1517 },
1518 "nil-to-empty-string": {
1519 isExpectedFailure: true,
1520 enableVolumeAttributesClass: true,
1521 oldPV: testVolumeWithVolumeAttributesClass(nil),
1522 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
1523 },
1524 "string-to-nil": {
1525 isExpectedFailure: true,
1526 enableVolumeAttributesClass: true,
1527 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1528 newPV: testVolumeWithVolumeAttributesClass(nil),
1529 },
1530 "string-to-empty-string": {
1531 isExpectedFailure: true,
1532 enableVolumeAttributesClass: true,
1533 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1534 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
1535 },
1536 "vac-nothing-changed-when-feature-gate-is-off": {
1537 isExpectedFailure: false,
1538 enableVolumeAttributesClass: false,
1539 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1540 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1541 },
1542 "vac-changed-when-feature-gate-is-off": {
1543 isExpectedFailure: true,
1544 enableVolumeAttributesClass: false,
1545 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1546 newPV: testVolumeWithVolumeAttributesClass(ptr.To("bar")),
1547 },
1548 "nil-to-string-when-feature-gate-is-off": {
1549 isExpectedFailure: true,
1550 enableVolumeAttributesClass: false,
1551 oldPV: testVolumeWithVolumeAttributesClass(nil),
1552 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1553 },
1554 "nil-to-empty-string-when-feature-gate-is-off": {
1555 isExpectedFailure: true,
1556 enableVolumeAttributesClass: false,
1557 oldPV: testVolumeWithVolumeAttributesClass(nil),
1558 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
1559 },
1560 "string-to-nil-when-feature-gate-is-off": {
1561 isExpectedFailure: true,
1562 enableVolumeAttributesClass: false,
1563 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1564 newPV: testVolumeWithVolumeAttributesClass(nil),
1565 },
1566 "string-to-empty-string-when-feature-gate-is-off": {
1567 isExpectedFailure: true,
1568 enableVolumeAttributesClass: false,
1569 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
1570 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
1571 },
1572 }
1573
1574 for name, scenario := range scenarios {
1575 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
1576
1577 originalNewPV := scenario.newPV.DeepCopy()
1578 originalOldPV := scenario.oldPV.DeepCopy()
1579 opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
1580 errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
1581 if len(errs) == 0 && scenario.isExpectedFailure {
1582 t.Errorf("Unexpected success for scenario: %s", name)
1583 }
1584 if len(errs) > 0 && !scenario.isExpectedFailure {
1585 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
1586 }
1587 if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
1588 t.Errorf("newPV was modified: %s", diff)
1589 }
1590 if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
1591 t.Errorf("oldPV was modified: %s", diff)
1592 }
1593 }
1594 }
1595
1596 func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1597 return &core.PersistentVolumeClaim{
1598 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
1599 Spec: spec,
1600 }
1601 }
1602
1603 func testVolumeClaimWithStatus(
1604 name, namespace string,
1605 spec core.PersistentVolumeClaimSpec,
1606 status core.PersistentVolumeClaimStatus) *core.PersistentVolumeClaim {
1607 return &core.PersistentVolumeClaim{
1608 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
1609 Spec: spec,
1610 Status: status,
1611 }
1612 }
1613
1614 func testVolumeClaimStorageClass(name string, namespace string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1615 annotations := map[string]string{
1616 v1.BetaStorageClassAnnotation: annval,
1617 }
1618
1619 return &core.PersistentVolumeClaim{
1620 ObjectMeta: metav1.ObjectMeta{
1621 Name: name,
1622 Namespace: namespace,
1623 Annotations: annotations,
1624 },
1625 Spec: spec,
1626 }
1627 }
1628
1629 func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1630 annotations := map[string]string{
1631 ann: annval,
1632 }
1633
1634 return &core.PersistentVolumeClaim{
1635 ObjectMeta: metav1.ObjectMeta{
1636 Name: name,
1637 Namespace: namespace,
1638 Annotations: annotations,
1639 },
1640 Spec: spec,
1641 }
1642 }
1643
1644 func testVolumeClaimStorageClassInSpec(name, namespace, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1645 spec.StorageClassName = &scName
1646 return &core.PersistentVolumeClaim{
1647 ObjectMeta: metav1.ObjectMeta{
1648 Name: name,
1649 Namespace: namespace,
1650 },
1651 Spec: spec,
1652 }
1653 }
1654
1655 func testVolumeClaimStorageClassNilInSpec(name, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1656 spec.StorageClassName = nil
1657 return &core.PersistentVolumeClaim{
1658 ObjectMeta: metav1.ObjectMeta{
1659 Name: name,
1660 Namespace: namespace,
1661 },
1662 Spec: spec,
1663 }
1664 }
1665
1666 func testVolumeSnapshotDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec {
1667 scName := "csi-plugin"
1668 dataSourceInSpec := core.PersistentVolumeClaimSpec{
1669 AccessModes: []core.PersistentVolumeAccessMode{
1670 core.ReadOnlyMany,
1671 },
1672 Resources: core.VolumeResourceRequirements{
1673 Requests: core.ResourceList{
1674 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1675 },
1676 },
1677 StorageClassName: &scName,
1678 DataSource: &core.TypedLocalObjectReference{
1679 APIGroup: &apiGroup,
1680 Kind: kind,
1681 Name: name,
1682 },
1683 }
1684
1685 return &dataSourceInSpec
1686 }
1687
1688 func TestAlphaVolumeSnapshotDataSource(t *testing.T) {
1689 successTestCases := []core.PersistentVolumeClaimSpec{
1690 *testVolumeSnapshotDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
1691 }
1692 failedTestCases := []core.PersistentVolumeClaimSpec{
1693 *testVolumeSnapshotDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
1694 *testVolumeSnapshotDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
1695 }
1696
1697 for _, tc := range successTestCases {
1698 opts := PersistentVolumeClaimSpecValidationOptions{}
1699 if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) != 0 {
1700 t.Errorf("expected success: %v", errs)
1701 }
1702 }
1703 for _, tc := range failedTestCases {
1704 opts := PersistentVolumeClaimSpecValidationOptions{}
1705 if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) == 0 {
1706 t.Errorf("expected failure: %v", errs)
1707 }
1708 }
1709 }
1710
1711 func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1712 spec.StorageClassName = &scName
1713 return &core.PersistentVolumeClaim{
1714 ObjectMeta: metav1.ObjectMeta{
1715 Name: name,
1716 Namespace: namespace,
1717 Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
1718 },
1719 Spec: spec,
1720 }
1721 }
1722
1723 func testVolumeClaimStorageClassInAnnotationAndNilInSpec(name, namespace, scNameInAnn string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
1724 spec.StorageClassName = nil
1725 return &core.PersistentVolumeClaim{
1726 ObjectMeta: metav1.ObjectMeta{
1727 Name: name,
1728 Namespace: namespace,
1729 Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
1730 },
1731 Spec: spec,
1732 }
1733 }
1734
1735 func testValidatePVC(t *testing.T, ephemeral bool) {
1736 invalidClassName := "-invalid-"
1737 validClassName := "valid"
1738 invalidAPIGroup := "^invalid"
1739 invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
1740 validMode := core.PersistentVolumeFilesystem
1741 goodName := "foo"
1742 goodNS := "ns"
1743 if ephemeral {
1744
1745 goodName = ""
1746 goodNS = ""
1747 }
1748 goodClaimSpec := core.PersistentVolumeClaimSpec{
1749 Selector: &metav1.LabelSelector{
1750 MatchExpressions: []metav1.LabelSelectorRequirement{{
1751 Key: "key2",
1752 Operator: "Exists",
1753 }},
1754 },
1755 AccessModes: []core.PersistentVolumeAccessMode{
1756 core.ReadWriteOnce,
1757 core.ReadOnlyMany,
1758 },
1759 Resources: core.VolumeResourceRequirements{
1760 Requests: core.ResourceList{
1761 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1762 },
1763 },
1764 StorageClassName: &validClassName,
1765 VolumeMode: &validMode,
1766 }
1767 now := metav1.Now()
1768 ten := int64(10)
1769
1770 scenarios := map[string]struct {
1771 isExpectedFailure bool
1772 enableVolumeAttributesClass bool
1773 claim *core.PersistentVolumeClaim
1774 }{
1775 "good-claim": {
1776 isExpectedFailure: false,
1777 claim: testVolumeClaim(goodName, goodNS, goodClaimSpec),
1778 },
1779 "missing-name": {
1780 isExpectedFailure: !ephemeral,
1781 claim: testVolumeClaim("", goodNS, goodClaimSpec),
1782 },
1783 "missing-namespace": {
1784 isExpectedFailure: !ephemeral,
1785 claim: testVolumeClaim(goodName, "", goodClaimSpec),
1786 },
1787 "with-generate-name": {
1788 isExpectedFailure: ephemeral,
1789 claim: func() *core.PersistentVolumeClaim {
1790 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1791 claim.GenerateName = "pvc-"
1792 return claim
1793 }(),
1794 },
1795 "with-uid": {
1796 isExpectedFailure: ephemeral,
1797 claim: func() *core.PersistentVolumeClaim {
1798 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1799 claim.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d"
1800 return claim
1801 }(),
1802 },
1803 "with-resource-version": {
1804 isExpectedFailure: ephemeral,
1805 claim: func() *core.PersistentVolumeClaim {
1806 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1807 claim.ResourceVersion = "1"
1808 return claim
1809 }(),
1810 },
1811 "with-generation": {
1812 isExpectedFailure: ephemeral,
1813 claim: func() *core.PersistentVolumeClaim {
1814 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1815 claim.Generation = 100
1816 return claim
1817 }(),
1818 },
1819 "with-creation-timestamp": {
1820 isExpectedFailure: ephemeral,
1821 claim: func() *core.PersistentVolumeClaim {
1822 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1823 claim.CreationTimestamp = now
1824 return claim
1825 }(),
1826 },
1827 "with-deletion-grace-period-seconds": {
1828 isExpectedFailure: ephemeral,
1829 claim: func() *core.PersistentVolumeClaim {
1830 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1831 claim.DeletionGracePeriodSeconds = &ten
1832 return claim
1833 }(),
1834 },
1835 "with-owner-references": {
1836 isExpectedFailure: ephemeral,
1837 claim: func() *core.PersistentVolumeClaim {
1838 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1839 claim.OwnerReferences = []metav1.OwnerReference{{
1840 APIVersion: "v1",
1841 Kind: "pod",
1842 Name: "foo",
1843 UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d",
1844 },
1845 }
1846 return claim
1847 }(),
1848 },
1849 "with-finalizers": {
1850 isExpectedFailure: ephemeral,
1851 claim: func() *core.PersistentVolumeClaim {
1852 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1853 claim.Finalizers = []string{
1854 "example.com/foo",
1855 }
1856 return claim
1857 }(),
1858 },
1859 "with-managed-fields": {
1860 isExpectedFailure: ephemeral,
1861 claim: func() *core.PersistentVolumeClaim {
1862 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1863 claim.ManagedFields = []metav1.ManagedFieldsEntry{{
1864 FieldsType: "FieldsV1",
1865 Operation: "Apply",
1866 APIVersion: "apps/v1",
1867 Manager: "foo",
1868 },
1869 }
1870 return claim
1871 }(),
1872 },
1873 "with-good-labels": {
1874 claim: func() *core.PersistentVolumeClaim {
1875 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1876 claim.Labels = map[string]string{
1877 "apps.kubernetes.io/name": "test",
1878 }
1879 return claim
1880 }(),
1881 },
1882 "with-bad-labels": {
1883 isExpectedFailure: true,
1884 claim: func() *core.PersistentVolumeClaim {
1885 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1886 claim.Labels = map[string]string{
1887 "hello-world": "hyphen not allowed",
1888 }
1889 return claim
1890 }(),
1891 },
1892 "with-good-annotations": {
1893 claim: func() *core.PersistentVolumeClaim {
1894 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1895 claim.Labels = map[string]string{
1896 "foo": "bar",
1897 }
1898 return claim
1899 }(),
1900 },
1901 "with-bad-annotations": {
1902 isExpectedFailure: true,
1903 claim: func() *core.PersistentVolumeClaim {
1904 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
1905 claim.Labels = map[string]string{
1906 "hello-world": "hyphen not allowed",
1907 }
1908 return claim
1909 }(),
1910 },
1911 "with-read-write-once-pod": {
1912 isExpectedFailure: false,
1913 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1914 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
1915 Resources: core.VolumeResourceRequirements{
1916 Requests: core.ResourceList{
1917 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1918 },
1919 },
1920 }),
1921 },
1922 "with-read-write-once-pod-and-others": {
1923 isExpectedFailure: true,
1924 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1925 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
1926 Resources: core.VolumeResourceRequirements{
1927 Requests: core.ResourceList{
1928 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1929 },
1930 },
1931 }),
1932 },
1933 "invalid-claim-zero-capacity": {
1934 isExpectedFailure: true,
1935 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1936 Selector: &metav1.LabelSelector{
1937 MatchExpressions: []metav1.LabelSelectorRequirement{{
1938 Key: "key2",
1939 Operator: "Exists",
1940 }},
1941 },
1942 AccessModes: []core.PersistentVolumeAccessMode{
1943 core.ReadWriteOnce,
1944 core.ReadOnlyMany,
1945 },
1946 Resources: core.VolumeResourceRequirements{
1947 Requests: core.ResourceList{
1948 core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
1949 },
1950 },
1951 StorageClassName: &validClassName,
1952 }),
1953 },
1954 "invalid-label-selector": {
1955 isExpectedFailure: true,
1956 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1957 Selector: &metav1.LabelSelector{
1958 MatchExpressions: []metav1.LabelSelectorRequirement{{
1959 Key: "key2",
1960 Operator: "InvalidOp",
1961 Values: []string{"value1", "value2"},
1962 }},
1963 },
1964 AccessModes: []core.PersistentVolumeAccessMode{
1965 core.ReadWriteOnce,
1966 core.ReadOnlyMany,
1967 },
1968 Resources: core.VolumeResourceRequirements{
1969 Requests: core.ResourceList{
1970 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1971 },
1972 },
1973 }),
1974 },
1975 "invalid-accessmode": {
1976 isExpectedFailure: true,
1977 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1978 AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
1979 Resources: core.VolumeResourceRequirements{
1980 Requests: core.ResourceList{
1981 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1982 },
1983 },
1984 }),
1985 },
1986 "no-access-modes": {
1987 isExpectedFailure: true,
1988 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1989 Resources: core.VolumeResourceRequirements{
1990 Requests: core.ResourceList{
1991 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
1992 },
1993 },
1994 }),
1995 },
1996 "no-resource-requests": {
1997 isExpectedFailure: true,
1998 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
1999 AccessModes: []core.PersistentVolumeAccessMode{
2000 core.ReadWriteOnce,
2001 },
2002 }),
2003 },
2004 "invalid-resource-requests": {
2005 isExpectedFailure: true,
2006 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2007 AccessModes: []core.PersistentVolumeAccessMode{
2008 core.ReadWriteOnce,
2009 },
2010 Resources: core.VolumeResourceRequirements{
2011 Requests: core.ResourceList{
2012 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
2013 },
2014 },
2015 }),
2016 },
2017 "negative-storage-request": {
2018 isExpectedFailure: true,
2019 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2020 Selector: &metav1.LabelSelector{
2021 MatchExpressions: []metav1.LabelSelectorRequirement{{
2022 Key: "key2",
2023 Operator: "Exists",
2024 }},
2025 },
2026 AccessModes: []core.PersistentVolumeAccessMode{
2027 core.ReadWriteOnce,
2028 core.ReadOnlyMany,
2029 },
2030 Resources: core.VolumeResourceRequirements{
2031 Requests: core.ResourceList{
2032 core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
2033 },
2034 },
2035 }),
2036 },
2037 "zero-storage-request": {
2038 isExpectedFailure: true,
2039 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2040 Selector: &metav1.LabelSelector{
2041 MatchExpressions: []metav1.LabelSelectorRequirement{{
2042 Key: "key2",
2043 Operator: "Exists",
2044 }},
2045 },
2046 AccessModes: []core.PersistentVolumeAccessMode{
2047 core.ReadWriteOnce,
2048 core.ReadOnlyMany,
2049 },
2050 Resources: core.VolumeResourceRequirements{
2051 Requests: core.ResourceList{
2052 core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
2053 },
2054 },
2055 }),
2056 },
2057 "invalid-storage-class-name": {
2058 isExpectedFailure: true,
2059 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2060 Selector: &metav1.LabelSelector{
2061 MatchExpressions: []metav1.LabelSelectorRequirement{{
2062 Key: "key2",
2063 Operator: "Exists",
2064 }},
2065 },
2066 AccessModes: []core.PersistentVolumeAccessMode{
2067 core.ReadWriteOnce,
2068 core.ReadOnlyMany,
2069 },
2070 Resources: core.VolumeResourceRequirements{
2071 Requests: core.ResourceList{
2072 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2073 },
2074 },
2075 StorageClassName: &invalidClassName,
2076 }),
2077 },
2078 "invalid-volume-mode": {
2079 isExpectedFailure: true,
2080 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2081 AccessModes: []core.PersistentVolumeAccessMode{
2082 core.ReadWriteOnce,
2083 core.ReadOnlyMany,
2084 },
2085 Resources: core.VolumeResourceRequirements{
2086 Requests: core.ResourceList{
2087 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2088 },
2089 },
2090 VolumeMode: &invalidMode,
2091 }),
2092 },
2093 "mismatch-data-source-and-ref": {
2094 isExpectedFailure: true,
2095 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2096 AccessModes: []core.PersistentVolumeAccessMode{
2097 core.ReadWriteOnce,
2098 },
2099 Resources: core.VolumeResourceRequirements{
2100 Requests: core.ResourceList{
2101 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2102 },
2103 },
2104 DataSource: &core.TypedLocalObjectReference{
2105 Kind: "PersistentVolumeClaim",
2106 Name: "pvc1",
2107 },
2108 DataSourceRef: &core.TypedObjectReference{
2109 Kind: "PersistentVolumeClaim",
2110 Name: "pvc2",
2111 },
2112 }),
2113 },
2114 "invaild-apigroup-in-data-source": {
2115 isExpectedFailure: true,
2116 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2117 AccessModes: []core.PersistentVolumeAccessMode{
2118 core.ReadWriteOnce,
2119 },
2120 Resources: core.VolumeResourceRequirements{
2121 Requests: core.ResourceList{
2122 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2123 },
2124 },
2125 DataSource: &core.TypedLocalObjectReference{
2126 APIGroup: &invalidAPIGroup,
2127 Kind: "Foo",
2128 Name: "foo1",
2129 },
2130 }),
2131 },
2132 "invaild-apigroup-in-data-source-ref": {
2133 isExpectedFailure: true,
2134 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2135 AccessModes: []core.PersistentVolumeAccessMode{
2136 core.ReadWriteOnce,
2137 },
2138 Resources: core.VolumeResourceRequirements{
2139 Requests: core.ResourceList{
2140 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2141 },
2142 },
2143 DataSourceRef: &core.TypedObjectReference{
2144 APIGroup: &invalidAPIGroup,
2145 Kind: "Foo",
2146 Name: "foo1",
2147 },
2148 }),
2149 },
2150 "invalid-volume-attributes-class-name": {
2151 isExpectedFailure: true,
2152 enableVolumeAttributesClass: true,
2153 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
2154 Selector: &metav1.LabelSelector{
2155 MatchExpressions: []metav1.LabelSelectorRequirement{{
2156 Key: "key2",
2157 Operator: "Exists",
2158 }},
2159 },
2160 AccessModes: []core.PersistentVolumeAccessMode{
2161 core.ReadWriteOnce,
2162 core.ReadOnlyMany,
2163 },
2164 Resources: core.VolumeResourceRequirements{
2165 Requests: core.ResourceList{
2166 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2167 },
2168 },
2169 VolumeAttributesClassName: &invalidClassName,
2170 }),
2171 },
2172 }
2173
2174 for name, scenario := range scenarios {
2175 t.Run(name, func(t *testing.T) {
2176 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
2177
2178 var errs field.ErrorList
2179 if ephemeral {
2180 volumes := []core.Volume{{
2181 Name: "foo",
2182 VolumeSource: core.VolumeSource{
2183 Ephemeral: &core.EphemeralVolumeSource{
2184 VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
2185 ObjectMeta: scenario.claim.ObjectMeta,
2186 Spec: scenario.claim.Spec,
2187 },
2188 },
2189 },
2190 },
2191 }
2192 opts := PodValidationOptions{}
2193 _, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts)
2194 } else {
2195 opts := ValidationOptionsForPersistentVolumeClaim(scenario.claim, nil)
2196 errs = ValidatePersistentVolumeClaim(scenario.claim, opts)
2197 }
2198 if len(errs) == 0 && scenario.isExpectedFailure {
2199 t.Error("Unexpected success for scenario")
2200 }
2201 if len(errs) > 0 && !scenario.isExpectedFailure {
2202 t.Errorf("Unexpected failure: %+v", errs)
2203 }
2204 })
2205 }
2206 }
2207
2208 func TestValidatePersistentVolumeClaim(t *testing.T) {
2209 testValidatePVC(t, false)
2210 }
2211
2212 func TestValidateEphemeralVolume(t *testing.T) {
2213 testValidatePVC(t, true)
2214 }
2215
2216 func TestAlphaPVVolumeModeUpdate(t *testing.T) {
2217 block := core.PersistentVolumeBlock
2218 file := core.PersistentVolumeFilesystem
2219
2220 scenarios := map[string]struct {
2221 isExpectedFailure bool
2222 oldPV *core.PersistentVolume
2223 newPV *core.PersistentVolume
2224 }{
2225 "valid-update-volume-mode-block-to-block": {
2226 isExpectedFailure: false,
2227 oldPV: createTestVolModePV(&block),
2228 newPV: createTestVolModePV(&block),
2229 },
2230 "valid-update-volume-mode-file-to-file": {
2231 isExpectedFailure: false,
2232 oldPV: createTestVolModePV(&file),
2233 newPV: createTestVolModePV(&file),
2234 },
2235 "invalid-update-volume-mode-to-block": {
2236 isExpectedFailure: true,
2237 oldPV: createTestVolModePV(&file),
2238 newPV: createTestVolModePV(&block),
2239 },
2240 "invalid-update-volume-mode-to-file": {
2241 isExpectedFailure: true,
2242 oldPV: createTestVolModePV(&block),
2243 newPV: createTestVolModePV(&file),
2244 },
2245 "invalid-update-volume-mode-nil-to-file": {
2246 isExpectedFailure: true,
2247 oldPV: createTestVolModePV(nil),
2248 newPV: createTestVolModePV(&file),
2249 },
2250 "invalid-update-volume-mode-nil-to-block": {
2251 isExpectedFailure: true,
2252 oldPV: createTestVolModePV(nil),
2253 newPV: createTestVolModePV(&block),
2254 },
2255 "invalid-update-volume-mode-file-to-nil": {
2256 isExpectedFailure: true,
2257 oldPV: createTestVolModePV(&file),
2258 newPV: createTestVolModePV(nil),
2259 },
2260 "invalid-update-volume-mode-block-to-nil": {
2261 isExpectedFailure: true,
2262 oldPV: createTestVolModePV(&block),
2263 newPV: createTestVolModePV(nil),
2264 },
2265 "invalid-update-volume-mode-nil-to-nil": {
2266 isExpectedFailure: false,
2267 oldPV: createTestVolModePV(nil),
2268 newPV: createTestVolModePV(nil),
2269 },
2270 "invalid-update-volume-mode-empty-to-mode": {
2271 isExpectedFailure: true,
2272 oldPV: createTestPV(),
2273 newPV: createTestVolModePV(&block),
2274 },
2275 }
2276
2277 for name, scenario := range scenarios {
2278 t.Run(name, func(t *testing.T) {
2279 opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
2280
2281 errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
2282 if len(errs) == 0 && scenario.isExpectedFailure {
2283 t.Errorf("Unexpected success for scenario: %s", name)
2284 }
2285 if len(errs) > 0 && !scenario.isExpectedFailure {
2286 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
2287 }
2288 })
2289 }
2290 }
2291
2292 func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
2293 block := core.PersistentVolumeBlock
2294 file := core.PersistentVolumeFilesystem
2295 invaildAPIGroup := "^invalid"
2296
2297 validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2298 AccessModes: []core.PersistentVolumeAccessMode{
2299 core.ReadWriteOnce,
2300 core.ReadOnlyMany,
2301 },
2302 Resources: core.VolumeResourceRequirements{
2303 Requests: core.ResourceList{
2304 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2305 },
2306 },
2307 }, core.PersistentVolumeClaimStatus{
2308 Phase: core.ClaimBound,
2309 })
2310
2311 validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
2312 AccessModes: []core.PersistentVolumeAccessMode{
2313 core.ReadOnlyMany,
2314 },
2315 Resources: core.VolumeResourceRequirements{
2316 Requests: core.ResourceList{
2317 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2318 },
2319 },
2320 })
2321 validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", core.PersistentVolumeClaimSpec{
2322 AccessModes: []core.PersistentVolumeAccessMode{
2323 core.ReadOnlyMany,
2324 },
2325 Resources: core.VolumeResourceRequirements{
2326 Requests: core.ResourceList{
2327 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2328 },
2329 },
2330 })
2331 validUpdateClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2332 AccessModes: []core.PersistentVolumeAccessMode{
2333 core.ReadWriteOnce,
2334 core.ReadOnlyMany,
2335 },
2336 Resources: core.VolumeResourceRequirements{
2337 Requests: core.ResourceList{
2338 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2339 },
2340 },
2341 VolumeName: "volume",
2342 })
2343 invalidUpdateClaimResources := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2344 AccessModes: []core.PersistentVolumeAccessMode{
2345 core.ReadWriteOnce,
2346 core.ReadOnlyMany,
2347 },
2348 Resources: core.VolumeResourceRequirements{
2349 Requests: core.ResourceList{
2350 core.ResourceName(core.ResourceStorage): resource.MustParse("20G"),
2351 },
2352 },
2353 VolumeName: "volume",
2354 })
2355 invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2356 AccessModes: []core.PersistentVolumeAccessMode{
2357 core.ReadWriteOnce,
2358 },
2359 Resources: core.VolumeResourceRequirements{
2360 Requests: core.ResourceList{
2361 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2362 },
2363 },
2364 VolumeName: "volume",
2365 })
2366 validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2367 AccessModes: []core.PersistentVolumeAccessMode{
2368 core.ReadWriteOnce,
2369 },
2370 VolumeMode: &file,
2371 Resources: core.VolumeResourceRequirements{
2372 Requests: core.ResourceList{
2373 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2374 },
2375 },
2376 VolumeName: "volume",
2377 })
2378 validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2379 AccessModes: []core.PersistentVolumeAccessMode{
2380 core.ReadWriteOnce,
2381 },
2382 VolumeMode: &block,
2383 Resources: core.VolumeResourceRequirements{
2384 Requests: core.ResourceList{
2385 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2386 },
2387 },
2388 VolumeName: "volume",
2389 })
2390 invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2391 AccessModes: []core.PersistentVolumeAccessMode{
2392 core.ReadWriteOnce,
2393 },
2394 VolumeMode: nil,
2395 Resources: core.VolumeResourceRequirements{
2396 Requests: core.ResourceList{
2397 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2398 },
2399 },
2400 VolumeName: "volume",
2401 })
2402 invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
2403 AccessModes: []core.PersistentVolumeAccessMode{
2404 core.ReadOnlyMany,
2405 },
2406 Resources: core.VolumeResourceRequirements{
2407 Requests: core.ResourceList{
2408 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2409 },
2410 },
2411 VolumeName: "volume",
2412 })
2413 validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
2414 AccessModes: []core.PersistentVolumeAccessMode{
2415 core.ReadOnlyMany,
2416 },
2417 Resources: core.VolumeResourceRequirements{
2418 Requests: core.ResourceList{
2419 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2420 },
2421 },
2422 VolumeName: "volume",
2423 })
2424 validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
2425 AccessModes: []core.PersistentVolumeAccessMode{
2426 core.ReadWriteOnce,
2427 core.ReadOnlyMany,
2428 },
2429 Resources: core.VolumeResourceRequirements{
2430 Requests: core.ResourceList{
2431 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2432 },
2433 },
2434 VolumeName: "volume",
2435 })
2436 validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2437 AccessModes: []core.PersistentVolumeAccessMode{
2438 core.ReadWriteOnce,
2439 core.ReadOnlyMany,
2440 },
2441 Resources: core.VolumeResourceRequirements{
2442 Requests: core.ResourceList{
2443 core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
2444 },
2445 },
2446 }, core.PersistentVolumeClaimStatus{
2447 Phase: core.ClaimBound,
2448 })
2449
2450 invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2451 AccessModes: []core.PersistentVolumeAccessMode{
2452 core.ReadWriteOnce,
2453 core.ReadOnlyMany,
2454 },
2455 Resources: core.VolumeResourceRequirements{
2456 Requests: core.ResourceList{
2457 core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
2458 },
2459 },
2460 }, core.PersistentVolumeClaimStatus{
2461 Phase: core.ClaimBound,
2462 })
2463
2464 unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2465 AccessModes: []core.PersistentVolumeAccessMode{
2466 core.ReadWriteOnce,
2467 core.ReadOnlyMany,
2468 },
2469 Resources: core.VolumeResourceRequirements{
2470 Requests: core.ResourceList{
2471 core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
2472 },
2473 },
2474 }, core.PersistentVolumeClaimStatus{
2475 Phase: core.ClaimPending,
2476 })
2477
2478 validClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
2479 AccessModes: []core.PersistentVolumeAccessMode{
2480 core.ReadOnlyMany,
2481 },
2482 Resources: core.VolumeResourceRequirements{
2483 Requests: core.ResourceList{
2484 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2485 },
2486 },
2487 })
2488
2489 validClaimStorageClassInSpecChanged := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
2490 AccessModes: []core.PersistentVolumeAccessMode{
2491 core.ReadOnlyMany,
2492 },
2493 Resources: core.VolumeResourceRequirements{
2494 Requests: core.ResourceList{
2495 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2496 },
2497 },
2498 })
2499
2500 validClaimStorageClassNil := testVolumeClaimStorageClassNilInSpec("foo", "ns", core.PersistentVolumeClaimSpec{
2501 AccessModes: []core.PersistentVolumeAccessMode{
2502 core.ReadOnlyMany,
2503 },
2504 Resources: core.VolumeResourceRequirements{
2505 Requests: core.ResourceList{
2506 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2507 },
2508 },
2509 })
2510
2511 invalidClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
2512 AccessModes: []core.PersistentVolumeAccessMode{
2513 core.ReadOnlyMany,
2514 },
2515 Resources: core.VolumeResourceRequirements{
2516 Requests: core.ResourceList{
2517 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2518 },
2519 },
2520 })
2521
2522 validClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
2523 "foo", "ns", "fast", "fast", core.PersistentVolumeClaimSpec{
2524 AccessModes: []core.PersistentVolumeAccessMode{
2525 core.ReadOnlyMany,
2526 },
2527 Resources: core.VolumeResourceRequirements{
2528 Requests: core.ResourceList{
2529 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2530 },
2531 },
2532 })
2533
2534 validClaimStorageClassInAnnotationAndNilInSpec := testVolumeClaimStorageClassInAnnotationAndNilInSpec(
2535 "foo", "ns", "fast", core.PersistentVolumeClaimSpec{
2536 AccessModes: []core.PersistentVolumeAccessMode{
2537 core.ReadOnlyMany,
2538 },
2539 Resources: core.VolumeResourceRequirements{
2540 Requests: core.ResourceList{
2541 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2542 },
2543 },
2544 })
2545
2546 invalidClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
2547 "foo", "ns", "fast2", "fast", core.PersistentVolumeClaimSpec{
2548 AccessModes: []core.PersistentVolumeAccessMode{
2549 core.ReadOnlyMany,
2550 },
2551 Resources: core.VolumeResourceRequirements{
2552 Requests: core.ResourceList{
2553 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2554 },
2555 },
2556 })
2557
2558 validClaimRWOPAccessMode := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2559 AccessModes: []core.PersistentVolumeAccessMode{
2560 core.ReadWriteOncePod,
2561 },
2562 Resources: core.VolumeResourceRequirements{
2563 Requests: core.ResourceList{
2564 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2565 },
2566 },
2567 VolumeName: "volume",
2568 })
2569
2570 validClaimRWOPAccessModeAddAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
2571 AccessModes: []core.PersistentVolumeAccessMode{
2572 core.ReadWriteOncePod,
2573 },
2574 Resources: core.VolumeResourceRequirements{
2575 Requests: core.ResourceList{
2576 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2577 },
2578 },
2579 VolumeName: "volume",
2580 })
2581 validClaimShrinkInitial := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2582 AccessModes: []core.PersistentVolumeAccessMode{
2583 core.ReadWriteOnce,
2584 core.ReadOnlyMany,
2585 },
2586 Resources: core.VolumeResourceRequirements{
2587 Requests: core.ResourceList{
2588 core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
2589 },
2590 },
2591 }, core.PersistentVolumeClaimStatus{
2592 Phase: core.ClaimBound,
2593 Capacity: core.ResourceList{
2594 core.ResourceStorage: resource.MustParse("10G"),
2595 },
2596 })
2597
2598 unboundShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2599 AccessModes: []core.PersistentVolumeAccessMode{
2600 core.ReadWriteOnce,
2601 core.ReadOnlyMany,
2602 },
2603 Resources: core.VolumeResourceRequirements{
2604 Requests: core.ResourceList{
2605 core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
2606 },
2607 },
2608 }, core.PersistentVolumeClaimStatus{
2609 Phase: core.ClaimPending,
2610 Capacity: core.ResourceList{
2611 core.ResourceStorage: resource.MustParse("10G"),
2612 },
2613 })
2614
2615 validClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2616 AccessModes: []core.PersistentVolumeAccessMode{
2617 core.ReadWriteOnce,
2618 core.ReadOnlyMany,
2619 },
2620 Resources: core.VolumeResourceRequirements{
2621 Requests: core.ResourceList{
2622 core.ResourceStorage: resource.MustParse("13G"),
2623 },
2624 },
2625 }, core.PersistentVolumeClaimStatus{
2626 Phase: core.ClaimBound,
2627 Capacity: core.ResourceList{
2628 core.ResourceStorage: resource.MustParse("10G"),
2629 },
2630 })
2631
2632 invalidShrinkToStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2633 AccessModes: []core.PersistentVolumeAccessMode{
2634 core.ReadWriteOnce,
2635 core.ReadOnlyMany,
2636 },
2637 Resources: core.VolumeResourceRequirements{
2638 Requests: core.ResourceList{
2639 core.ResourceStorage: resource.MustParse("10G"),
2640 },
2641 },
2642 }, core.PersistentVolumeClaimStatus{
2643 Phase: core.ClaimBound,
2644 Capacity: core.ResourceList{
2645 core.ResourceStorage: resource.MustParse("10G"),
2646 },
2647 })
2648
2649 invalidClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2650 AccessModes: []core.PersistentVolumeAccessMode{
2651 core.ReadWriteOnce,
2652 core.ReadOnlyMany,
2653 },
2654 Resources: core.VolumeResourceRequirements{
2655 Requests: core.ResourceList{
2656 core.ResourceStorage: resource.MustParse("3G"),
2657 },
2658 },
2659 }, core.PersistentVolumeClaimStatus{
2660 Phase: core.ClaimBound,
2661 Capacity: core.ResourceList{
2662 core.ResourceStorage: resource.MustParse("10G"),
2663 },
2664 })
2665
2666 invalidClaimDataSourceAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2667 AccessModes: []core.PersistentVolumeAccessMode{
2668 core.ReadWriteOnce,
2669 },
2670 VolumeMode: &file,
2671 Resources: core.VolumeResourceRequirements{
2672 Requests: core.ResourceList{
2673 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2674 },
2675 },
2676 VolumeName: "volume",
2677 DataSource: &core.TypedLocalObjectReference{
2678 APIGroup: &invaildAPIGroup,
2679 Kind: "Foo",
2680 Name: "foo",
2681 },
2682 })
2683
2684 invalidClaimDataSourceRefAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
2685 AccessModes: []core.PersistentVolumeAccessMode{
2686 core.ReadWriteOnce,
2687 },
2688 VolumeMode: &file,
2689 Resources: core.VolumeResourceRequirements{
2690 Requests: core.ResourceList{
2691 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2692 },
2693 },
2694 VolumeName: "volume",
2695 DataSourceRef: &core.TypedObjectReference{
2696 APIGroup: &invaildAPIGroup,
2697 Kind: "Foo",
2698 Name: "foo",
2699 },
2700 })
2701
2702 validClaimNilVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2703 AccessModes: []core.PersistentVolumeAccessMode{
2704 core.ReadWriteOnce,
2705 core.ReadOnlyMany,
2706 },
2707 Resources: core.VolumeResourceRequirements{
2708 Requests: core.ResourceList{
2709 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2710 },
2711 },
2712 }, core.PersistentVolumeClaimStatus{
2713 Phase: core.ClaimBound,
2714 })
2715 validClaimEmptyVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2716 VolumeAttributesClassName: utilpointer.String(""),
2717 AccessModes: []core.PersistentVolumeAccessMode{
2718 core.ReadWriteOnce,
2719 core.ReadOnlyMany,
2720 },
2721 Resources: core.VolumeResourceRequirements{
2722 Requests: core.ResourceList{
2723 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2724 },
2725 },
2726 }, core.PersistentVolumeClaimStatus{
2727 Phase: core.ClaimBound,
2728 })
2729 validClaimVolumeAttributesClass1 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2730 VolumeAttributesClassName: utilpointer.String("vac1"),
2731 AccessModes: []core.PersistentVolumeAccessMode{
2732 core.ReadWriteOnce,
2733 core.ReadOnlyMany,
2734 },
2735 Resources: core.VolumeResourceRequirements{
2736 Requests: core.ResourceList{
2737 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2738 },
2739 },
2740 }, core.PersistentVolumeClaimStatus{
2741 Phase: core.ClaimBound,
2742 })
2743 validClaimVolumeAttributesClass2 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
2744 VolumeAttributesClassName: utilpointer.String("vac2"),
2745 AccessModes: []core.PersistentVolumeAccessMode{
2746 core.ReadWriteOnce,
2747 core.ReadOnlyMany,
2748 },
2749 Resources: core.VolumeResourceRequirements{
2750 Requests: core.ResourceList{
2751 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
2752 },
2753 },
2754 }, core.PersistentVolumeClaimStatus{
2755 Phase: core.ClaimBound,
2756 })
2757
2758 scenarios := map[string]struct {
2759 isExpectedFailure bool
2760 oldClaim *core.PersistentVolumeClaim
2761 newClaim *core.PersistentVolumeClaim
2762 enableRecoverFromExpansion bool
2763 enableVolumeAttributesClass bool
2764 }{
2765 "valid-update-volumeName-only": {
2766 isExpectedFailure: false,
2767 oldClaim: validClaim,
2768 newClaim: validUpdateClaim,
2769 },
2770 "valid-no-op-update": {
2771 isExpectedFailure: false,
2772 oldClaim: validUpdateClaim,
2773 newClaim: validUpdateClaim,
2774 },
2775 "invalid-update-change-resources-on-bound-claim": {
2776 isExpectedFailure: true,
2777 oldClaim: validUpdateClaim,
2778 newClaim: invalidUpdateClaimResources,
2779 },
2780 "invalid-update-change-access-modes-on-bound-claim": {
2781 isExpectedFailure: true,
2782 oldClaim: validUpdateClaim,
2783 newClaim: invalidUpdateClaimAccessModes,
2784 },
2785 "valid-update-volume-mode-block-to-block": {
2786 isExpectedFailure: false,
2787 oldClaim: validClaimVolumeModeBlock,
2788 newClaim: validClaimVolumeModeBlock,
2789 },
2790 "valid-update-volume-mode-file-to-file": {
2791 isExpectedFailure: false,
2792 oldClaim: validClaimVolumeModeFile,
2793 newClaim: validClaimVolumeModeFile,
2794 },
2795 "invalid-update-volume-mode-to-block": {
2796 isExpectedFailure: true,
2797 oldClaim: validClaimVolumeModeFile,
2798 newClaim: validClaimVolumeModeBlock,
2799 },
2800 "invalid-update-volume-mode-to-file": {
2801 isExpectedFailure: true,
2802 oldClaim: validClaimVolumeModeBlock,
2803 newClaim: validClaimVolumeModeFile,
2804 },
2805 "invalid-update-volume-mode-nil-to-file": {
2806 isExpectedFailure: true,
2807 oldClaim: invalidClaimVolumeModeNil,
2808 newClaim: validClaimVolumeModeFile,
2809 },
2810 "invalid-update-volume-mode-nil-to-block": {
2811 isExpectedFailure: true,
2812 oldClaim: invalidClaimVolumeModeNil,
2813 newClaim: validClaimVolumeModeBlock,
2814 },
2815 "invalid-update-volume-mode-block-to-nil": {
2816 isExpectedFailure: true,
2817 oldClaim: validClaimVolumeModeBlock,
2818 newClaim: invalidClaimVolumeModeNil,
2819 },
2820 "invalid-update-volume-mode-file-to-nil": {
2821 isExpectedFailure: true,
2822 oldClaim: validClaimVolumeModeFile,
2823 newClaim: invalidClaimVolumeModeNil,
2824 },
2825 "invalid-update-volume-mode-empty-to-mode": {
2826 isExpectedFailure: true,
2827 oldClaim: validClaim,
2828 newClaim: validClaimVolumeModeBlock,
2829 },
2830 "invalid-update-volume-mode-mode-to-empty": {
2831 isExpectedFailure: true,
2832 oldClaim: validClaimVolumeModeBlock,
2833 newClaim: validClaim,
2834 },
2835 "invalid-update-change-storage-class-annotation-after-creation": {
2836 isExpectedFailure: true,
2837 oldClaim: validClaimStorageClass,
2838 newClaim: invalidUpdateClaimStorageClass,
2839 },
2840 "valid-update-mutable-annotation": {
2841 isExpectedFailure: false,
2842 oldClaim: validClaimAnnotation,
2843 newClaim: validUpdateClaimMutableAnnotation,
2844 },
2845 "valid-update-add-annotation": {
2846 isExpectedFailure: false,
2847 oldClaim: validClaim,
2848 newClaim: validAddClaimAnnotation,
2849 },
2850 "valid-size-update-resize-disabled": {
2851 oldClaim: validClaim,
2852 newClaim: validSizeUpdate,
2853 },
2854 "valid-size-update-resize-enabled": {
2855 isExpectedFailure: false,
2856 oldClaim: validClaim,
2857 newClaim: validSizeUpdate,
2858 },
2859 "invalid-size-update-resize-enabled": {
2860 isExpectedFailure: true,
2861 oldClaim: validClaim,
2862 newClaim: invalidSizeUpdate,
2863 },
2864 "unbound-size-update-resize-enabled": {
2865 isExpectedFailure: true,
2866 oldClaim: validClaim,
2867 newClaim: unboundSizeUpdate,
2868 },
2869 "valid-upgrade-storage-class-annotation-to-spec": {
2870 isExpectedFailure: false,
2871 oldClaim: validClaimStorageClass,
2872 newClaim: validClaimStorageClassInSpec,
2873 },
2874 "valid-upgrade-nil-storage-class-spec-to-spec": {
2875 isExpectedFailure: false,
2876 oldClaim: validClaimStorageClassNil,
2877 newClaim: validClaimStorageClassInSpec,
2878 },
2879 "invalid-upgrade-not-nil-storage-class-spec-to-spec": {
2880 isExpectedFailure: true,
2881 oldClaim: validClaimStorageClassInSpec,
2882 newClaim: validClaimStorageClassInSpecChanged,
2883 },
2884 "invalid-upgrade-to-nil-storage-class-spec-to-spec": {
2885 isExpectedFailure: true,
2886 oldClaim: validClaimStorageClassInSpec,
2887 newClaim: validClaimStorageClassNil,
2888 },
2889 "valid-upgrade-storage-class-annotation-and-nil-spec-to-spec-retro": {
2890 isExpectedFailure: false,
2891 oldClaim: validClaimStorageClassInAnnotationAndNilInSpec,
2892 newClaim: validClaimStorageClassInAnnotationAndSpec,
2893 },
2894 "invalid-upgrade-storage-class-annotation-and-spec-to-spec-retro": {
2895 isExpectedFailure: true,
2896 oldClaim: validClaimStorageClassInAnnotationAndSpec,
2897 newClaim: validClaimStorageClassInSpecChanged,
2898 },
2899 "invalid-upgrade-storage-class-annotation-and-no-spec": {
2900 isExpectedFailure: true,
2901 oldClaim: validClaimStorageClassInAnnotationAndNilInSpec,
2902 newClaim: validClaimStorageClassInSpecChanged,
2903 },
2904 "invalid-upgrade-storage-class-annotation-to-spec": {
2905 isExpectedFailure: true,
2906 oldClaim: validClaimStorageClass,
2907 newClaim: invalidClaimStorageClassInSpec,
2908 },
2909 "valid-upgrade-storage-class-annotation-to-annotation-and-spec": {
2910 isExpectedFailure: false,
2911 oldClaim: validClaimStorageClass,
2912 newClaim: validClaimStorageClassInAnnotationAndSpec,
2913 },
2914 "invalid-upgrade-storage-class-annotation-to-annotation-and-spec": {
2915 isExpectedFailure: true,
2916 oldClaim: validClaimStorageClass,
2917 newClaim: invalidClaimStorageClassInAnnotationAndSpec,
2918 },
2919 "invalid-upgrade-storage-class-in-spec": {
2920 isExpectedFailure: true,
2921 oldClaim: validClaimStorageClassInSpec,
2922 newClaim: invalidClaimStorageClassInSpec,
2923 },
2924 "invalid-downgrade-storage-class-spec-to-annotation": {
2925 isExpectedFailure: true,
2926 oldClaim: validClaimStorageClassInSpec,
2927 newClaim: validClaimStorageClass,
2928 },
2929 "valid-update-rwop-used-and-rwop-feature-disabled": {
2930 isExpectedFailure: false,
2931 oldClaim: validClaimRWOPAccessMode,
2932 newClaim: validClaimRWOPAccessModeAddAnnotation,
2933 },
2934 "valid-expand-shrink-resize-enabled": {
2935 oldClaim: validClaimShrinkInitial,
2936 newClaim: validClaimShrink,
2937 enableRecoverFromExpansion: true,
2938 },
2939 "invalid-expand-shrink-resize-enabled": {
2940 oldClaim: validClaimShrinkInitial,
2941 newClaim: invalidClaimShrink,
2942 enableRecoverFromExpansion: true,
2943 isExpectedFailure: true,
2944 },
2945 "invalid-expand-shrink-to-status-resize-enabled": {
2946 oldClaim: validClaimShrinkInitial,
2947 newClaim: invalidShrinkToStatus,
2948 enableRecoverFromExpansion: true,
2949 isExpectedFailure: true,
2950 },
2951 "invalid-expand-shrink-recover-disabled": {
2952 oldClaim: validClaimShrinkInitial,
2953 newClaim: validClaimShrink,
2954 enableRecoverFromExpansion: false,
2955 isExpectedFailure: true,
2956 },
2957 "unbound-size-shrink-resize-enabled": {
2958 oldClaim: validClaimShrinkInitial,
2959 newClaim: unboundShrink,
2960 enableRecoverFromExpansion: true,
2961 isExpectedFailure: true,
2962 },
2963 "allow-update-pvc-when-data-source-used": {
2964 oldClaim: invalidClaimDataSourceAPIGroup,
2965 newClaim: invalidClaimDataSourceAPIGroup,
2966 isExpectedFailure: false,
2967 },
2968 "allow-update-pvc-when-data-source-ref-used": {
2969 oldClaim: invalidClaimDataSourceRefAPIGroup,
2970 newClaim: invalidClaimDataSourceRefAPIGroup,
2971 isExpectedFailure: false,
2972 },
2973 "valid-update-volume-attributes-class-from-nil": {
2974 oldClaim: validClaimNilVolumeAttributesClass,
2975 newClaim: validClaimVolumeAttributesClass1,
2976 enableVolumeAttributesClass: true,
2977 isExpectedFailure: false,
2978 },
2979 "valid-update-volume-attributes-class-from-empty": {
2980 oldClaim: validClaimEmptyVolumeAttributesClass,
2981 newClaim: validClaimVolumeAttributesClass1,
2982 enableVolumeAttributesClass: true,
2983 isExpectedFailure: false,
2984 },
2985 "valid-update-volume-attributes-class": {
2986 oldClaim: validClaimVolumeAttributesClass1,
2987 newClaim: validClaimVolumeAttributesClass2,
2988 enableVolumeAttributesClass: true,
2989 isExpectedFailure: false,
2990 },
2991 "invalid-update-volume-attributes-class": {
2992 oldClaim: validClaimVolumeAttributesClass1,
2993 newClaim: validClaimNilVolumeAttributesClass,
2994 enableVolumeAttributesClass: true,
2995 isExpectedFailure: true,
2996 },
2997 "invalid-update-volume-attributes-class-to-nil": {
2998 oldClaim: validClaimVolumeAttributesClass1,
2999 newClaim: validClaimNilVolumeAttributesClass,
3000 enableVolumeAttributesClass: true,
3001 isExpectedFailure: true,
3002 },
3003 "invalid-update-volume-attributes-class-to-empty": {
3004 oldClaim: validClaimVolumeAttributesClass1,
3005 newClaim: validClaimEmptyVolumeAttributesClass,
3006 enableVolumeAttributesClass: true,
3007 isExpectedFailure: true,
3008 },
3009 "invalid-update-volume-attributes-class-to-nil-without-featuregate-enabled": {
3010 oldClaim: validClaimVolumeAttributesClass1,
3011 newClaim: validClaimNilVolumeAttributesClass,
3012 enableVolumeAttributesClass: false,
3013 isExpectedFailure: true,
3014 },
3015 "invalid-update-volume-attributes-class-without-featuregate-enabled": {
3016 oldClaim: validClaimVolumeAttributesClass1,
3017 newClaim: validClaimVolumeAttributesClass2,
3018 enableVolumeAttributesClass: false,
3019 isExpectedFailure: true,
3020 },
3021 }
3022
3023 for name, scenario := range scenarios {
3024 t.Run(name, func(t *testing.T) {
3025 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
3026 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
3027
3028 scenario.oldClaim.ResourceVersion = "1"
3029 scenario.newClaim.ResourceVersion = "1"
3030 opts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
3031 errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim, opts)
3032 if len(errs) == 0 && scenario.isExpectedFailure {
3033 t.Errorf("Unexpected success for scenario: %s", name)
3034 }
3035 if len(errs) > 0 && !scenario.isExpectedFailure {
3036 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
3037 }
3038 })
3039 }
3040 }
3041
3042 func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
3043 invaildAPIGroup := "^invalid"
3044
3045 tests := map[string]struct {
3046 oldPvc *core.PersistentVolumeClaim
3047 enableVolumeAttributesClass bool
3048 expectValidationOpts PersistentVolumeClaimSpecValidationOptions
3049 }{
3050 "nil pv": {
3051 oldPvc: nil,
3052 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
3053 EnableRecoverFromExpansionFailure: false,
3054 EnableVolumeAttributesClass: false,
3055 },
3056 },
3057 "invaild apiGroup in dataSource allowed because the old pvc is used": {
3058 oldPvc: pvcWithDataSource(&core.TypedLocalObjectReference{APIGroup: &invaildAPIGroup}),
3059 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
3060 AllowInvalidAPIGroupInDataSourceOrRef: true,
3061 },
3062 },
3063 "invaild apiGroup in dataSourceRef allowed because the old pvc is used": {
3064 oldPvc: pvcWithDataSourceRef(&core.TypedObjectReference{APIGroup: &invaildAPIGroup}),
3065 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
3066 AllowInvalidAPIGroupInDataSourceOrRef: true,
3067 },
3068 },
3069 "volume attributes class allowed because feature enable": {
3070 oldPvc: pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
3071 enableVolumeAttributesClass: true,
3072 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
3073 EnableRecoverFromExpansionFailure: false,
3074 EnableVolumeAttributesClass: true,
3075 },
3076 },
3077 "volume attributes class validated because used and feature disabled": {
3078 oldPvc: pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
3079 enableVolumeAttributesClass: false,
3080 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
3081 EnableRecoverFromExpansionFailure: false,
3082 EnableVolumeAttributesClass: true,
3083 },
3084 },
3085 }
3086
3087 for name, tc := range tests {
3088 t.Run(name, func(t *testing.T) {
3089 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
3090
3091 opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc)
3092 if opts != tc.expectValidationOpts {
3093 t.Errorf("Expected opts: %+v, received: %+v", tc.expectValidationOpts, opts)
3094 }
3095 })
3096 }
3097 }
3098
3099 func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) {
3100 tests := map[string]struct {
3101 oldPvcTemplate *core.PersistentVolumeClaimTemplate
3102 enableVolumeAttributesClass bool
3103 expectValidationOpts PersistentVolumeClaimSpecValidationOptions
3104 }{
3105 "nil pv": {
3106 oldPvcTemplate: nil,
3107 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{},
3108 },
3109 "volume attributes class allowed because feature enable": {
3110 oldPvcTemplate: pvcTemplateWithVolumeAttributesClassName(utilpointer.String("foo")),
3111 enableVolumeAttributesClass: true,
3112 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
3113 EnableVolumeAttributesClass: true,
3114 },
3115 },
3116 }
3117
3118 for name, tc := range tests {
3119 t.Run(name, func(t *testing.T) {
3120 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
3121
3122 opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate)
3123 if opts != tc.expectValidationOpts {
3124 t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
3125 }
3126 })
3127 }
3128 }
3129
3130 func TestValidateKeyToPath(t *testing.T) {
3131 testCases := []struct {
3132 kp core.KeyToPath
3133 ok bool
3134 errtype field.ErrorType
3135 }{{
3136 kp: core.KeyToPath{Key: "k", Path: "p"},
3137 ok: true,
3138 }, {
3139 kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"},
3140 ok: true,
3141 }, {
3142 kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"},
3143 ok: true,
3144 }, {
3145 kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(0644)},
3146 ok: true,
3147 }, {
3148 kp: core.KeyToPath{Key: "", Path: "p"},
3149 ok: false,
3150 errtype: field.ErrorTypeRequired,
3151 }, {
3152 kp: core.KeyToPath{Key: "k", Path: ""},
3153 ok: false,
3154 errtype: field.ErrorTypeRequired,
3155 }, {
3156 kp: core.KeyToPath{Key: "k", Path: "..p"},
3157 ok: false,
3158 errtype: field.ErrorTypeInvalid,
3159 }, {
3160 kp: core.KeyToPath{Key: "k", Path: "../p"},
3161 ok: false,
3162 errtype: field.ErrorTypeInvalid,
3163 }, {
3164 kp: core.KeyToPath{Key: "k", Path: "p/../p"},
3165 ok: false,
3166 errtype: field.ErrorTypeInvalid,
3167 }, {
3168 kp: core.KeyToPath{Key: "k", Path: "p/.."},
3169 ok: false,
3170 errtype: field.ErrorTypeInvalid,
3171 }, {
3172 kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(01000)},
3173 ok: false,
3174 errtype: field.ErrorTypeInvalid,
3175 }, {
3176 kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(-1)},
3177 ok: false,
3178 errtype: field.ErrorTypeInvalid,
3179 },
3180 }
3181
3182 for i, tc := range testCases {
3183 errs := validateKeyToPath(&tc.kp, field.NewPath("field"))
3184 if tc.ok && len(errs) > 0 {
3185 t.Errorf("[%d] unexpected errors: %v", i, errs)
3186 } else if !tc.ok && len(errs) == 0 {
3187 t.Errorf("[%d] expected error type %v", i, tc.errtype)
3188 } else if len(errs) > 1 {
3189 t.Errorf("[%d] expected only one error, got %d", i, len(errs))
3190 } else if !tc.ok {
3191 if errs[0].Type != tc.errtype {
3192 t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type)
3193 }
3194 }
3195 }
3196 }
3197
3198 func TestValidateNFSVolumeSource(t *testing.T) {
3199 testCases := []struct {
3200 name string
3201 nfs *core.NFSVolumeSource
3202 errtype field.ErrorType
3203 errfield string
3204 errdetail string
3205 }{{
3206 name: "missing server",
3207 nfs: &core.NFSVolumeSource{Server: "", Path: "/tmp"},
3208 errtype: field.ErrorTypeRequired,
3209 errfield: "server",
3210 }, {
3211 name: "missing path",
3212 nfs: &core.NFSVolumeSource{Server: "my-server", Path: ""},
3213 errtype: field.ErrorTypeRequired,
3214 errfield: "path",
3215 }, {
3216 name: "abs path",
3217 nfs: &core.NFSVolumeSource{Server: "my-server", Path: "tmp"},
3218 errtype: field.ErrorTypeInvalid,
3219 errfield: "path",
3220 errdetail: "must be an absolute path",
3221 },
3222 }
3223
3224 for i, tc := range testCases {
3225 errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field"))
3226
3227 if len(errs) > 0 && tc.errtype == "" {
3228 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
3229 } else if len(errs) == 0 && tc.errtype != "" {
3230 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
3231 } else if len(errs) >= 1 {
3232 if errs[0].Type != tc.errtype {
3233 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
3234 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
3235 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
3236 } else if !strings.Contains(errs[0].Detail, tc.errdetail) {
3237 t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail)
3238 }
3239 }
3240 }
3241 }
3242
3243 func TestValidateGlusterfs(t *testing.T) {
3244 testCases := []struct {
3245 name string
3246 gfs *core.GlusterfsVolumeSource
3247 errtype field.ErrorType
3248 errfield string
3249 }{{
3250 name: "missing endpointname",
3251 gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"},
3252 errtype: field.ErrorTypeRequired,
3253 errfield: "endpoints",
3254 }, {
3255 name: "missing path",
3256 gfs: &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""},
3257 errtype: field.ErrorTypeRequired,
3258 errfield: "path",
3259 }, {
3260 name: "missing endpointname and path",
3261 gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""},
3262 errtype: field.ErrorTypeRequired,
3263 errfield: "endpoints",
3264 },
3265 }
3266
3267 for i, tc := range testCases {
3268 errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field"))
3269
3270 if len(errs) > 0 && tc.errtype == "" {
3271 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
3272 } else if len(errs) == 0 && tc.errtype != "" {
3273 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
3274 } else if len(errs) >= 1 {
3275 if errs[0].Type != tc.errtype {
3276 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
3277 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
3278 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
3279 }
3280 }
3281 }
3282 }
3283
3284 func TestValidateGlusterfsPersistentVolumeSource(t *testing.T) {
3285 var epNs *string
3286 namespace := ""
3287 epNs = &namespace
3288
3289 testCases := []struct {
3290 name string
3291 gfs *core.GlusterfsPersistentVolumeSource
3292 errtype field.ErrorType
3293 errfield string
3294 }{{
3295 name: "missing endpointname",
3296 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"},
3297 errtype: field.ErrorTypeRequired,
3298 errfield: "endpoints",
3299 }, {
3300 name: "missing path",
3301 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""},
3302 errtype: field.ErrorTypeRequired,
3303 errfield: "path",
3304 }, {
3305 name: "non null endpointnamespace with empty string",
3306 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs},
3307 errtype: field.ErrorTypeInvalid,
3308 errfield: "endpointsNamespace",
3309 }, {
3310 name: "missing endpointname and path",
3311 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""},
3312 errtype: field.ErrorTypeRequired,
3313 errfield: "endpoints",
3314 },
3315 }
3316
3317 for i, tc := range testCases {
3318 errs := validateGlusterfsPersistentVolumeSource(tc.gfs, field.NewPath("field"))
3319
3320 if len(errs) > 0 && tc.errtype == "" {
3321 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
3322 } else if len(errs) == 0 && tc.errtype != "" {
3323 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
3324 } else if len(errs) >= 1 {
3325 if errs[0].Type != tc.errtype {
3326 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
3327 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
3328 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
3329 }
3330 }
3331 }
3332 }
3333
3334 func TestValidateCSIVolumeSource(t *testing.T) {
3335 testCases := []struct {
3336 name string
3337 csi *core.CSIVolumeSource
3338 errtype field.ErrorType
3339 errfield string
3340 }{{
3341 name: "all required fields ok",
3342 csi: &core.CSIVolumeSource{Driver: "test-driver"},
3343 }, {
3344 name: "missing driver name",
3345 csi: &core.CSIVolumeSource{Driver: ""},
3346 errtype: field.ErrorTypeRequired,
3347 errfield: "driver",
3348 }, {
3349 name: "driver name: ok no punctuations",
3350 csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd"},
3351 }, {
3352 name: "driver name: ok dot only",
3353 csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage.csi.flex"},
3354 }, {
3355 name: "driver name: ok dash only",
3356 csi: &core.CSIVolumeSource{Driver: "io-kubernetes-storage-csi-flex"},
3357 }, {
3358 name: "driver name: invalid underscore",
3359 csi: &core.CSIVolumeSource{Driver: "io_kubernetes_storage_csi_flex"},
3360 errtype: field.ErrorTypeInvalid,
3361 errfield: "driver",
3362 }, {
3363 name: "driver name: invalid dot underscores",
3364 csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage_csi.flex"},
3365 errtype: field.ErrorTypeInvalid,
3366 errfield: "driver",
3367 }, {
3368 name: "driver name: ok beginning with number",
3369 csi: &core.CSIVolumeSource{Driver: "2io.kubernetes.storage-csi.flex"},
3370 }, {
3371 name: "driver name: ok ending with number",
3372 csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage-csi.flex2"},
3373 }, {
3374 name: "driver name: invalid dot dash underscores",
3375 csi: &core.CSIVolumeSource{Driver: "io.kubernetes-storage.csi_flex"},
3376 errtype: field.ErrorTypeInvalid,
3377 errfield: "driver",
3378 },
3379
3380 {
3381 name: "driver name: ok length 1",
3382 csi: &core.CSIVolumeSource{Driver: "a"},
3383 }, {
3384 name: "driver name: invalid length > 63",
3385 csi: &core.CSIVolumeSource{Driver: strings.Repeat("g", 65)},
3386 errtype: field.ErrorTypeTooLong,
3387 errfield: "driver",
3388 }, {
3389 name: "driver name: invalid start char",
3390 csi: &core.CSIVolumeSource{Driver: "_comgooglestoragecsigcepd"},
3391 errtype: field.ErrorTypeInvalid,
3392 errfield: "driver",
3393 }, {
3394 name: "driver name: invalid end char",
3395 csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd/"},
3396 errtype: field.ErrorTypeInvalid,
3397 errfield: "driver",
3398 }, {
3399 name: "driver name: invalid separators",
3400 csi: &core.CSIVolumeSource{Driver: "com/google/storage/csi~gcepd"},
3401 errtype: field.ErrorTypeInvalid,
3402 errfield: "driver",
3403 }, {
3404 name: "valid nodePublishSecretRef",
3405 csi: &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: "foobar"}},
3406 }, {
3407 name: "nodePublishSecretRef: invalid name missing",
3408 csi: &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: ""}},
3409 errtype: field.ErrorTypeRequired,
3410 errfield: "nodePublishSecretRef.name",
3411 },
3412 }
3413
3414 for i, tc := range testCases {
3415 errs := validateCSIVolumeSource(tc.csi, field.NewPath("field"))
3416
3417 if len(errs) > 0 && tc.errtype == "" {
3418 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
3419 } else if len(errs) == 0 && tc.errtype != "" {
3420 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
3421 } else if len(errs) >= 1 {
3422 if errs[0].Type != tc.errtype {
3423 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
3424 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
3425 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
3426 }
3427 }
3428 }
3429 }
3430
3431 func TestValidateCSIPersistentVolumeSource(t *testing.T) {
3432 testCases := []struct {
3433 name string
3434 csi *core.CSIPersistentVolumeSource
3435 errtype field.ErrorType
3436 errfield string
3437 }{{
3438 name: "all required fields ok",
3439 csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
3440 }, {
3441 name: "with default values ok",
3442 csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"},
3443 }, {
3444 name: "missing driver name",
3445 csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
3446 errtype: field.ErrorTypeRequired,
3447 errfield: "driver",
3448 }, {
3449 name: "missing volume handle",
3450 csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"},
3451 errtype: field.ErrorTypeRequired,
3452 errfield: "volumeHandle",
3453 }, {
3454 name: "driver name: ok no punctuations",
3455 csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"},
3456 }, {
3457 name: "driver name: ok dot only",
3458 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"},
3459 }, {
3460 name: "driver name: ok dash only",
3461 csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"},
3462 }, {
3463 name: "driver name: invalid underscore",
3464 csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"},
3465 errtype: field.ErrorTypeInvalid,
3466 errfield: "driver",
3467 }, {
3468 name: "driver name: invalid dot underscores",
3469 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
3470 errtype: field.ErrorTypeInvalid,
3471 errfield: "driver",
3472 }, {
3473 name: "driver name: ok beginning with number",
3474 csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"},
3475 }, {
3476 name: "driver name: ok ending with number",
3477 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"},
3478 }, {
3479 name: "driver name: invalid dot dash underscores",
3480 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"},
3481 errtype: field.ErrorTypeInvalid,
3482 errfield: "driver",
3483 }, {
3484 name: "driver name: invalid length 0",
3485 csi: &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"},
3486 errtype: field.ErrorTypeRequired,
3487 errfield: "driver",
3488 }, {
3489 name: "driver name: ok length 1",
3490 csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
3491 }, {
3492 name: "driver name: invalid length > 63",
3493 csi: &core.CSIPersistentVolumeSource{Driver: strings.Repeat("g", 65), VolumeHandle: "test-123"},
3494 errtype: field.ErrorTypeTooLong,
3495 errfield: "driver",
3496 }, {
3497 name: "driver name: invalid start char",
3498 csi: &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"},
3499 errtype: field.ErrorTypeInvalid,
3500 errfield: "driver",
3501 }, {
3502 name: "driver name: invalid end char",
3503 csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"},
3504 errtype: field.ErrorTypeInvalid,
3505 errfield: "driver",
3506 }, {
3507 name: "driver name: invalid separators",
3508 csi: &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"},
3509 errtype: field.ErrorTypeInvalid,
3510 errfield: "driver",
3511 }, {
3512 name: "controllerExpandSecretRef: invalid name missing",
3513 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}},
3514 errtype: field.ErrorTypeRequired,
3515 errfield: "controllerExpandSecretRef.name",
3516 }, {
3517 name: "controllerExpandSecretRef: invalid namespace missing",
3518 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}},
3519 errtype: field.ErrorTypeRequired,
3520 errfield: "controllerExpandSecretRef.namespace",
3521 }, {
3522 name: "valid controllerExpandSecretRef",
3523 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
3524 }, {
3525 name: "controllerPublishSecretRef: invalid name missing",
3526 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Namespace: "default"}},
3527 errtype: field.ErrorTypeRequired,
3528 errfield: "controllerPublishSecretRef.name",
3529 }, {
3530 name: "controllerPublishSecretRef: invalid namespace missing",
3531 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar"}},
3532 errtype: field.ErrorTypeRequired,
3533 errfield: "controllerPublishSecretRef.namespace",
3534 }, {
3535 name: "valid controllerPublishSecretRef",
3536 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
3537 }, {
3538 name: "valid nodePublishSecretRef",
3539 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
3540 }, {
3541 name: "nodePublishSecretRef: invalid name missing",
3542 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Namespace: "foobar"}},
3543 errtype: field.ErrorTypeRequired,
3544 errfield: "nodePublishSecretRef.name",
3545 }, {
3546 name: "nodePublishSecretRef: invalid namespace missing",
3547 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar"}},
3548 errtype: field.ErrorTypeRequired,
3549 errfield: "nodePublishSecretRef.namespace",
3550 }, {
3551 name: "nodeExpandSecretRef: invalid name missing",
3552 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Namespace: "default"}},
3553 errtype: field.ErrorTypeRequired,
3554 errfield: "nodeExpandSecretRef.name",
3555 }, {
3556 name: "nodeExpandSecretRef: invalid namespace missing",
3557 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar"}},
3558 errtype: field.ErrorTypeRequired,
3559 errfield: "nodeExpandSecretRef.namespace",
3560 }, {
3561 name: "valid nodeExpandSecretRef",
3562 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
3563 }, {
3564 name: "Invalid nodePublishSecretRef",
3565 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
3566 },
3567
3568
3569 {
3570 name: "valid nodeExpandSecretRef",
3571 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
3572 }, {
3573 name: "valid long nodeExpandSecretRef",
3574 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
3575 }, {
3576 name: "Invalid nodeExpandSecretRef",
3577 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
3578 errtype: field.ErrorTypeInvalid,
3579 errfield: "nodeExpandSecretRef.name",
3580 }, {
3581 name: "valid nodePublishSecretRef",
3582 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
3583 }, {
3584 name: "valid long nodePublishSecretRef",
3585 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
3586 }, {
3587 name: "Invalid nodePublishSecretRef",
3588 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
3589 errtype: field.ErrorTypeInvalid,
3590 errfield: "nodePublishSecretRef.name",
3591 }, {
3592 name: "valid ControllerExpandSecretRef",
3593 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
3594 }, {
3595 name: "valid long ControllerExpandSecretRef",
3596 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
3597 }, {
3598 name: "Invalid ControllerExpandSecretRef",
3599 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
3600 errtype: field.ErrorTypeInvalid,
3601 errfield: "controllerExpandSecretRef.name",
3602 },
3603 }
3604
3605 for i, tc := range testCases {
3606 errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field"))
3607
3608 if len(errs) > 0 && tc.errtype == "" {
3609 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
3610 } else if len(errs) == 0 && tc.errtype != "" {
3611 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
3612 } else if len(errs) >= 1 {
3613 if errs[0].Type != tc.errtype {
3614 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
3615 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
3616 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
3617 }
3618 }
3619 }
3620 }
3621
3622
3623
3624
3625 func TestValidateVolumes(t *testing.T) {
3626 validInitiatorName := "iqn.2015-02.example.com:init"
3627 invalidInitiatorName := "2015-02.example.com:init"
3628
3629 type verr struct {
3630 etype field.ErrorType
3631 field string
3632 detail string
3633 }
3634
3635 testCases := []struct {
3636 name string
3637 vol core.Volume
3638 errs []verr
3639 opts PodValidationOptions
3640 }{
3641
3642 {
3643 name: "valid alpha name",
3644 vol: core.Volume{
3645 Name: "empty",
3646 VolumeSource: core.VolumeSource{
3647 EmptyDir: &core.EmptyDirVolumeSource{},
3648 },
3649 },
3650 }, {
3651 name: "valid num name",
3652 vol: core.Volume{
3653 Name: "123",
3654 VolumeSource: core.VolumeSource{
3655 EmptyDir: &core.EmptyDirVolumeSource{},
3656 },
3657 },
3658 }, {
3659 name: "valid alphanum name",
3660 vol: core.Volume{
3661 Name: "empty-123",
3662 VolumeSource: core.VolumeSource{
3663 EmptyDir: &core.EmptyDirVolumeSource{},
3664 },
3665 },
3666 }, {
3667 name: "valid numalpha name",
3668 vol: core.Volume{
3669 Name: "123-empty",
3670 VolumeSource: core.VolumeSource{
3671 EmptyDir: &core.EmptyDirVolumeSource{},
3672 },
3673 },
3674 }, {
3675 name: "zero-length name",
3676 vol: core.Volume{
3677 Name: "",
3678 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
3679 },
3680 errs: []verr{{
3681 etype: field.ErrorTypeRequired,
3682 field: "name",
3683 }},
3684 }, {
3685 name: "name > 63 characters",
3686 vol: core.Volume{
3687 Name: strings.Repeat("a", 64),
3688 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
3689 },
3690 errs: []verr{{
3691 etype: field.ErrorTypeInvalid,
3692 field: "name",
3693 detail: "must be no more than",
3694 }},
3695 }, {
3696 name: "name has dots",
3697 vol: core.Volume{
3698 Name: "a.b.c",
3699 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
3700 },
3701 errs: []verr{{
3702 etype: field.ErrorTypeInvalid,
3703 field: "name",
3704 detail: "must not contain dots",
3705 }},
3706 }, {
3707 name: "name not a DNS label",
3708 vol: core.Volume{
3709 Name: "Not a DNS label!",
3710 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
3711 },
3712 errs: []verr{{
3713 etype: field.ErrorTypeInvalid,
3714 field: "name",
3715 detail: dnsLabelErrMsg,
3716 }},
3717 },
3718
3719 {
3720 name: "more than one source",
3721 vol: core.Volume{
3722 Name: "dups",
3723 VolumeSource: core.VolumeSource{
3724 EmptyDir: &core.EmptyDirVolumeSource{},
3725 HostPath: &core.HostPathVolumeSource{
3726 Path: "/mnt/path",
3727 Type: newHostPathType(string(core.HostPathDirectory)),
3728 },
3729 },
3730 },
3731 errs: []verr{{
3732 etype: field.ErrorTypeForbidden,
3733 field: "hostPath",
3734 detail: "may not specify more than 1 volume",
3735 }},
3736 },
3737
3738 {
3739 name: "default HostPath",
3740 vol: core.Volume{
3741 Name: "hostpath",
3742 VolumeSource: core.VolumeSource{
3743 HostPath: &core.HostPathVolumeSource{
3744 Path: "/mnt/path",
3745 Type: newHostPathType(string(core.HostPathDirectory)),
3746 },
3747 },
3748 },
3749 },
3750
3751 {
3752 name: "valid HostPath",
3753 vol: core.Volume{
3754 Name: "hostpath",
3755 VolumeSource: core.VolumeSource{
3756 HostPath: &core.HostPathVolumeSource{
3757 Path: "/mnt/path",
3758 Type: newHostPathType(string(core.HostPathSocket)),
3759 },
3760 },
3761 },
3762 },
3763
3764 {
3765 name: "invalid HostPath",
3766 vol: core.Volume{
3767 Name: "hostpath",
3768 VolumeSource: core.VolumeSource{
3769 HostPath: &core.HostPathVolumeSource{
3770 Path: "/mnt/path",
3771 Type: newHostPathType("invalid"),
3772 },
3773 },
3774 },
3775 errs: []verr{{
3776 etype: field.ErrorTypeNotSupported,
3777 field: "type",
3778 }},
3779 }, {
3780 name: "invalid HostPath backsteps",
3781 vol: core.Volume{
3782 Name: "hostpath",
3783 VolumeSource: core.VolumeSource{
3784 HostPath: &core.HostPathVolumeSource{
3785 Path: "/mnt/path/..",
3786 Type: newHostPathType(string(core.HostPathDirectory)),
3787 },
3788 },
3789 },
3790 errs: []verr{{
3791 etype: field.ErrorTypeInvalid,
3792 field: "path",
3793 detail: "must not contain '..'",
3794 }},
3795 },
3796
3797 {
3798 name: "valid GcePersistentDisk",
3799 vol: core.Volume{
3800 Name: "gce-pd",
3801 VolumeSource: core.VolumeSource{
3802 GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
3803 PDName: "my-PD",
3804 FSType: "ext4",
3805 Partition: 1,
3806 ReadOnly: false,
3807 },
3808 },
3809 },
3810 },
3811
3812 {
3813 name: "valid AWSElasticBlockStore",
3814 vol: core.Volume{
3815 Name: "aws-ebs",
3816 VolumeSource: core.VolumeSource{
3817 AWSElasticBlockStore: &core.AWSElasticBlockStoreVolumeSource{
3818 VolumeID: "my-PD",
3819 FSType: "ext4",
3820 Partition: 1,
3821 ReadOnly: false,
3822 },
3823 },
3824 },
3825 },
3826
3827 {
3828 name: "valid GitRepo",
3829 vol: core.Volume{
3830 Name: "git-repo",
3831 VolumeSource: core.VolumeSource{
3832 GitRepo: &core.GitRepoVolumeSource{
3833 Repository: "my-repo",
3834 Revision: "hashstring",
3835 Directory: "target",
3836 },
3837 },
3838 },
3839 }, {
3840 name: "valid GitRepo in .",
3841 vol: core.Volume{
3842 Name: "git-repo-dot",
3843 VolumeSource: core.VolumeSource{
3844 GitRepo: &core.GitRepoVolumeSource{
3845 Repository: "my-repo",
3846 Directory: ".",
3847 },
3848 },
3849 },
3850 }, {
3851 name: "valid GitRepo with .. in name",
3852 vol: core.Volume{
3853 Name: "git-repo-dot-dot-foo",
3854 VolumeSource: core.VolumeSource{
3855 GitRepo: &core.GitRepoVolumeSource{
3856 Repository: "my-repo",
3857 Directory: "..foo",
3858 },
3859 },
3860 },
3861 }, {
3862 name: "GitRepo starts with ../",
3863 vol: core.Volume{
3864 Name: "gitrepo",
3865 VolumeSource: core.VolumeSource{
3866 GitRepo: &core.GitRepoVolumeSource{
3867 Repository: "foo",
3868 Directory: "../dots/bar",
3869 },
3870 },
3871 },
3872 errs: []verr{{
3873 etype: field.ErrorTypeInvalid,
3874 field: "gitRepo.directory",
3875 detail: `must not contain '..'`,
3876 }},
3877 }, {
3878 name: "GitRepo contains ..",
3879 vol: core.Volume{
3880 Name: "gitrepo",
3881 VolumeSource: core.VolumeSource{
3882 GitRepo: &core.GitRepoVolumeSource{
3883 Repository: "foo",
3884 Directory: "dots/../bar",
3885 },
3886 },
3887 },
3888 errs: []verr{{
3889 etype: field.ErrorTypeInvalid,
3890 field: "gitRepo.directory",
3891 detail: `must not contain '..'`,
3892 }},
3893 }, {
3894 name: "GitRepo absolute target",
3895 vol: core.Volume{
3896 Name: "gitrepo",
3897 VolumeSource: core.VolumeSource{
3898 GitRepo: &core.GitRepoVolumeSource{
3899 Repository: "foo",
3900 Directory: "/abstarget",
3901 },
3902 },
3903 },
3904 errs: []verr{{
3905 etype: field.ErrorTypeInvalid,
3906 field: "gitRepo.directory",
3907 }},
3908 },
3909
3910 {
3911 name: "valid ISCSI",
3912 vol: core.Volume{
3913 Name: "iscsi",
3914 VolumeSource: core.VolumeSource{
3915 ISCSI: &core.ISCSIVolumeSource{
3916 TargetPortal: "127.0.0.1",
3917 IQN: "iqn.2015-02.example.com:test",
3918 Lun: 1,
3919 FSType: "ext4",
3920 ReadOnly: false,
3921 },
3922 },
3923 },
3924 }, {
3925 name: "valid IQN: eui format",
3926 vol: core.Volume{
3927 Name: "iscsi",
3928 VolumeSource: core.VolumeSource{
3929 ISCSI: &core.ISCSIVolumeSource{
3930 TargetPortal: "127.0.0.1",
3931 IQN: "eui.0123456789ABCDEF",
3932 Lun: 1,
3933 FSType: "ext4",
3934 ReadOnly: false,
3935 },
3936 },
3937 },
3938 }, {
3939 name: "valid IQN: naa format",
3940 vol: core.Volume{
3941 Name: "iscsi",
3942 VolumeSource: core.VolumeSource{
3943 ISCSI: &core.ISCSIVolumeSource{
3944 TargetPortal: "127.0.0.1",
3945 IQN: "naa.62004567BA64678D0123456789ABCDEF",
3946 Lun: 1,
3947 FSType: "ext4",
3948 ReadOnly: false,
3949 },
3950 },
3951 },
3952 }, {
3953 name: "empty portal",
3954 vol: core.Volume{
3955 Name: "iscsi",
3956 VolumeSource: core.VolumeSource{
3957 ISCSI: &core.ISCSIVolumeSource{
3958 TargetPortal: "",
3959 IQN: "iqn.2015-02.example.com:test",
3960 Lun: 1,
3961 FSType: "ext4",
3962 ReadOnly: false,
3963 },
3964 },
3965 },
3966 errs: []verr{{
3967 etype: field.ErrorTypeRequired,
3968 field: "iscsi.targetPortal",
3969 }},
3970 }, {
3971 name: "empty iqn",
3972 vol: core.Volume{
3973 Name: "iscsi",
3974 VolumeSource: core.VolumeSource{
3975 ISCSI: &core.ISCSIVolumeSource{
3976 TargetPortal: "127.0.0.1",
3977 IQN: "",
3978 Lun: 1,
3979 FSType: "ext4",
3980 ReadOnly: false,
3981 },
3982 },
3983 },
3984 errs: []verr{{
3985 etype: field.ErrorTypeRequired,
3986 field: "iscsi.iqn",
3987 }},
3988 }, {
3989 name: "invalid IQN: iqn format",
3990 vol: core.Volume{
3991 Name: "iscsi",
3992 VolumeSource: core.VolumeSource{
3993 ISCSI: &core.ISCSIVolumeSource{
3994 TargetPortal: "127.0.0.1",
3995 IQN: "iqn.2015-02.example.com:test;ls;",
3996 Lun: 1,
3997 FSType: "ext4",
3998 ReadOnly: false,
3999 },
4000 },
4001 },
4002 errs: []verr{{
4003 etype: field.ErrorTypeInvalid,
4004 field: "iscsi.iqn",
4005 }},
4006 }, {
4007 name: "invalid IQN: eui format",
4008 vol: core.Volume{
4009 Name: "iscsi",
4010 VolumeSource: core.VolumeSource{
4011 ISCSI: &core.ISCSIVolumeSource{
4012 TargetPortal: "127.0.0.1",
4013 IQN: "eui.0123456789ABCDEFGHIJ",
4014 Lun: 1,
4015 FSType: "ext4",
4016 ReadOnly: false,
4017 },
4018 },
4019 },
4020 errs: []verr{{
4021 etype: field.ErrorTypeInvalid,
4022 field: "iscsi.iqn",
4023 }},
4024 }, {
4025 name: "invalid IQN: naa format",
4026 vol: core.Volume{
4027 Name: "iscsi",
4028 VolumeSource: core.VolumeSource{
4029 ISCSI: &core.ISCSIVolumeSource{
4030 TargetPortal: "127.0.0.1",
4031 IQN: "naa.62004567BA_4-78D.123456789ABCDEF",
4032 Lun: 1,
4033 FSType: "ext4",
4034 ReadOnly: false,
4035 },
4036 },
4037 },
4038 errs: []verr{{
4039 etype: field.ErrorTypeInvalid,
4040 field: "iscsi.iqn",
4041 }},
4042 }, {
4043 name: "valid initiatorName",
4044 vol: core.Volume{
4045 Name: "iscsi",
4046 VolumeSource: core.VolumeSource{
4047 ISCSI: &core.ISCSIVolumeSource{
4048 TargetPortal: "127.0.0.1",
4049 IQN: "iqn.2015-02.example.com:test",
4050 Lun: 1,
4051 InitiatorName: &validInitiatorName,
4052 FSType: "ext4",
4053 ReadOnly: false,
4054 },
4055 },
4056 },
4057 }, {
4058 name: "invalid initiatorName",
4059 vol: core.Volume{
4060 Name: "iscsi",
4061 VolumeSource: core.VolumeSource{
4062 ISCSI: &core.ISCSIVolumeSource{
4063 TargetPortal: "127.0.0.1",
4064 IQN: "iqn.2015-02.example.com:test",
4065 Lun: 1,
4066 InitiatorName: &invalidInitiatorName,
4067 FSType: "ext4",
4068 ReadOnly: false,
4069 },
4070 },
4071 },
4072 errs: []verr{{
4073 etype: field.ErrorTypeInvalid,
4074 field: "iscsi.initiatorname",
4075 }},
4076 }, {
4077 name: "empty secret",
4078 vol: core.Volume{
4079 Name: "iscsi",
4080 VolumeSource: core.VolumeSource{
4081 ISCSI: &core.ISCSIVolumeSource{
4082 TargetPortal: "127.0.0.1",
4083 IQN: "iqn.2015-02.example.com:test",
4084 Lun: 1,
4085 FSType: "ext4",
4086 ReadOnly: false,
4087 DiscoveryCHAPAuth: true,
4088 },
4089 },
4090 },
4091 errs: []verr{{
4092 etype: field.ErrorTypeRequired,
4093 field: "iscsi.secretRef",
4094 }},
4095 }, {
4096 name: "empty secret",
4097 vol: core.Volume{
4098 Name: "iscsi",
4099 VolumeSource: core.VolumeSource{
4100 ISCSI: &core.ISCSIVolumeSource{
4101 TargetPortal: "127.0.0.1",
4102 IQN: "iqn.2015-02.example.com:test",
4103 Lun: 1,
4104 FSType: "ext4",
4105 ReadOnly: false,
4106 SessionCHAPAuth: true,
4107 },
4108 },
4109 },
4110 errs: []verr{{
4111 etype: field.ErrorTypeRequired,
4112 field: "iscsi.secretRef",
4113 }},
4114 },
4115
4116 {
4117 name: "valid Secret",
4118 vol: core.Volume{
4119 Name: "secret",
4120 VolumeSource: core.VolumeSource{
4121 Secret: &core.SecretVolumeSource{
4122 SecretName: "my-secret",
4123 },
4124 },
4125 },
4126 }, {
4127 name: "valid Secret with defaultMode",
4128 vol: core.Volume{
4129 Name: "secret",
4130 VolumeSource: core.VolumeSource{
4131 Secret: &core.SecretVolumeSource{
4132 SecretName: "my-secret",
4133 DefaultMode: utilpointer.Int32(0644),
4134 },
4135 },
4136 },
4137 }, {
4138 name: "valid Secret with projection and mode",
4139 vol: core.Volume{
4140 Name: "secret",
4141 VolumeSource: core.VolumeSource{
4142 Secret: &core.SecretVolumeSource{
4143 SecretName: "my-secret",
4144 Items: []core.KeyToPath{{
4145 Key: "key",
4146 Path: "filename",
4147 Mode: utilpointer.Int32(0644),
4148 }},
4149 },
4150 },
4151 },
4152 }, {
4153 name: "valid Secret with subdir projection",
4154 vol: core.Volume{
4155 Name: "secret",
4156 VolumeSource: core.VolumeSource{
4157 Secret: &core.SecretVolumeSource{
4158 SecretName: "my-secret",
4159 Items: []core.KeyToPath{{
4160 Key: "key",
4161 Path: "dir/filename",
4162 }},
4163 },
4164 },
4165 },
4166 }, {
4167 name: "secret with missing path",
4168 vol: core.Volume{
4169 Name: "secret",
4170 VolumeSource: core.VolumeSource{
4171 Secret: &core.SecretVolumeSource{
4172 SecretName: "s",
4173 Items: []core.KeyToPath{{Key: "key", Path: ""}},
4174 },
4175 },
4176 },
4177 errs: []verr{{
4178 etype: field.ErrorTypeRequired,
4179 field: "secret.items[0].path",
4180 }},
4181 }, {
4182 name: "secret with leading ..",
4183 vol: core.Volume{
4184 Name: "secret",
4185 VolumeSource: core.VolumeSource{
4186 Secret: &core.SecretVolumeSource{
4187 SecretName: "s",
4188 Items: []core.KeyToPath{{Key: "key", Path: "../foo"}},
4189 },
4190 },
4191 },
4192 errs: []verr{{
4193 etype: field.ErrorTypeInvalid,
4194 field: "secret.items[0].path",
4195 }},
4196 }, {
4197 name: "secret with .. inside",
4198 vol: core.Volume{
4199 Name: "secret",
4200 VolumeSource: core.VolumeSource{
4201 Secret: &core.SecretVolumeSource{
4202 SecretName: "s",
4203 Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
4204 },
4205 },
4206 },
4207 errs: []verr{{
4208 etype: field.ErrorTypeInvalid,
4209 field: "secret.items[0].path",
4210 }},
4211 }, {
4212 name: "secret with invalid positive defaultMode",
4213 vol: core.Volume{
4214 Name: "secret",
4215 VolumeSource: core.VolumeSource{
4216 Secret: &core.SecretVolumeSource{
4217 SecretName: "s",
4218 DefaultMode: utilpointer.Int32(01000),
4219 },
4220 },
4221 },
4222 errs: []verr{{
4223 etype: field.ErrorTypeInvalid,
4224 field: "secret.defaultMode",
4225 }},
4226 }, {
4227 name: "secret with invalid negative defaultMode",
4228 vol: core.Volume{
4229 Name: "secret",
4230 VolumeSource: core.VolumeSource{
4231 Secret: &core.SecretVolumeSource{
4232 SecretName: "s",
4233 DefaultMode: utilpointer.Int32(-1),
4234 },
4235 },
4236 },
4237 errs: []verr{{
4238 etype: field.ErrorTypeInvalid,
4239 field: "secret.defaultMode",
4240 }},
4241 },
4242
4243 {
4244 name: "valid ConfigMap",
4245 vol: core.Volume{
4246 Name: "cfgmap",
4247 VolumeSource: core.VolumeSource{
4248 ConfigMap: &core.ConfigMapVolumeSource{
4249 LocalObjectReference: core.LocalObjectReference{
4250 Name: "my-cfgmap",
4251 },
4252 },
4253 },
4254 },
4255 }, {
4256 name: "valid ConfigMap with defaultMode",
4257 vol: core.Volume{
4258 Name: "cfgmap",
4259 VolumeSource: core.VolumeSource{
4260 ConfigMap: &core.ConfigMapVolumeSource{
4261 LocalObjectReference: core.LocalObjectReference{
4262 Name: "my-cfgmap",
4263 },
4264 DefaultMode: utilpointer.Int32(0644),
4265 },
4266 },
4267 },
4268 }, {
4269 name: "valid ConfigMap with projection and mode",
4270 vol: core.Volume{
4271 Name: "cfgmap",
4272 VolumeSource: core.VolumeSource{
4273 ConfigMap: &core.ConfigMapVolumeSource{
4274 LocalObjectReference: core.LocalObjectReference{
4275 Name: "my-cfgmap"},
4276 Items: []core.KeyToPath{{
4277 Key: "key",
4278 Path: "filename",
4279 Mode: utilpointer.Int32(0644),
4280 }},
4281 },
4282 },
4283 },
4284 }, {
4285 name: "valid ConfigMap with subdir projection",
4286 vol: core.Volume{
4287 Name: "cfgmap",
4288 VolumeSource: core.VolumeSource{
4289 ConfigMap: &core.ConfigMapVolumeSource{
4290 LocalObjectReference: core.LocalObjectReference{
4291 Name: "my-cfgmap"},
4292 Items: []core.KeyToPath{{
4293 Key: "key",
4294 Path: "dir/filename",
4295 }},
4296 },
4297 },
4298 },
4299 }, {
4300 name: "configmap with missing path",
4301 vol: core.Volume{
4302 Name: "cfgmap",
4303 VolumeSource: core.VolumeSource{
4304 ConfigMap: &core.ConfigMapVolumeSource{
4305 LocalObjectReference: core.LocalObjectReference{Name: "c"},
4306 Items: []core.KeyToPath{{Key: "key", Path: ""}},
4307 },
4308 },
4309 },
4310 errs: []verr{{
4311 etype: field.ErrorTypeRequired,
4312 field: "configMap.items[0].path",
4313 }},
4314 }, {
4315 name: "configmap with leading ..",
4316 vol: core.Volume{
4317 Name: "cfgmap",
4318 VolumeSource: core.VolumeSource{
4319 ConfigMap: &core.ConfigMapVolumeSource{
4320 LocalObjectReference: core.LocalObjectReference{Name: "c"},
4321 Items: []core.KeyToPath{{Key: "key", Path: "../foo"}},
4322 },
4323 },
4324 },
4325 errs: []verr{{
4326 etype: field.ErrorTypeInvalid,
4327 field: "configMap.items[0].path",
4328 }},
4329 }, {
4330 name: "configmap with .. inside",
4331 vol: core.Volume{
4332 Name: "cfgmap",
4333 VolumeSource: core.VolumeSource{
4334 ConfigMap: &core.ConfigMapVolumeSource{
4335 LocalObjectReference: core.LocalObjectReference{Name: "c"},
4336 Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
4337 },
4338 },
4339 },
4340 errs: []verr{{
4341 etype: field.ErrorTypeInvalid,
4342 field: "configMap.items[0].path",
4343 }},
4344 }, {
4345 name: "configmap with invalid positive defaultMode",
4346 vol: core.Volume{
4347 Name: "cfgmap",
4348 VolumeSource: core.VolumeSource{
4349 ConfigMap: &core.ConfigMapVolumeSource{
4350 LocalObjectReference: core.LocalObjectReference{Name: "c"},
4351 DefaultMode: utilpointer.Int32(01000),
4352 },
4353 },
4354 },
4355 errs: []verr{{
4356 etype: field.ErrorTypeInvalid,
4357 field: "configMap.defaultMode",
4358 }},
4359 }, {
4360 name: "configmap with invalid negative defaultMode",
4361 vol: core.Volume{
4362 Name: "cfgmap",
4363 VolumeSource: core.VolumeSource{
4364 ConfigMap: &core.ConfigMapVolumeSource{
4365 LocalObjectReference: core.LocalObjectReference{Name: "c"},
4366 DefaultMode: utilpointer.Int32(-1),
4367 },
4368 },
4369 },
4370 errs: []verr{{
4371 etype: field.ErrorTypeInvalid,
4372 field: "configMap.defaultMode",
4373 }},
4374 },
4375
4376 {
4377 name: "valid Glusterfs",
4378 vol: core.Volume{
4379 Name: "glusterfs",
4380 VolumeSource: core.VolumeSource{
4381 Glusterfs: &core.GlusterfsVolumeSource{
4382 EndpointsName: "host1",
4383 Path: "path",
4384 ReadOnly: false,
4385 },
4386 },
4387 },
4388 }, {
4389 name: "empty hosts",
4390 vol: core.Volume{
4391 Name: "glusterfs",
4392 VolumeSource: core.VolumeSource{
4393 Glusterfs: &core.GlusterfsVolumeSource{
4394 EndpointsName: "",
4395 Path: "path",
4396 ReadOnly: false,
4397 },
4398 },
4399 },
4400 errs: []verr{{
4401 etype: field.ErrorTypeRequired,
4402 field: "glusterfs.endpoints",
4403 }},
4404 }, {
4405 name: "empty path",
4406 vol: core.Volume{
4407 Name: "glusterfs",
4408 VolumeSource: core.VolumeSource{
4409 Glusterfs: &core.GlusterfsVolumeSource{
4410 EndpointsName: "host",
4411 Path: "",
4412 ReadOnly: false,
4413 },
4414 },
4415 },
4416 errs: []verr{{
4417 etype: field.ErrorTypeRequired,
4418 field: "glusterfs.path",
4419 }},
4420 },
4421
4422 {
4423 name: "valid Flocker -- datasetUUID",
4424 vol: core.Volume{
4425 Name: "flocker",
4426 VolumeSource: core.VolumeSource{
4427 Flocker: &core.FlockerVolumeSource{
4428 DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
4429 },
4430 },
4431 },
4432 }, {
4433 name: "valid Flocker -- datasetName",
4434 vol: core.Volume{
4435 Name: "flocker",
4436 VolumeSource: core.VolumeSource{
4437 Flocker: &core.FlockerVolumeSource{
4438 DatasetName: "datasetName",
4439 },
4440 },
4441 },
4442 }, {
4443 name: "both empty",
4444 vol: core.Volume{
4445 Name: "flocker",
4446 VolumeSource: core.VolumeSource{
4447 Flocker: &core.FlockerVolumeSource{
4448 DatasetName: "",
4449 },
4450 },
4451 },
4452 errs: []verr{{
4453 etype: field.ErrorTypeRequired,
4454 field: "flocker",
4455 }},
4456 }, {
4457 name: "both specified",
4458 vol: core.Volume{
4459 Name: "flocker",
4460 VolumeSource: core.VolumeSource{
4461 Flocker: &core.FlockerVolumeSource{
4462 DatasetName: "datasetName",
4463 DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
4464 },
4465 },
4466 },
4467 errs: []verr{{
4468 etype: field.ErrorTypeInvalid,
4469 field: "flocker",
4470 }},
4471 }, {
4472 name: "slash in flocker datasetName",
4473 vol: core.Volume{
4474 Name: "flocker",
4475 VolumeSource: core.VolumeSource{
4476 Flocker: &core.FlockerVolumeSource{
4477 DatasetName: "foo/bar",
4478 },
4479 },
4480 },
4481 errs: []verr{{
4482 etype: field.ErrorTypeInvalid,
4483 field: "flocker.datasetName",
4484 detail: "must not contain '/'",
4485 }},
4486 },
4487
4488 {
4489 name: "valid RBD",
4490 vol: core.Volume{
4491 Name: "rbd",
4492 VolumeSource: core.VolumeSource{
4493 RBD: &core.RBDVolumeSource{
4494 CephMonitors: []string{"foo"},
4495 RBDImage: "bar",
4496 FSType: "ext4",
4497 },
4498 },
4499 },
4500 }, {
4501 name: "empty rbd monitors",
4502 vol: core.Volume{
4503 Name: "rbd",
4504 VolumeSource: core.VolumeSource{
4505 RBD: &core.RBDVolumeSource{
4506 CephMonitors: []string{},
4507 RBDImage: "bar",
4508 FSType: "ext4",
4509 },
4510 },
4511 },
4512 errs: []verr{{
4513 etype: field.ErrorTypeRequired,
4514 field: "rbd.monitors",
4515 }},
4516 }, {
4517 name: "empty image",
4518 vol: core.Volume{
4519 Name: "rbd",
4520 VolumeSource: core.VolumeSource{
4521 RBD: &core.RBDVolumeSource{
4522 CephMonitors: []string{"foo"},
4523 RBDImage: "",
4524 FSType: "ext4",
4525 },
4526 },
4527 },
4528 errs: []verr{{
4529 etype: field.ErrorTypeRequired,
4530 field: "rbd.image",
4531 }},
4532 },
4533
4534 {
4535 name: "valid Cinder",
4536 vol: core.Volume{
4537 Name: "cinder",
4538 VolumeSource: core.VolumeSource{
4539 Cinder: &core.CinderVolumeSource{
4540 VolumeID: "29ea5088-4f60-4757-962e-dba678767887",
4541 FSType: "ext4",
4542 ReadOnly: false,
4543 },
4544 },
4545 },
4546 },
4547
4548 {
4549 name: "valid CephFS",
4550 vol: core.Volume{
4551 Name: "cephfs",
4552 VolumeSource: core.VolumeSource{
4553 CephFS: &core.CephFSVolumeSource{
4554 Monitors: []string{"foo"},
4555 },
4556 },
4557 },
4558 }, {
4559 name: "empty cephfs monitors",
4560 vol: core.Volume{
4561 Name: "cephfs",
4562 VolumeSource: core.VolumeSource{
4563 CephFS: &core.CephFSVolumeSource{
4564 Monitors: []string{},
4565 },
4566 },
4567 },
4568 errs: []verr{{
4569 etype: field.ErrorTypeRequired,
4570 field: "cephfs.monitors",
4571 }},
4572 },
4573
4574 {
4575 name: "valid DownwardAPI",
4576 vol: core.Volume{
4577 Name: "downwardapi",
4578 VolumeSource: core.VolumeSource{
4579 DownwardAPI: &core.DownwardAPIVolumeSource{
4580 Items: []core.DownwardAPIVolumeFile{{
4581 Path: "labels",
4582 FieldRef: &core.ObjectFieldSelector{
4583 APIVersion: "v1",
4584 FieldPath: "metadata.labels",
4585 },
4586 }, {
4587 Path: "labels with subscript",
4588 FieldRef: &core.ObjectFieldSelector{
4589 APIVersion: "v1",
4590 FieldPath: "metadata.labels['key']",
4591 },
4592 }, {
4593 Path: "labels with complex subscript",
4594 FieldRef: &core.ObjectFieldSelector{
4595 APIVersion: "v1",
4596 FieldPath: "metadata.labels['test.example.com/key']",
4597 },
4598 }, {
4599 Path: "annotations",
4600 FieldRef: &core.ObjectFieldSelector{
4601 APIVersion: "v1",
4602 FieldPath: "metadata.annotations",
4603 },
4604 }, {
4605 Path: "annotations with subscript",
4606 FieldRef: &core.ObjectFieldSelector{
4607 APIVersion: "v1",
4608 FieldPath: "metadata.annotations['key']",
4609 },
4610 }, {
4611 Path: "annotations with complex subscript",
4612 FieldRef: &core.ObjectFieldSelector{
4613 APIVersion: "v1",
4614 FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']",
4615 },
4616 }, {
4617 Path: "namespace",
4618 FieldRef: &core.ObjectFieldSelector{
4619 APIVersion: "v1",
4620 FieldPath: "metadata.namespace",
4621 },
4622 }, {
4623 Path: "name",
4624 FieldRef: &core.ObjectFieldSelector{
4625 APIVersion: "v1",
4626 FieldPath: "metadata.name",
4627 },
4628 }, {
4629 Path: "path/with/subdirs",
4630 FieldRef: &core.ObjectFieldSelector{
4631 APIVersion: "v1",
4632 FieldPath: "metadata.labels",
4633 },
4634 }, {
4635 Path: "path/./withdot",
4636 FieldRef: &core.ObjectFieldSelector{
4637 APIVersion: "v1",
4638 FieldPath: "metadata.labels",
4639 },
4640 }, {
4641 Path: "path/with/embedded..dotdot",
4642 FieldRef: &core.ObjectFieldSelector{
4643 APIVersion: "v1",
4644 FieldPath: "metadata.labels",
4645 },
4646 }, {
4647 Path: "path/with/leading/..dotdot",
4648 FieldRef: &core.ObjectFieldSelector{
4649 APIVersion: "v1",
4650 FieldPath: "metadata.labels",
4651 },
4652 }, {
4653 Path: "cpu_limit",
4654 ResourceFieldRef: &core.ResourceFieldSelector{
4655 ContainerName: "test-container",
4656 Resource: "limits.cpu",
4657 },
4658 }, {
4659 Path: "cpu_request",
4660 ResourceFieldRef: &core.ResourceFieldSelector{
4661 ContainerName: "test-container",
4662 Resource: "requests.cpu",
4663 },
4664 }, {
4665 Path: "memory_limit",
4666 ResourceFieldRef: &core.ResourceFieldSelector{
4667 ContainerName: "test-container",
4668 Resource: "limits.memory",
4669 },
4670 }, {
4671 Path: "memory_request",
4672 ResourceFieldRef: &core.ResourceFieldSelector{
4673 ContainerName: "test-container",
4674 Resource: "requests.memory",
4675 },
4676 }},
4677 },
4678 },
4679 },
4680 }, {
4681 name: "hugepages-downwardAPI-enabled",
4682 vol: core.Volume{
4683 Name: "downwardapi",
4684 VolumeSource: core.VolumeSource{
4685 DownwardAPI: &core.DownwardAPIVolumeSource{
4686 Items: []core.DownwardAPIVolumeFile{{
4687 Path: "hugepages_request",
4688 ResourceFieldRef: &core.ResourceFieldSelector{
4689 ContainerName: "test-container",
4690 Resource: "requests.hugepages-2Mi",
4691 },
4692 }, {
4693 Path: "hugepages_limit",
4694 ResourceFieldRef: &core.ResourceFieldSelector{
4695 ContainerName: "test-container",
4696 Resource: "limits.hugepages-2Mi",
4697 },
4698 }},
4699 },
4700 },
4701 },
4702 }, {
4703 name: "downapi valid defaultMode",
4704 vol: core.Volume{
4705 Name: "downapi",
4706 VolumeSource: core.VolumeSource{
4707 DownwardAPI: &core.DownwardAPIVolumeSource{
4708 DefaultMode: utilpointer.Int32(0644),
4709 },
4710 },
4711 },
4712 }, {
4713 name: "downapi valid item mode",
4714 vol: core.Volume{
4715 Name: "downapi",
4716 VolumeSource: core.VolumeSource{
4717 DownwardAPI: &core.DownwardAPIVolumeSource{
4718 Items: []core.DownwardAPIVolumeFile{{
4719 Mode: utilpointer.Int32(0644),
4720 Path: "path",
4721 FieldRef: &core.ObjectFieldSelector{
4722 APIVersion: "v1",
4723 FieldPath: "metadata.labels",
4724 },
4725 }},
4726 },
4727 },
4728 },
4729 }, {
4730 name: "downapi invalid positive item mode",
4731 vol: core.Volume{
4732 Name: "downapi",
4733 VolumeSource: core.VolumeSource{
4734 DownwardAPI: &core.DownwardAPIVolumeSource{
4735 Items: []core.DownwardAPIVolumeFile{{
4736 Mode: utilpointer.Int32(01000),
4737 Path: "path",
4738 FieldRef: &core.ObjectFieldSelector{
4739 APIVersion: "v1",
4740 FieldPath: "metadata.labels",
4741 },
4742 }},
4743 },
4744 },
4745 },
4746 errs: []verr{{
4747 etype: field.ErrorTypeInvalid,
4748 field: "downwardAPI.mode",
4749 }},
4750 }, {
4751 name: "downapi invalid negative item mode",
4752 vol: core.Volume{
4753 Name: "downapi",
4754 VolumeSource: core.VolumeSource{
4755 DownwardAPI: &core.DownwardAPIVolumeSource{
4756 Items: []core.DownwardAPIVolumeFile{{
4757 Mode: utilpointer.Int32(-1),
4758 Path: "path",
4759 FieldRef: &core.ObjectFieldSelector{
4760 APIVersion: "v1",
4761 FieldPath: "metadata.labels",
4762 },
4763 }},
4764 },
4765 },
4766 },
4767 errs: []verr{{
4768 etype: field.ErrorTypeInvalid,
4769 field: "downwardAPI.mode",
4770 }},
4771 }, {
4772 name: "downapi empty metatada path",
4773 vol: core.Volume{
4774 Name: "downapi",
4775 VolumeSource: core.VolumeSource{
4776 DownwardAPI: &core.DownwardAPIVolumeSource{
4777 Items: []core.DownwardAPIVolumeFile{{
4778 Path: "",
4779 FieldRef: &core.ObjectFieldSelector{
4780 APIVersion: "v1",
4781 FieldPath: "metadata.labels",
4782 },
4783 }},
4784 },
4785 },
4786 },
4787 errs: []verr{{
4788 etype: field.ErrorTypeRequired,
4789 field: "downwardAPI.path",
4790 }},
4791 }, {
4792 name: "downapi absolute path",
4793 vol: core.Volume{
4794 Name: "downapi",
4795 VolumeSource: core.VolumeSource{
4796 DownwardAPI: &core.DownwardAPIVolumeSource{
4797 Items: []core.DownwardAPIVolumeFile{{
4798 Path: "/absolutepath",
4799 FieldRef: &core.ObjectFieldSelector{
4800 APIVersion: "v1",
4801 FieldPath: "metadata.labels",
4802 },
4803 }},
4804 },
4805 },
4806 },
4807 errs: []verr{{
4808 etype: field.ErrorTypeInvalid,
4809 field: "downwardAPI.path",
4810 }},
4811 }, {
4812 name: "downapi dot dot path",
4813 vol: core.Volume{
4814 Name: "downapi",
4815 VolumeSource: core.VolumeSource{
4816 DownwardAPI: &core.DownwardAPIVolumeSource{
4817 Items: []core.DownwardAPIVolumeFile{{
4818 Path: "../../passwd",
4819 FieldRef: &core.ObjectFieldSelector{
4820 APIVersion: "v1",
4821 FieldPath: "metadata.labels",
4822 },
4823 }},
4824 },
4825 },
4826 },
4827 errs: []verr{{
4828 etype: field.ErrorTypeInvalid,
4829 field: "downwardAPI.path",
4830 detail: `must not contain '..'`,
4831 }},
4832 }, {
4833 name: "downapi dot dot file name",
4834 vol: core.Volume{
4835 Name: "downapi",
4836 VolumeSource: core.VolumeSource{
4837 DownwardAPI: &core.DownwardAPIVolumeSource{
4838 Items: []core.DownwardAPIVolumeFile{{
4839 Path: "..badFileName",
4840 FieldRef: &core.ObjectFieldSelector{
4841 APIVersion: "v1",
4842 FieldPath: "metadata.labels",
4843 },
4844 }},
4845 },
4846 },
4847 },
4848 errs: []verr{{
4849 etype: field.ErrorTypeInvalid,
4850 field: "downwardAPI.path",
4851 detail: `must not start with '..'`,
4852 }},
4853 }, {
4854 name: "downapi dot dot first level dirent",
4855 vol: core.Volume{
4856 Name: "downapi",
4857 VolumeSource: core.VolumeSource{
4858 DownwardAPI: &core.DownwardAPIVolumeSource{
4859 Items: []core.DownwardAPIVolumeFile{{
4860 Path: "..badDirName/goodFileName",
4861 FieldRef: &core.ObjectFieldSelector{
4862 APIVersion: "v1",
4863 FieldPath: "metadata.labels",
4864 },
4865 }},
4866 },
4867 },
4868 },
4869 errs: []verr{{
4870 etype: field.ErrorTypeInvalid,
4871 field: "downwardAPI.path",
4872 detail: `must not start with '..'`,
4873 }},
4874 }, {
4875 name: "downapi fieldRef and ResourceFieldRef together",
4876 vol: core.Volume{
4877 Name: "downapi",
4878 VolumeSource: core.VolumeSource{
4879 DownwardAPI: &core.DownwardAPIVolumeSource{
4880 Items: []core.DownwardAPIVolumeFile{{
4881 Path: "test",
4882 FieldRef: &core.ObjectFieldSelector{
4883 APIVersion: "v1",
4884 FieldPath: "metadata.labels",
4885 },
4886 ResourceFieldRef: &core.ResourceFieldSelector{
4887 ContainerName: "test-container",
4888 Resource: "requests.memory",
4889 },
4890 }},
4891 },
4892 },
4893 },
4894 errs: []verr{{
4895 etype: field.ErrorTypeInvalid,
4896 field: "downwardAPI",
4897 detail: "fieldRef and resourceFieldRef can not be specified simultaneously",
4898 }},
4899 }, {
4900 name: "downapi invalid positive defaultMode",
4901 vol: core.Volume{
4902 Name: "downapi",
4903 VolumeSource: core.VolumeSource{
4904 DownwardAPI: &core.DownwardAPIVolumeSource{
4905 DefaultMode: utilpointer.Int32(01000),
4906 },
4907 },
4908 },
4909 errs: []verr{{
4910 etype: field.ErrorTypeInvalid,
4911 field: "downwardAPI.defaultMode",
4912 }},
4913 }, {
4914 name: "downapi invalid negative defaultMode",
4915 vol: core.Volume{
4916 Name: "downapi",
4917 VolumeSource: core.VolumeSource{
4918 DownwardAPI: &core.DownwardAPIVolumeSource{
4919 DefaultMode: utilpointer.Int32(-1),
4920 },
4921 },
4922 },
4923 errs: []verr{{
4924 etype: field.ErrorTypeInvalid,
4925 field: "downwardAPI.defaultMode",
4926 }},
4927 },
4928
4929 {
4930 name: "FC valid targetWWNs and lun",
4931 vol: core.Volume{
4932 Name: "fc",
4933 VolumeSource: core.VolumeSource{
4934 FC: &core.FCVolumeSource{
4935 TargetWWNs: []string{"some_wwn"},
4936 Lun: utilpointer.Int32(1),
4937 FSType: "ext4",
4938 ReadOnly: false,
4939 },
4940 },
4941 },
4942 }, {
4943 name: "FC valid wwids",
4944 vol: core.Volume{
4945 Name: "fc",
4946 VolumeSource: core.VolumeSource{
4947 FC: &core.FCVolumeSource{
4948 WWIDs: []string{"some_wwid"},
4949 FSType: "ext4",
4950 ReadOnly: false,
4951 },
4952 },
4953 },
4954 }, {
4955 name: "FC empty targetWWNs and wwids",
4956 vol: core.Volume{
4957 Name: "fc",
4958 VolumeSource: core.VolumeSource{
4959 FC: &core.FCVolumeSource{
4960 TargetWWNs: []string{},
4961 Lun: utilpointer.Int32(1),
4962 WWIDs: []string{},
4963 FSType: "ext4",
4964 ReadOnly: false,
4965 },
4966 },
4967 },
4968 errs: []verr{{
4969 etype: field.ErrorTypeRequired,
4970 field: "fc.targetWWNs",
4971 detail: "must specify either targetWWNs or wwids",
4972 }},
4973 }, {
4974 name: "FC invalid: both targetWWNs and wwids simultaneously",
4975 vol: core.Volume{
4976 Name: "fc",
4977 VolumeSource: core.VolumeSource{
4978 FC: &core.FCVolumeSource{
4979 TargetWWNs: []string{"some_wwn"},
4980 Lun: utilpointer.Int32(1),
4981 WWIDs: []string{"some_wwid"},
4982 FSType: "ext4",
4983 ReadOnly: false,
4984 },
4985 },
4986 },
4987 errs: []verr{{
4988 etype: field.ErrorTypeInvalid,
4989 field: "fc.targetWWNs",
4990 detail: "targetWWNs and wwids can not be specified simultaneously",
4991 }},
4992 }, {
4993 name: "FC valid targetWWNs and empty lun",
4994 vol: core.Volume{
4995 Name: "fc",
4996 VolumeSource: core.VolumeSource{
4997 FC: &core.FCVolumeSource{
4998 TargetWWNs: []string{"wwn"},
4999 Lun: nil,
5000 FSType: "ext4",
5001 ReadOnly: false,
5002 },
5003 },
5004 },
5005 errs: []verr{{
5006 etype: field.ErrorTypeRequired,
5007 field: "fc.lun",
5008 detail: "lun is required if targetWWNs is specified",
5009 }},
5010 }, {
5011 name: "FC valid targetWWNs and invalid lun",
5012 vol: core.Volume{
5013 Name: "fc",
5014 VolumeSource: core.VolumeSource{
5015 FC: &core.FCVolumeSource{
5016 TargetWWNs: []string{"wwn"},
5017 Lun: utilpointer.Int32(256),
5018 FSType: "ext4",
5019 ReadOnly: false,
5020 },
5021 },
5022 },
5023 errs: []verr{{
5024 etype: field.ErrorTypeInvalid,
5025 field: "fc.lun",
5026 detail: validation.InclusiveRangeError(0, 255),
5027 }},
5028 },
5029
5030 {
5031 name: "valid FlexVolume",
5032 vol: core.Volume{
5033 Name: "flex-volume",
5034 VolumeSource: core.VolumeSource{
5035 FlexVolume: &core.FlexVolumeSource{
5036 Driver: "kubernetes.io/blue",
5037 FSType: "ext4",
5038 },
5039 },
5040 },
5041 },
5042
5043 {
5044 name: "valid AzureFile",
5045 vol: core.Volume{
5046 Name: "azure-file",
5047 VolumeSource: core.VolumeSource{
5048 AzureFile: &core.AzureFileVolumeSource{
5049 SecretName: "key",
5050 ShareName: "share",
5051 ReadOnly: false,
5052 },
5053 },
5054 },
5055 }, {
5056 name: "AzureFile empty secret",
5057 vol: core.Volume{
5058 Name: "azure-file",
5059 VolumeSource: core.VolumeSource{
5060 AzureFile: &core.AzureFileVolumeSource{
5061 SecretName: "",
5062 ShareName: "share",
5063 ReadOnly: false,
5064 },
5065 },
5066 },
5067 errs: []verr{{
5068 etype: field.ErrorTypeRequired,
5069 field: "azureFile.secretName",
5070 }},
5071 }, {
5072 name: "AzureFile empty share",
5073 vol: core.Volume{
5074 Name: "azure-file",
5075 VolumeSource: core.VolumeSource{
5076 AzureFile: &core.AzureFileVolumeSource{
5077 SecretName: "name",
5078 ShareName: "",
5079 ReadOnly: false,
5080 },
5081 },
5082 },
5083 errs: []verr{{
5084 etype: field.ErrorTypeRequired,
5085 field: "azureFile.shareName",
5086 }},
5087 },
5088
5089 {
5090 name: "valid Quobyte",
5091 vol: core.Volume{
5092 Name: "quobyte",
5093 VolumeSource: core.VolumeSource{
5094 Quobyte: &core.QuobyteVolumeSource{
5095 Registry: "registry:7861",
5096 Volume: "volume",
5097 ReadOnly: false,
5098 User: "root",
5099 Group: "root",
5100 Tenant: "ThisIsSomeTenantUUID",
5101 },
5102 },
5103 },
5104 }, {
5105 name: "empty registry quobyte",
5106 vol: core.Volume{
5107 Name: "quobyte",
5108 VolumeSource: core.VolumeSource{
5109 Quobyte: &core.QuobyteVolumeSource{
5110 Volume: "/test",
5111 Tenant: "ThisIsSomeTenantUUID",
5112 },
5113 },
5114 },
5115 errs: []verr{{
5116 etype: field.ErrorTypeRequired,
5117 field: "quobyte.registry",
5118 }},
5119 }, {
5120 name: "wrong format registry quobyte",
5121 vol: core.Volume{
5122 Name: "quobyte",
5123 VolumeSource: core.VolumeSource{
5124 Quobyte: &core.QuobyteVolumeSource{
5125 Registry: "registry7861",
5126 Volume: "/test",
5127 Tenant: "ThisIsSomeTenantUUID",
5128 },
5129 },
5130 },
5131 errs: []verr{{
5132 etype: field.ErrorTypeInvalid,
5133 field: "quobyte.registry",
5134 }},
5135 }, {
5136 name: "wrong format multiple registries quobyte",
5137 vol: core.Volume{
5138 Name: "quobyte",
5139 VolumeSource: core.VolumeSource{
5140 Quobyte: &core.QuobyteVolumeSource{
5141 Registry: "registry:7861,reg2",
5142 Volume: "/test",
5143 Tenant: "ThisIsSomeTenantUUID",
5144 },
5145 },
5146 },
5147 errs: []verr{{
5148 etype: field.ErrorTypeInvalid,
5149 field: "quobyte.registry",
5150 }},
5151 }, {
5152 name: "empty volume quobyte",
5153 vol: core.Volume{
5154 Name: "quobyte",
5155 VolumeSource: core.VolumeSource{
5156 Quobyte: &core.QuobyteVolumeSource{
5157 Registry: "registry:7861",
5158 Tenant: "ThisIsSomeTenantUUID",
5159 },
5160 },
5161 },
5162 errs: []verr{{
5163 etype: field.ErrorTypeRequired,
5164 field: "quobyte.volume",
5165 }},
5166 }, {
5167 name: "empty tenant quobyte",
5168 vol: core.Volume{
5169 Name: "quobyte",
5170 VolumeSource: core.VolumeSource{
5171 Quobyte: &core.QuobyteVolumeSource{
5172 Registry: "registry:7861",
5173 Volume: "/test",
5174 Tenant: "",
5175 },
5176 },
5177 },
5178 }, {
5179 name: "too long tenant quobyte",
5180 vol: core.Volume{
5181 Name: "quobyte",
5182 VolumeSource: core.VolumeSource{
5183 Quobyte: &core.QuobyteVolumeSource{
5184 Registry: "registry:7861",
5185 Volume: "/test",
5186 Tenant: "this is too long to be a valid uuid so this test has to fail on the maximum length validation of the tenant.",
5187 },
5188 },
5189 },
5190 errs: []verr{{
5191 etype: field.ErrorTypeRequired,
5192 field: "quobyte.tenant",
5193 }},
5194 },
5195
5196 {
5197 name: "valid AzureDisk",
5198 vol: core.Volume{
5199 Name: "azure-disk",
5200 VolumeSource: core.VolumeSource{
5201 AzureDisk: &core.AzureDiskVolumeSource{
5202 DiskName: "foo",
5203 DataDiskURI: "https://blob/vhds/bar.vhd",
5204 },
5205 },
5206 },
5207 }, {
5208 name: "AzureDisk empty disk name",
5209 vol: core.Volume{
5210 Name: "azure-disk",
5211 VolumeSource: core.VolumeSource{
5212 AzureDisk: &core.AzureDiskVolumeSource{
5213 DiskName: "",
5214 DataDiskURI: "https://blob/vhds/bar.vhd",
5215 },
5216 },
5217 },
5218 errs: []verr{{
5219 etype: field.ErrorTypeRequired,
5220 field: "azureDisk.diskName",
5221 }},
5222 }, {
5223 name: "AzureDisk empty disk uri",
5224 vol: core.Volume{
5225 Name: "azure-disk",
5226 VolumeSource: core.VolumeSource{
5227 AzureDisk: &core.AzureDiskVolumeSource{
5228 DiskName: "foo",
5229 DataDiskURI: "",
5230 },
5231 },
5232 },
5233 errs: []verr{{
5234 etype: field.ErrorTypeRequired,
5235 field: "azureDisk.diskURI",
5236 }},
5237 },
5238
5239 {
5240 name: "valid scaleio volume",
5241 vol: core.Volume{
5242 Name: "scaleio-volume",
5243 VolumeSource: core.VolumeSource{
5244 ScaleIO: &core.ScaleIOVolumeSource{
5245 Gateway: "http://abcd/efg",
5246 System: "test-system",
5247 VolumeName: "test-vol-1",
5248 },
5249 },
5250 },
5251 }, {
5252 name: "ScaleIO with empty name",
5253 vol: core.Volume{
5254 Name: "scaleio-volume",
5255 VolumeSource: core.VolumeSource{
5256 ScaleIO: &core.ScaleIOVolumeSource{
5257 Gateway: "http://abcd/efg",
5258 System: "test-system",
5259 VolumeName: "",
5260 },
5261 },
5262 },
5263 errs: []verr{{
5264 etype: field.ErrorTypeRequired,
5265 field: "scaleIO.volumeName",
5266 }},
5267 }, {
5268 name: "ScaleIO with empty gateway",
5269 vol: core.Volume{
5270 Name: "scaleio-volume",
5271 VolumeSource: core.VolumeSource{
5272 ScaleIO: &core.ScaleIOVolumeSource{
5273 Gateway: "",
5274 System: "test-system",
5275 VolumeName: "test-vol-1",
5276 },
5277 },
5278 },
5279 errs: []verr{{
5280 etype: field.ErrorTypeRequired,
5281 field: "scaleIO.gateway",
5282 }},
5283 }, {
5284 name: "ScaleIO with empty system",
5285 vol: core.Volume{
5286 Name: "scaleio-volume",
5287 VolumeSource: core.VolumeSource{
5288 ScaleIO: &core.ScaleIOVolumeSource{
5289 Gateway: "http://agc/efg/gateway",
5290 System: "",
5291 VolumeName: "test-vol-1",
5292 },
5293 },
5294 },
5295 errs: []verr{{
5296 etype: field.ErrorTypeRequired,
5297 field: "scaleIO.system",
5298 }},
5299 },
5300
5301 {
5302 name: "ProjectedVolumeSource more than one projection in a source",
5303 vol: core.Volume{
5304 Name: "projected-volume",
5305 VolumeSource: core.VolumeSource{
5306 Projected: &core.ProjectedVolumeSource{
5307 Sources: []core.VolumeProjection{{
5308 Secret: &core.SecretProjection{
5309 LocalObjectReference: core.LocalObjectReference{
5310 Name: "foo",
5311 },
5312 },
5313 }, {
5314 Secret: &core.SecretProjection{
5315 LocalObjectReference: core.LocalObjectReference{
5316 Name: "foo",
5317 },
5318 },
5319 DownwardAPI: &core.DownwardAPIProjection{},
5320 }},
5321 },
5322 },
5323 },
5324 errs: []verr{{
5325 etype: field.ErrorTypeForbidden,
5326 field: "projected.sources[1]",
5327 }},
5328 }, {
5329 name: "ProjectedVolumeSource more than one projection in a source",
5330 vol: core.Volume{
5331 Name: "projected-volume",
5332 VolumeSource: core.VolumeSource{
5333 Projected: &core.ProjectedVolumeSource{
5334 Sources: []core.VolumeProjection{{
5335 Secret: &core.SecretProjection{},
5336 }, {
5337 Secret: &core.SecretProjection{},
5338 DownwardAPI: &core.DownwardAPIProjection{},
5339 }},
5340 },
5341 },
5342 },
5343 errs: []verr{{
5344 etype: field.ErrorTypeRequired,
5345 field: "projected.sources[0].secret.name",
5346 }, {
5347 etype: field.ErrorTypeRequired,
5348 field: "projected.sources[1].secret.name",
5349 }, {
5350 etype: field.ErrorTypeForbidden,
5351 field: "projected.sources[1]",
5352 }},
5353 },
5354 }
5355
5356 for _, tc := range testCases {
5357 t.Run(tc.name, func(t *testing.T) {
5358 names, errs := ValidateVolumes([]core.Volume{tc.vol}, nil, field.NewPath("field"), tc.opts)
5359 if len(errs) != len(tc.errs) {
5360 t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs)
5361 }
5362 if len(errs) == 0 && (len(names) > 1 || !IsMatchedVolume(tc.vol.Name, names)) {
5363 t.Errorf("wrong names result: %v", names)
5364 }
5365 for i, err := range errs {
5366 expErr := tc.errs[i]
5367 if err.Type != expErr.etype {
5368 t.Errorf("unexpected error type:\n\twant: %q\n\t got: %q", expErr.etype, err.Type)
5369 }
5370 if !strings.HasSuffix(err.Field, "."+expErr.field) {
5371 t.Errorf("unexpected error field:\n\twant: %q\n\t got: %q", expErr.field, err.Field)
5372 }
5373 if !strings.Contains(err.Detail, expErr.detail) {
5374 t.Errorf("unexpected error detail:\n\twant: %q\n\t got: %q", expErr.detail, err.Detail)
5375 }
5376 }
5377 })
5378 }
5379
5380 dupsCase := []core.Volume{
5381 {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
5382 {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
5383 }
5384 _, errs := ValidateVolumes(dupsCase, nil, field.NewPath("field"), PodValidationOptions{})
5385 if len(errs) == 0 {
5386 t.Errorf("expected error")
5387 } else if len(errs) != 1 {
5388 t.Errorf("expected 1 error, got %d: %v", len(errs), errs)
5389 } else if errs[0].Type != field.ErrorTypeDuplicate {
5390 t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
5391 }
5392
5393
5394 hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}}
5395
5396
5397 if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working", nil, PodValidationOptions{}); len(errs) != 0 {
5398 t.Errorf("Unexpected error when HugePages feature is enabled.")
5399 }
5400
5401 }
5402
5403 func TestValidateReadOnlyPersistentDisks(t *testing.T) {
5404 cases := []struct {
5405 name string
5406 volumes []core.Volume
5407 oldVolume []core.Volume
5408 gateValue bool
5409 expectError bool
5410 }{{
5411 name: "gate on, read-only disk, nil old",
5412 gateValue: true,
5413 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5414 oldVolume: []core.Volume(nil),
5415 expectError: false,
5416 }, {
5417 name: "gate off, read-only disk, nil old",
5418 gateValue: false,
5419 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5420 oldVolume: []core.Volume(nil),
5421 expectError: false,
5422 }, {
5423 name: "gate on, read-write, nil old",
5424 gateValue: true,
5425 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5426 oldVolume: []core.Volume(nil),
5427 expectError: false,
5428 }, {
5429 name: "gate off, read-write, nil old",
5430 gateValue: false,
5431 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5432 oldVolume: []core.Volume(nil),
5433 expectError: true,
5434 }, {
5435 name: "gate on, new read-only and old read-write",
5436 gateValue: true,
5437 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5438 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5439 expectError: false,
5440 }, {
5441 name: "gate off, new read-only and old read-write",
5442 gateValue: false,
5443 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5444 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5445 expectError: false,
5446 }, {
5447 name: "gate on, new read-write and old read-write",
5448 gateValue: true,
5449 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5450 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5451 expectError: false,
5452 }, {
5453 name: "gate off, new read-write and old read-write",
5454 gateValue: false,
5455 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5456 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5457 expectError: false,
5458 }, {
5459 name: "gate on, new read-only and old read-only",
5460 gateValue: true,
5461 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5462 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5463 expectError: false,
5464 }, {
5465 name: "gate off, new read-only and old read-only",
5466 gateValue: false,
5467 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5468 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5469 expectError: false,
5470 }, {
5471 name: "gate on, new read-write and old read-only",
5472 gateValue: true,
5473 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5474 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5475 expectError: false,
5476 }, {
5477 name: "gate off, new read-write and old read-only",
5478 gateValue: false,
5479 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
5480 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
5481 expectError: true,
5482 },
5483 }
5484 for _, testCase := range cases {
5485 t.Run(testCase.name, func(t *testing.T) {
5486 fidPath := field.NewPath("testField")
5487 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, testCase.gateValue)()
5488 errs := ValidateReadOnlyPersistentDisks(testCase.volumes, testCase.oldVolume, fidPath)
5489 if !testCase.expectError && len(errs) != 0 {
5490 t.Errorf("expected success, got:%v", errs)
5491 }
5492 })
5493 }
5494 }
5495
5496 func TestHugePagesIsolation(t *testing.T) {
5497 testCases := map[string]struct {
5498 pod *core.Pod
5499 expectError bool
5500 }{
5501 "Valid: request hugepages-2Mi": {
5502 pod: &core.Pod{
5503 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
5504 Spec: core.PodSpec{
5505 Containers: []core.Container{{
5506 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
5507 Resources: core.ResourceRequirements{
5508 Requests: core.ResourceList{
5509 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5510 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5511 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5512 },
5513 Limits: core.ResourceList{
5514 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5515 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5516 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5517 },
5518 },
5519 }},
5520 RestartPolicy: core.RestartPolicyAlways,
5521 DNSPolicy: core.DNSClusterFirst,
5522 },
5523 },
5524 },
5525 "Valid: request more than one hugepages size": {
5526 pod: &core.Pod{
5527 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
5528 Spec: core.PodSpec{
5529 Containers: []core.Container{{
5530 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
5531 Resources: core.ResourceRequirements{
5532 Requests: core.ResourceList{
5533 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5534 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5535 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5536 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
5537 },
5538 Limits: core.ResourceList{
5539 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5540 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5541 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5542 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
5543 },
5544 },
5545 }},
5546 RestartPolicy: core.RestartPolicyAlways,
5547 DNSPolicy: core.DNSClusterFirst,
5548 },
5549 },
5550 expectError: false,
5551 },
5552 "Valid: request hugepages-1Gi, limit hugepages-2Mi and hugepages-1Gi": {
5553 pod: &core.Pod{
5554 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
5555 Spec: core.PodSpec{
5556 Containers: []core.Container{{
5557 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
5558 Resources: core.ResourceRequirements{
5559 Requests: core.ResourceList{
5560 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5561 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5562 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5563 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
5564 },
5565 Limits: core.ResourceList{
5566 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5567 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5568 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5569 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
5570 },
5571 },
5572 }},
5573 RestartPolicy: core.RestartPolicyAlways,
5574 DNSPolicy: core.DNSClusterFirst,
5575 },
5576 },
5577 },
5578 "Invalid: not requesting cpu and memory": {
5579 pod: &core.Pod{
5580 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
5581 Spec: core.PodSpec{
5582 Containers: []core.Container{{
5583 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
5584 Resources: core.ResourceRequirements{
5585 Requests: core.ResourceList{
5586 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5587 },
5588 Limits: core.ResourceList{
5589 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5590 },
5591 },
5592 }},
5593 RestartPolicy: core.RestartPolicyAlways,
5594 DNSPolicy: core.DNSClusterFirst,
5595 },
5596 },
5597 expectError: true,
5598 },
5599 "Invalid: request 1Gi hugepages-2Mi but limit 2Gi": {
5600 pod: &core.Pod{
5601 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
5602 Spec: core.PodSpec{
5603 Containers: []core.Container{{
5604 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
5605 Resources: core.ResourceRequirements{
5606 Requests: core.ResourceList{
5607 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5608 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5609 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
5610 },
5611 Limits: core.ResourceList{
5612 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
5613 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
5614 core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"),
5615 },
5616 },
5617 }},
5618 RestartPolicy: core.RestartPolicyAlways,
5619 DNSPolicy: core.DNSClusterFirst,
5620 },
5621 },
5622 expectError: true,
5623 },
5624 }
5625 for tcName, tc := range testCases {
5626 t.Run(tcName, func(t *testing.T) {
5627 errs := ValidatePodCreate(tc.pod, PodValidationOptions{})
5628 if tc.expectError && len(errs) == 0 {
5629 t.Errorf("Unexpected success")
5630 }
5631 if !tc.expectError && len(errs) != 0 {
5632 t.Errorf("Unexpected error(s): %v", errs)
5633 }
5634 })
5635 }
5636 }
5637
5638 func TestPVCVolumeMode(t *testing.T) {
5639 block := core.PersistentVolumeBlock
5640 file := core.PersistentVolumeFilesystem
5641 fake := core.PersistentVolumeMode("fake")
5642 empty := core.PersistentVolumeMode("")
5643
5644
5645 successCasesPVC := map[string]*core.PersistentVolumeClaim{
5646 "valid block value": createTestVolModePVC(&block),
5647 "valid filesystem value": createTestVolModePVC(&file),
5648 "valid nil value": createTestVolModePVC(nil),
5649 }
5650 for k, v := range successCasesPVC {
5651 opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
5652 if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) != 0 {
5653 t.Errorf("expected success for %s", k)
5654 }
5655 }
5656
5657
5658 errorCasesPVC := map[string]*core.PersistentVolumeClaim{
5659 "invalid value": createTestVolModePVC(&fake),
5660 "empty value": createTestVolModePVC(&empty),
5661 }
5662 for k, v := range errorCasesPVC {
5663 opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
5664 if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) == 0 {
5665 t.Errorf("expected failure for %s", k)
5666 }
5667 }
5668 }
5669
5670 func TestPVVolumeMode(t *testing.T) {
5671 block := core.PersistentVolumeBlock
5672 file := core.PersistentVolumeFilesystem
5673 fake := core.PersistentVolumeMode("fake")
5674 empty := core.PersistentVolumeMode("")
5675
5676
5677 successCasesPV := map[string]*core.PersistentVolume{
5678 "valid block value": createTestVolModePV(&block),
5679 "valid filesystem value": createTestVolModePV(&file),
5680 "valid nil value": createTestVolModePV(nil),
5681 }
5682 for k, v := range successCasesPV {
5683 opts := ValidationOptionsForPersistentVolume(v, nil)
5684 if errs := ValidatePersistentVolume(v, opts); len(errs) != 0 {
5685 t.Errorf("expected success for %s", k)
5686 }
5687 }
5688
5689
5690 errorCasesPV := map[string]*core.PersistentVolume{
5691 "invalid value": createTestVolModePV(&fake),
5692 "empty value": createTestVolModePV(&empty),
5693 }
5694 for k, v := range errorCasesPV {
5695 opts := ValidationOptionsForPersistentVolume(v, nil)
5696 if errs := ValidatePersistentVolume(v, opts); len(errs) == 0 {
5697 t.Errorf("expected failure for %s", k)
5698 }
5699 }
5700 }
5701
5702 func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim {
5703 validName := "valid-storage-class"
5704
5705 pvc := core.PersistentVolumeClaim{
5706 ObjectMeta: metav1.ObjectMeta{
5707 Name: "foo",
5708 Namespace: "default",
5709 },
5710 Spec: core.PersistentVolumeClaimSpec{
5711 Resources: core.VolumeResourceRequirements{
5712 Requests: core.ResourceList{
5713 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
5714 },
5715 },
5716 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
5717 StorageClassName: &validName,
5718 VolumeMode: vmode,
5719 },
5720 }
5721 return &pvc
5722 }
5723
5724 func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume {
5725
5726
5727 pv := core.PersistentVolume{
5728 ObjectMeta: metav1.ObjectMeta{
5729 Name: "foo",
5730 Namespace: "",
5731 },
5732 Spec: core.PersistentVolumeSpec{
5733 Capacity: core.ResourceList{
5734 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
5735 },
5736 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
5737 PersistentVolumeSource: core.PersistentVolumeSource{
5738 HostPath: &core.HostPathVolumeSource{
5739 Path: "/foo",
5740 Type: newHostPathType(string(core.HostPathDirectory)),
5741 },
5742 },
5743 StorageClassName: "test-storage-class",
5744 VolumeMode: vmode,
5745 },
5746 }
5747 return &pv
5748 }
5749
5750 func createTestPV() *core.PersistentVolume {
5751
5752
5753 pv := core.PersistentVolume{
5754 ObjectMeta: metav1.ObjectMeta{
5755 Name: "foo",
5756 Namespace: "",
5757 },
5758 Spec: core.PersistentVolumeSpec{
5759 Capacity: core.ResourceList{
5760 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
5761 },
5762 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
5763 PersistentVolumeSource: core.PersistentVolumeSource{
5764 HostPath: &core.HostPathVolumeSource{
5765 Path: "/foo",
5766 Type: newHostPathType(string(core.HostPathDirectory)),
5767 },
5768 },
5769 StorageClassName: "test-storage-class",
5770 },
5771 }
5772 return &pv
5773 }
5774
5775 func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
5776
5777 testCases := []core.VolumeSource{
5778 {EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}},
5779 }
5780
5781 for _, tc := range testCases {
5782 if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil, PodValidationOptions{}); len(errs) != 0 {
5783 t.Errorf("expected success: %v", errs)
5784 }
5785 }
5786
5787 containerLimitCase := core.ResourceRequirements{
5788 Limits: core.ResourceList{
5789 core.ResourceEphemeralStorage: *resource.NewMilliQuantity(
5790 int64(40000),
5791 resource.BinarySI),
5792 },
5793 }
5794 if errs := ValidateResourceRequirements(&containerLimitCase, nil, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 {
5795 t.Errorf("expected success: %v", errs)
5796 }
5797 }
5798
5799 func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) {
5800 spec := core.ResourceQuotaSpec{
5801 Hard: core.ResourceList{
5802 core.ResourceCPU: resource.MustParse("100"),
5803 core.ResourceMemory: resource.MustParse("10000"),
5804 core.ResourceRequestsCPU: resource.MustParse("100"),
5805 core.ResourceRequestsMemory: resource.MustParse("10000"),
5806 core.ResourceLimitsCPU: resource.MustParse("100"),
5807 core.ResourceLimitsMemory: resource.MustParse("10000"),
5808 core.ResourcePods: resource.MustParse("10"),
5809 core.ResourceServices: resource.MustParse("0"),
5810 core.ResourceReplicationControllers: resource.MustParse("10"),
5811 core.ResourceQuotas: resource.MustParse("10"),
5812 core.ResourceConfigMaps: resource.MustParse("10"),
5813 core.ResourceSecrets: resource.MustParse("10"),
5814 core.ResourceEphemeralStorage: resource.MustParse("10000"),
5815 core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"),
5816 core.ResourceLimitsEphemeralStorage: resource.MustParse("10000"),
5817 },
5818 }
5819 resourceQuota := &core.ResourceQuota{
5820 ObjectMeta: metav1.ObjectMeta{
5821 Name: "abc",
5822 Namespace: "foo",
5823 },
5824 Spec: spec,
5825 }
5826
5827 if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
5828 t.Errorf("expected success: %v", errs)
5829 }
5830 }
5831
5832 func TestValidatePorts(t *testing.T) {
5833 successCase := []core.ContainerPort{
5834 {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
5835 {Name: "easy", ContainerPort: 82, Protocol: "TCP"},
5836 {Name: "as", ContainerPort: 83, Protocol: "UDP"},
5837 {Name: "do-re-me", ContainerPort: 84, Protocol: "SCTP"},
5838 {ContainerPort: 85, Protocol: "TCP"},
5839 }
5840 if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 {
5841 t.Errorf("expected success: %v", errs)
5842 }
5843
5844 nonCanonicalCase := []core.ContainerPort{
5845 {ContainerPort: 80, Protocol: "TCP"},
5846 }
5847 if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 {
5848 t.Errorf("expected success: %v", errs)
5849 }
5850
5851 errorCases := map[string]struct {
5852 P []core.ContainerPort
5853 T field.ErrorType
5854 F string
5855 D string
5856 }{
5857 "name > 15 characters": {
5858 []core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}},
5859 field.ErrorTypeInvalid,
5860 "name", "15",
5861 },
5862 "name contains invalid characters": {
5863 []core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}},
5864 field.ErrorTypeInvalid,
5865 "name", "alpha-numeric",
5866 },
5867 "name is a number": {
5868 []core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}},
5869 field.ErrorTypeInvalid,
5870 "name", "at least one letter",
5871 },
5872 "name not unique": {
5873 []core.ContainerPort{
5874 {Name: "abc", ContainerPort: 80, Protocol: "TCP"},
5875 {Name: "abc", ContainerPort: 81, Protocol: "TCP"},
5876 },
5877 field.ErrorTypeDuplicate,
5878 "[1].name", "",
5879 },
5880 "zero container port": {
5881 []core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}},
5882 field.ErrorTypeRequired,
5883 "containerPort", "",
5884 },
5885 "invalid container port": {
5886 []core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}},
5887 field.ErrorTypeInvalid,
5888 "containerPort", "between",
5889 },
5890 "invalid host port": {
5891 []core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}},
5892 field.ErrorTypeInvalid,
5893 "hostPort", "between",
5894 },
5895 "invalid protocol case": {
5896 []core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}},
5897 field.ErrorTypeNotSupported,
5898 "protocol", `supported values: "SCTP", "TCP", "UDP"`,
5899 },
5900 "invalid protocol": {
5901 []core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}},
5902 field.ErrorTypeNotSupported,
5903 "protocol", `supported values: "SCTP", "TCP", "UDP"`,
5904 },
5905 "protocol required": {
5906 []core.ContainerPort{{Name: "abc", ContainerPort: 80}},
5907 field.ErrorTypeRequired,
5908 "protocol", "",
5909 },
5910 }
5911 for k, v := range errorCases {
5912 errs := validateContainerPorts(v.P, field.NewPath("field"))
5913 if len(errs) == 0 {
5914 t.Errorf("expected failure for %s", k)
5915 }
5916 for i := range errs {
5917 if errs[i].Type != v.T {
5918 t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
5919 }
5920 if !strings.Contains(errs[i].Field, v.F) {
5921 t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
5922 }
5923 if !strings.Contains(errs[i].Detail, v.D) {
5924 t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
5925 }
5926 }
5927 }
5928 }
5929
5930 func TestLocalStorageEnvWithFeatureGate(t *testing.T) {
5931 testCases := []core.EnvVar{{
5932 Name: "ephemeral-storage-limits",
5933 ValueFrom: &core.EnvVarSource{
5934 ResourceFieldRef: &core.ResourceFieldSelector{
5935 ContainerName: "test-container",
5936 Resource: "limits.ephemeral-storage",
5937 },
5938 },
5939 }, {
5940 Name: "ephemeral-storage-requests",
5941 ValueFrom: &core.EnvVarSource{
5942 ResourceFieldRef: &core.ResourceFieldSelector{
5943 ContainerName: "test-container",
5944 Resource: "requests.ephemeral-storage",
5945 },
5946 },
5947 },
5948 }
5949 for _, testCase := range testCases {
5950 if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
5951 t.Errorf("expected success, got: %v", errs)
5952 }
5953 }
5954 }
5955
5956 func TestHugePagesEnv(t *testing.T) {
5957 testCases := []core.EnvVar{{
5958 Name: "hugepages-limits",
5959 ValueFrom: &core.EnvVarSource{
5960 ResourceFieldRef: &core.ResourceFieldSelector{
5961 ContainerName: "test-container",
5962 Resource: "limits.hugepages-2Mi",
5963 },
5964 },
5965 }, {
5966 Name: "hugepages-requests",
5967 ValueFrom: &core.EnvVarSource{
5968 ResourceFieldRef: &core.ResourceFieldSelector{
5969 ContainerName: "test-container",
5970 Resource: "requests.hugepages-2Mi",
5971 },
5972 },
5973 },
5974 }
5975
5976 for _, testCase := range testCases {
5977 t.Run(testCase.Name, func(t *testing.T) {
5978 opts := PodValidationOptions{}
5979 if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), opts); len(errs) != 0 {
5980 t.Errorf("expected success, got: %v", errs)
5981 }
5982 })
5983 }
5984 }
5985
5986 func TestRelaxedValidateEnv(t *testing.T) {
5987 successCase := []core.EnvVar{
5988 {Name: "!\"#$%&'()", Value: "value"},
5989 {Name: "* +,-./0123456789", Value: "value"},
5990 {Name: ":;<>?@", Value: "value"},
5991 {Name: "ABCDEFG", Value: "value"},
5992 {Name: "abcdefghijklmn", Value: "value"},
5993 {Name: "[\\]^_`{}|~", Value: "value"},
5994 {
5995 Name: "!\"#$%&'()",
5996 ValueFrom: &core.EnvVarSource{
5997 FieldRef: &core.ObjectFieldSelector{
5998 APIVersion: "v1",
5999 FieldPath: "metadata.annotations['key']",
6000 },
6001 },
6002 }, {
6003 Name: "!\"#$%&'()",
6004 ValueFrom: &core.EnvVarSource{
6005 FieldRef: &core.ObjectFieldSelector{
6006 APIVersion: "v1",
6007 FieldPath: "metadata.labels['key']",
6008 },
6009 },
6010 }, {
6011 Name: "* +,-./0123456789",
6012 ValueFrom: &core.EnvVarSource{
6013 FieldRef: &core.ObjectFieldSelector{
6014 APIVersion: "v1",
6015 FieldPath: "metadata.name",
6016 },
6017 },
6018 }, {
6019 Name: "* +,-./0123456789",
6020 ValueFrom: &core.EnvVarSource{
6021 FieldRef: &core.ObjectFieldSelector{
6022 APIVersion: "v1",
6023 FieldPath: "metadata.namespace",
6024 },
6025 },
6026 }, {
6027 Name: "* +,-./0123456789",
6028 ValueFrom: &core.EnvVarSource{
6029 FieldRef: &core.ObjectFieldSelector{
6030 APIVersion: "v1",
6031 FieldPath: "metadata.uid",
6032 },
6033 },
6034 }, {
6035 Name: ":;<>?@",
6036 ValueFrom: &core.EnvVarSource{
6037 FieldRef: &core.ObjectFieldSelector{
6038 APIVersion: "v1",
6039 FieldPath: "spec.nodeName",
6040 },
6041 },
6042 }, {
6043 Name: ":;<>?@",
6044 ValueFrom: &core.EnvVarSource{
6045 FieldRef: &core.ObjectFieldSelector{
6046 APIVersion: "v1",
6047 FieldPath: "spec.serviceAccountName",
6048 },
6049 },
6050 }, {
6051 Name: ":;<>?@",
6052 ValueFrom: &core.EnvVarSource{
6053 FieldRef: &core.ObjectFieldSelector{
6054 APIVersion: "v1",
6055 FieldPath: "status.hostIP",
6056 },
6057 },
6058 }, {
6059 Name: ":;<>?@",
6060 ValueFrom: &core.EnvVarSource{
6061 FieldRef: &core.ObjectFieldSelector{
6062 APIVersion: "v1",
6063 FieldPath: "status.podIP",
6064 },
6065 },
6066 }, {
6067 Name: "abcdefghijklmn",
6068 ValueFrom: &core.EnvVarSource{
6069 FieldRef: &core.ObjectFieldSelector{
6070 APIVersion: "v1",
6071 FieldPath: "status.podIPs",
6072 },
6073 },
6074 },
6075 {
6076 Name: "abcdefghijklmn",
6077 ValueFrom: &core.EnvVarSource{
6078 SecretKeyRef: &core.SecretKeySelector{
6079 LocalObjectReference: core.LocalObjectReference{
6080 Name: "some-secret",
6081 },
6082 Key: "secret-key",
6083 },
6084 },
6085 }, {
6086 Name: "!\"#$%&'()",
6087 ValueFrom: &core.EnvVarSource{
6088 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6089 LocalObjectReference: core.LocalObjectReference{
6090 Name: "some-config-map",
6091 },
6092 Key: "some-key",
6093 },
6094 },
6095 },
6096 }
6097 if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
6098 t.Errorf("expected success, got: %v", errs)
6099 }
6100
6101 errorCases := []struct {
6102 name string
6103 envs []core.EnvVar
6104 expectedError string
6105 }{{
6106 name: "illegal character",
6107 envs: []core.EnvVar{{Name: "=abc"}},
6108 expectedError: `[0].name: Invalid value: "=abc": ` + relaxedEnvVarNameFmtErrMsg,
6109 }, {
6110 name: "zero-length name",
6111 envs: []core.EnvVar{{Name: ""}},
6112 expectedError: "[0].name: Required value",
6113 }, {
6114 name: "value and valueFrom specified",
6115 envs: []core.EnvVar{{
6116 Name: "abc",
6117 Value: "foo",
6118 ValueFrom: &core.EnvVarSource{
6119 FieldRef: &core.ObjectFieldSelector{
6120 APIVersion: "v1",
6121 FieldPath: "metadata.name",
6122 },
6123 },
6124 }},
6125 expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
6126 }, {
6127 name: "valueFrom without a source",
6128 envs: []core.EnvVar{{
6129 Name: "abc",
6130 ValueFrom: &core.EnvVarSource{},
6131 }},
6132 expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
6133 }, {
6134 name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
6135 envs: []core.EnvVar{{
6136 Name: "abc",
6137 ValueFrom: &core.EnvVarSource{
6138 FieldRef: &core.ObjectFieldSelector{
6139 APIVersion: "v1",
6140 FieldPath: "metadata.name",
6141 },
6142 SecretKeyRef: &core.SecretKeySelector{
6143 LocalObjectReference: core.LocalObjectReference{
6144 Name: "a-secret",
6145 },
6146 Key: "a-key",
6147 },
6148 },
6149 }},
6150 expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
6151 }, {
6152 name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
6153 envs: []core.EnvVar{{
6154 Name: "some_var_name",
6155 ValueFrom: &core.EnvVarSource{
6156 FieldRef: &core.ObjectFieldSelector{
6157 APIVersion: "v1",
6158 FieldPath: "metadata.name",
6159 },
6160 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6161 LocalObjectReference: core.LocalObjectReference{
6162 Name: "some-config-map",
6163 },
6164 Key: "some-key",
6165 },
6166 },
6167 }},
6168 expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
6169 }, {
6170 name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
6171 envs: []core.EnvVar{{
6172 Name: "abc",
6173 ValueFrom: &core.EnvVarSource{
6174 FieldRef: &core.ObjectFieldSelector{
6175 APIVersion: "v1",
6176 FieldPath: "metadata.name",
6177 },
6178 SecretKeyRef: &core.SecretKeySelector{
6179 LocalObjectReference: core.LocalObjectReference{
6180 Name: "a-secret",
6181 },
6182 Key: "a-key",
6183 },
6184 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6185 LocalObjectReference: core.LocalObjectReference{
6186 Name: "some-config-map",
6187 },
6188 Key: "some-key",
6189 },
6190 },
6191 }},
6192 expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
6193 }, {
6194 name: "valueFrom.secretKeyRef.name invalid",
6195 envs: []core.EnvVar{{
6196 Name: "abc",
6197 ValueFrom: &core.EnvVarSource{
6198 SecretKeyRef: &core.SecretKeySelector{
6199 LocalObjectReference: core.LocalObjectReference{
6200 Name: "$%^&*#",
6201 },
6202 Key: "a-key",
6203 },
6204 },
6205 }},
6206 }, {
6207 name: "valueFrom.configMapKeyRef.name invalid",
6208 envs: []core.EnvVar{{
6209 Name: "abc",
6210 ValueFrom: &core.EnvVarSource{
6211 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6212 LocalObjectReference: core.LocalObjectReference{
6213 Name: "$%^&*#",
6214 },
6215 Key: "some-key",
6216 },
6217 },
6218 }},
6219 }, {
6220 name: "missing FieldPath on ObjectFieldSelector",
6221 envs: []core.EnvVar{{
6222 Name: "abc",
6223 ValueFrom: &core.EnvVarSource{
6224 FieldRef: &core.ObjectFieldSelector{
6225 APIVersion: "v1",
6226 },
6227 },
6228 }},
6229 expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
6230 }, {
6231 name: "missing APIVersion on ObjectFieldSelector",
6232 envs: []core.EnvVar{{
6233 Name: "abc",
6234 ValueFrom: &core.EnvVarSource{
6235 FieldRef: &core.ObjectFieldSelector{
6236 FieldPath: "metadata.name",
6237 },
6238 },
6239 }},
6240 expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
6241 }, {
6242 name: "invalid fieldPath",
6243 envs: []core.EnvVar{{
6244 Name: "abc",
6245 ValueFrom: &core.EnvVarSource{
6246 FieldRef: &core.ObjectFieldSelector{
6247 FieldPath: "metadata.whoops",
6248 APIVersion: "v1",
6249 },
6250 },
6251 }},
6252 expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
6253 }, {
6254 name: "metadata.name with subscript",
6255 envs: []core.EnvVar{{
6256 Name: "labels",
6257 ValueFrom: &core.EnvVarSource{
6258 FieldRef: &core.ObjectFieldSelector{
6259 FieldPath: "metadata.name['key']",
6260 APIVersion: "v1",
6261 },
6262 },
6263 }},
6264 expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
6265 }, {
6266 name: "metadata.labels without subscript",
6267 envs: []core.EnvVar{{
6268 Name: "labels",
6269 ValueFrom: &core.EnvVarSource{
6270 FieldRef: &core.ObjectFieldSelector{
6271 FieldPath: "metadata.labels",
6272 APIVersion: "v1",
6273 },
6274 },
6275 }},
6276 expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
6277 }, {
6278 name: "metadata.annotations without subscript",
6279 envs: []core.EnvVar{{
6280 Name: "abc",
6281 ValueFrom: &core.EnvVarSource{
6282 FieldRef: &core.ObjectFieldSelector{
6283 FieldPath: "metadata.annotations",
6284 APIVersion: "v1",
6285 },
6286 },
6287 }},
6288 expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
6289 }, {
6290 name: "metadata.annotations with invalid key",
6291 envs: []core.EnvVar{{
6292 Name: "abc",
6293 ValueFrom: &core.EnvVarSource{
6294 FieldRef: &core.ObjectFieldSelector{
6295 FieldPath: "metadata.annotations['invalid~key']",
6296 APIVersion: "v1",
6297 },
6298 },
6299 }},
6300 expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
6301 }, {
6302 name: "metadata.labels with invalid key",
6303 envs: []core.EnvVar{{
6304 Name: "abc",
6305 ValueFrom: &core.EnvVarSource{
6306 FieldRef: &core.ObjectFieldSelector{
6307 FieldPath: "metadata.labels['Www.k8s.io/test']",
6308 APIVersion: "v1",
6309 },
6310 },
6311 }},
6312 expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
6313 }, {
6314 name: "unsupported fieldPath",
6315 envs: []core.EnvVar{{
6316 Name: "abc",
6317 ValueFrom: &core.EnvVarSource{
6318 FieldRef: &core.ObjectFieldSelector{
6319 FieldPath: "status.phase",
6320 APIVersion: "v1",
6321 },
6322 },
6323 }},
6324 expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
6325 },
6326 }
6327 for _, tc := range errorCases {
6328 if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) == 0 {
6329 t.Errorf("expected failure for %s", tc.name)
6330 } else {
6331 for i := range errs {
6332 str := errs[i].Error()
6333 if str != "" && !strings.Contains(str, tc.expectedError) {
6334 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
6335 }
6336 }
6337 }
6338 }
6339 }
6340
6341 func TestValidateEnv(t *testing.T) {
6342 successCase := []core.EnvVar{
6343 {Name: "abc", Value: "value"},
6344 {Name: "ABC", Value: "value"},
6345 {Name: "AbC_123", Value: "value"},
6346 {Name: "abc", Value: ""},
6347 {Name: "a.b.c", Value: "value"},
6348 {Name: "a-b-c", Value: "value"}, {
6349 Name: "abc",
6350 ValueFrom: &core.EnvVarSource{
6351 FieldRef: &core.ObjectFieldSelector{
6352 APIVersion: "v1",
6353 FieldPath: "metadata.annotations['key']",
6354 },
6355 },
6356 }, {
6357 Name: "abc",
6358 ValueFrom: &core.EnvVarSource{
6359 FieldRef: &core.ObjectFieldSelector{
6360 APIVersion: "v1",
6361 FieldPath: "metadata.labels['key']",
6362 },
6363 },
6364 }, {
6365 Name: "abc",
6366 ValueFrom: &core.EnvVarSource{
6367 FieldRef: &core.ObjectFieldSelector{
6368 APIVersion: "v1",
6369 FieldPath: "metadata.name",
6370 },
6371 },
6372 }, {
6373 Name: "abc",
6374 ValueFrom: &core.EnvVarSource{
6375 FieldRef: &core.ObjectFieldSelector{
6376 APIVersion: "v1",
6377 FieldPath: "metadata.namespace",
6378 },
6379 },
6380 }, {
6381 Name: "abc",
6382 ValueFrom: &core.EnvVarSource{
6383 FieldRef: &core.ObjectFieldSelector{
6384 APIVersion: "v1",
6385 FieldPath: "metadata.uid",
6386 },
6387 },
6388 }, {
6389 Name: "abc",
6390 ValueFrom: &core.EnvVarSource{
6391 FieldRef: &core.ObjectFieldSelector{
6392 APIVersion: "v1",
6393 FieldPath: "spec.nodeName",
6394 },
6395 },
6396 }, {
6397 Name: "abc",
6398 ValueFrom: &core.EnvVarSource{
6399 FieldRef: &core.ObjectFieldSelector{
6400 APIVersion: "v1",
6401 FieldPath: "spec.serviceAccountName",
6402 },
6403 },
6404 }, {
6405 Name: "abc",
6406 ValueFrom: &core.EnvVarSource{
6407 FieldRef: &core.ObjectFieldSelector{
6408 APIVersion: "v1",
6409 FieldPath: "status.hostIP",
6410 },
6411 },
6412 }, {
6413 Name: "abc",
6414 ValueFrom: &core.EnvVarSource{
6415 FieldRef: &core.ObjectFieldSelector{
6416 APIVersion: "v1",
6417 FieldPath: "status.podIP",
6418 },
6419 },
6420 }, {
6421 Name: "abc",
6422 ValueFrom: &core.EnvVarSource{
6423 FieldRef: &core.ObjectFieldSelector{
6424 APIVersion: "v1",
6425 FieldPath: "status.podIPs",
6426 },
6427 },
6428 }, {
6429 Name: "secret_value",
6430 ValueFrom: &core.EnvVarSource{
6431 SecretKeyRef: &core.SecretKeySelector{
6432 LocalObjectReference: core.LocalObjectReference{
6433 Name: "some-secret",
6434 },
6435 Key: "secret-key",
6436 },
6437 },
6438 }, {
6439 Name: "ENV_VAR_1",
6440 ValueFrom: &core.EnvVarSource{
6441 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6442 LocalObjectReference: core.LocalObjectReference{
6443 Name: "some-config-map",
6444 },
6445 Key: "some-key",
6446 },
6447 },
6448 },
6449 }
6450 if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
6451 t.Errorf("expected success, got: %v", errs)
6452 }
6453
6454 updateSuccessCase := []core.EnvVar{
6455 {Name: "!\"#$%&'()", Value: "value"},
6456 {Name: "* +,-./0123456789", Value: "value"},
6457 {Name: ":;<>?@", Value: "value"},
6458 {Name: "ABCDEFG", Value: "value"},
6459 {Name: "abcdefghijklmn", Value: "value"},
6460 {Name: "[\\]^_`{}|~", Value: "value"},
6461 }
6462
6463 if errs := ValidateEnv(updateSuccessCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
6464 t.Errorf("expected success, got: %v", errs)
6465 }
6466
6467 updateErrorCase := []struct {
6468 name string
6469 envs []core.EnvVar
6470 expectedError string
6471 }{
6472 {
6473 name: "invalid name a",
6474 envs: []core.EnvVar{
6475 {Name: "!\"#$%&'()", Value: "value"},
6476 },
6477 expectedError: `field[0].name: Invalid value: ` + "\"!\\\"#$%&'()\": " + envVarNameErrMsg,
6478 },
6479 {
6480 name: "invalid name b",
6481 envs: []core.EnvVar{
6482 {Name: "* +,-./0123456789", Value: "value"},
6483 },
6484 expectedError: `field[0].name: Invalid value: ` + "\"* +,-./0123456789\": " + envVarNameErrMsg,
6485 },
6486 {
6487 name: "invalid name c",
6488 envs: []core.EnvVar{
6489 {Name: ":;<>?@", Value: "value"},
6490 },
6491 expectedError: `field[0].name: Invalid value: ` + "\":;<>?@\": " + envVarNameErrMsg,
6492 },
6493 {
6494 name: "invalid name d",
6495 envs: []core.EnvVar{
6496 {Name: "[\\]^_{}|~", Value: "value"},
6497 },
6498 expectedError: `field[0].name: Invalid value: ` + "\"[\\\\]^_{}|~\": " + envVarNameErrMsg,
6499 },
6500 }
6501
6502 for _, tc := range updateErrorCase {
6503 if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
6504 t.Errorf("expected failure for %s", tc.name)
6505 } else {
6506 for i := range errs {
6507 str := errs[i].Error()
6508 if str != "" && !strings.Contains(str, tc.expectedError) {
6509 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
6510 }
6511 }
6512 }
6513 }
6514
6515 errorCases := []struct {
6516 name string
6517 envs []core.EnvVar
6518 expectedError string
6519 }{{
6520 name: "zero-length name",
6521 envs: []core.EnvVar{{Name: ""}},
6522 expectedError: "[0].name: Required value",
6523 }, {
6524 name: "value and valueFrom specified",
6525 envs: []core.EnvVar{{
6526 Name: "abc",
6527 Value: "foo",
6528 ValueFrom: &core.EnvVarSource{
6529 FieldRef: &core.ObjectFieldSelector{
6530 APIVersion: "v1",
6531 FieldPath: "metadata.name",
6532 },
6533 },
6534 }},
6535 expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
6536 }, {
6537 name: "valueFrom without a source",
6538 envs: []core.EnvVar{{
6539 Name: "abc",
6540 ValueFrom: &core.EnvVarSource{},
6541 }},
6542 expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
6543 }, {
6544 name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
6545 envs: []core.EnvVar{{
6546 Name: "abc",
6547 ValueFrom: &core.EnvVarSource{
6548 FieldRef: &core.ObjectFieldSelector{
6549 APIVersion: "v1",
6550 FieldPath: "metadata.name",
6551 },
6552 SecretKeyRef: &core.SecretKeySelector{
6553 LocalObjectReference: core.LocalObjectReference{
6554 Name: "a-secret",
6555 },
6556 Key: "a-key",
6557 },
6558 },
6559 }},
6560 expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
6561 }, {
6562 name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
6563 envs: []core.EnvVar{{
6564 Name: "some_var_name",
6565 ValueFrom: &core.EnvVarSource{
6566 FieldRef: &core.ObjectFieldSelector{
6567 APIVersion: "v1",
6568 FieldPath: "metadata.name",
6569 },
6570 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6571 LocalObjectReference: core.LocalObjectReference{
6572 Name: "some-config-map",
6573 },
6574 Key: "some-key",
6575 },
6576 },
6577 }},
6578 expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
6579 }, {
6580 name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
6581 envs: []core.EnvVar{{
6582 Name: "abc",
6583 ValueFrom: &core.EnvVarSource{
6584 FieldRef: &core.ObjectFieldSelector{
6585 APIVersion: "v1",
6586 FieldPath: "metadata.name",
6587 },
6588 SecretKeyRef: &core.SecretKeySelector{
6589 LocalObjectReference: core.LocalObjectReference{
6590 Name: "a-secret",
6591 },
6592 Key: "a-key",
6593 },
6594 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6595 LocalObjectReference: core.LocalObjectReference{
6596 Name: "some-config-map",
6597 },
6598 Key: "some-key",
6599 },
6600 },
6601 }},
6602 expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
6603 }, {
6604 name: "valueFrom.secretKeyRef.name invalid",
6605 envs: []core.EnvVar{{
6606 Name: "abc",
6607 ValueFrom: &core.EnvVarSource{
6608 SecretKeyRef: &core.SecretKeySelector{
6609 LocalObjectReference: core.LocalObjectReference{
6610 Name: "$%^&*#",
6611 },
6612 Key: "a-key",
6613 },
6614 },
6615 }},
6616 }, {
6617 name: "valueFrom.configMapKeyRef.name invalid",
6618 envs: []core.EnvVar{{
6619 Name: "abc",
6620 ValueFrom: &core.EnvVarSource{
6621 ConfigMapKeyRef: &core.ConfigMapKeySelector{
6622 LocalObjectReference: core.LocalObjectReference{
6623 Name: "$%^&*#",
6624 },
6625 Key: "some-key",
6626 },
6627 },
6628 }},
6629 }, {
6630 name: "missing FieldPath on ObjectFieldSelector",
6631 envs: []core.EnvVar{{
6632 Name: "abc",
6633 ValueFrom: &core.EnvVarSource{
6634 FieldRef: &core.ObjectFieldSelector{
6635 APIVersion: "v1",
6636 },
6637 },
6638 }},
6639 expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
6640 }, {
6641 name: "missing APIVersion on ObjectFieldSelector",
6642 envs: []core.EnvVar{{
6643 Name: "abc",
6644 ValueFrom: &core.EnvVarSource{
6645 FieldRef: &core.ObjectFieldSelector{
6646 FieldPath: "metadata.name",
6647 },
6648 },
6649 }},
6650 expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
6651 }, {
6652 name: "invalid fieldPath",
6653 envs: []core.EnvVar{{
6654 Name: "abc",
6655 ValueFrom: &core.EnvVarSource{
6656 FieldRef: &core.ObjectFieldSelector{
6657 FieldPath: "metadata.whoops",
6658 APIVersion: "v1",
6659 },
6660 },
6661 }},
6662 expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
6663 }, {
6664 name: "metadata.name with subscript",
6665 envs: []core.EnvVar{{
6666 Name: "labels",
6667 ValueFrom: &core.EnvVarSource{
6668 FieldRef: &core.ObjectFieldSelector{
6669 FieldPath: "metadata.name['key']",
6670 APIVersion: "v1",
6671 },
6672 },
6673 }},
6674 expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
6675 }, {
6676 name: "metadata.labels without subscript",
6677 envs: []core.EnvVar{{
6678 Name: "labels",
6679 ValueFrom: &core.EnvVarSource{
6680 FieldRef: &core.ObjectFieldSelector{
6681 FieldPath: "metadata.labels",
6682 APIVersion: "v1",
6683 },
6684 },
6685 }},
6686 expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
6687 }, {
6688 name: "metadata.annotations without subscript",
6689 envs: []core.EnvVar{{
6690 Name: "abc",
6691 ValueFrom: &core.EnvVarSource{
6692 FieldRef: &core.ObjectFieldSelector{
6693 FieldPath: "metadata.annotations",
6694 APIVersion: "v1",
6695 },
6696 },
6697 }},
6698 expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
6699 }, {
6700 name: "metadata.annotations with invalid key",
6701 envs: []core.EnvVar{{
6702 Name: "abc",
6703 ValueFrom: &core.EnvVarSource{
6704 FieldRef: &core.ObjectFieldSelector{
6705 FieldPath: "metadata.annotations['invalid~key']",
6706 APIVersion: "v1",
6707 },
6708 },
6709 }},
6710 expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
6711 }, {
6712 name: "metadata.labels with invalid key",
6713 envs: []core.EnvVar{{
6714 Name: "abc",
6715 ValueFrom: &core.EnvVarSource{
6716 FieldRef: &core.ObjectFieldSelector{
6717 FieldPath: "metadata.labels['Www.k8s.io/test']",
6718 APIVersion: "v1",
6719 },
6720 },
6721 }},
6722 expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
6723 }, {
6724 name: "unsupported fieldPath",
6725 envs: []core.EnvVar{{
6726 Name: "abc",
6727 ValueFrom: &core.EnvVarSource{
6728 FieldRef: &core.ObjectFieldSelector{
6729 FieldPath: "status.phase",
6730 APIVersion: "v1",
6731 },
6732 },
6733 }},
6734 expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
6735 },
6736 }
6737 for _, tc := range errorCases {
6738 if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
6739 t.Errorf("expected failure for %s", tc.name)
6740 } else {
6741 for i := range errs {
6742 str := errs[i].Error()
6743 if str != "" && !strings.Contains(str, tc.expectedError) {
6744 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
6745 }
6746 }
6747 }
6748 }
6749 }
6750
6751 func TestValidateEnvFrom(t *testing.T) {
6752 successCase := []core.EnvFromSource{{
6753 ConfigMapRef: &core.ConfigMapEnvSource{
6754 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6755 },
6756 }, {
6757 Prefix: "pre_",
6758 ConfigMapRef: &core.ConfigMapEnvSource{
6759 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6760 },
6761 }, {
6762 Prefix: "a.b",
6763 ConfigMapRef: &core.ConfigMapEnvSource{
6764 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6765 },
6766 }, {
6767 SecretRef: &core.SecretEnvSource{
6768 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6769 },
6770 }, {
6771 Prefix: "pre_",
6772 SecretRef: &core.SecretEnvSource{
6773 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6774 },
6775 }, {
6776 Prefix: "a.b",
6777 SecretRef: &core.SecretEnvSource{
6778 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6779 },
6780 },
6781 }
6782 if errs := ValidateEnvFrom(successCase, nil, PodValidationOptions{}); len(errs) != 0 {
6783 t.Errorf("expected success: %v", errs)
6784 }
6785
6786 updateSuccessCase := []core.EnvFromSource{{
6787 ConfigMapRef: &core.ConfigMapEnvSource{
6788 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6789 },
6790 }, {
6791 Prefix: "* +,-./0123456789",
6792 ConfigMapRef: &core.ConfigMapEnvSource{
6793 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6794 },
6795 }, {
6796 Prefix: ":;<>?@",
6797 ConfigMapRef: &core.ConfigMapEnvSource{
6798 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6799 },
6800 }, {
6801 SecretRef: &core.SecretEnvSource{
6802 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6803 },
6804 }, {
6805 Prefix: "abcdefghijklmn",
6806 SecretRef: &core.SecretEnvSource{
6807 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6808 },
6809 }, {
6810 Prefix: "[\\]^_`{}|~",
6811 SecretRef: &core.SecretEnvSource{
6812 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6813 },
6814 }}
6815
6816 if errs := ValidateEnvFrom(updateSuccessCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
6817 t.Errorf("expected success, got: %v", errs)
6818 }
6819
6820 updateErrorCase := []struct {
6821 name string
6822 envs []core.EnvFromSource
6823 expectedError string
6824 }{
6825 {
6826 name: "invalid name a",
6827 envs: []core.EnvFromSource{
6828 {
6829 Prefix: "!\"#$%&'()",
6830 SecretRef: &core.SecretEnvSource{
6831 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6832 },
6833 },
6834 },
6835 expectedError: `field[0].prefix: Invalid value: ` + "\"!\\\"#$%&'()\": " + envVarNameErrMsg,
6836 },
6837 {
6838 name: "invalid name b",
6839 envs: []core.EnvFromSource{
6840 {
6841 Prefix: "* +,-./0123456789",
6842 SecretRef: &core.SecretEnvSource{
6843 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6844 },
6845 },
6846 },
6847 expectedError: `field[0].prefix: Invalid value: ` + "\"* +,-./0123456789\": " + envVarNameErrMsg,
6848 },
6849 {
6850 name: "invalid name c",
6851 envs: []core.EnvFromSource{
6852 {
6853 Prefix: ":;<>?@",
6854 SecretRef: &core.SecretEnvSource{
6855 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6856 },
6857 },
6858 },
6859 expectedError: `field[0].prefix: Invalid value: ` + "\":;<>?@\": " + envVarNameErrMsg,
6860 },
6861 {
6862 name: "invalid name d",
6863 envs: []core.EnvFromSource{
6864 {
6865 Prefix: "[\\]^_{}|~",
6866 SecretRef: &core.SecretEnvSource{
6867 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6868 },
6869 },
6870 },
6871 expectedError: `field[0].prefix: Invalid value: ` + "\"[\\\\]^_{}|~\": " + envVarNameErrMsg,
6872 },
6873 }
6874
6875 for _, tc := range updateErrorCase {
6876 if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
6877 t.Errorf("expected failure for %s", tc.name)
6878 } else {
6879 for i := range errs {
6880 str := errs[i].Error()
6881 if str != "" && !strings.Contains(str, tc.expectedError) {
6882 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
6883 }
6884 }
6885 }
6886 }
6887
6888 errorCases := []struct {
6889 name string
6890 envs []core.EnvFromSource
6891 expectedError string
6892 }{{
6893 name: "zero-length name",
6894 envs: []core.EnvFromSource{{
6895 ConfigMapRef: &core.ConfigMapEnvSource{
6896 LocalObjectReference: core.LocalObjectReference{Name: ""}},
6897 }},
6898 expectedError: "field[0].configMapRef.name: Required value",
6899 }, {
6900 name: "invalid name",
6901 envs: []core.EnvFromSource{{
6902 ConfigMapRef: &core.ConfigMapEnvSource{
6903 LocalObjectReference: core.LocalObjectReference{Name: "$"}},
6904 }},
6905 expectedError: "field[0].configMapRef.name: Invalid value",
6906 }, {
6907 name: "zero-length name",
6908 envs: []core.EnvFromSource{{
6909 SecretRef: &core.SecretEnvSource{
6910 LocalObjectReference: core.LocalObjectReference{Name: ""}},
6911 }},
6912 expectedError: "field[0].secretRef.name: Required value",
6913 }, {
6914 name: "invalid name",
6915 envs: []core.EnvFromSource{{
6916 SecretRef: &core.SecretEnvSource{
6917 LocalObjectReference: core.LocalObjectReference{Name: "&"}},
6918 }},
6919 expectedError: "field[0].secretRef.name: Invalid value",
6920 }, {
6921 name: "no refs",
6922 envs: []core.EnvFromSource{
6923 {},
6924 },
6925 expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
6926 }, {
6927 name: "multiple refs",
6928 envs: []core.EnvFromSource{{
6929 SecretRef: &core.SecretEnvSource{
6930 LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
6931 ConfigMapRef: &core.ConfigMapEnvSource{
6932 LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
6933 }},
6934 expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
6935 }, {
6936 name: "invalid secret ref name",
6937 envs: []core.EnvFromSource{{
6938 SecretRef: &core.SecretEnvSource{
6939 LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
6940 }},
6941 expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
6942 }, {
6943 name: "invalid config ref name",
6944 envs: []core.EnvFromSource{{
6945 ConfigMapRef: &core.ConfigMapEnvSource{
6946 LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
6947 }},
6948 expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
6949 },
6950 }
6951 for _, tc := range errorCases {
6952 if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
6953 t.Errorf("expected failure for %s", tc.name)
6954 } else {
6955 for i := range errs {
6956 str := errs[i].Error()
6957 if str != "" && !strings.Contains(str, tc.expectedError) {
6958 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
6959 }
6960 }
6961 }
6962 }
6963 }
6964
6965 func TestRelaxedValidateEnvFrom(t *testing.T) {
6966 successCase := []core.EnvFromSource{{
6967 ConfigMapRef: &core.ConfigMapEnvSource{
6968 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6969 },
6970 }, {
6971 Prefix: "!\"#$%&'()",
6972 ConfigMapRef: &core.ConfigMapEnvSource{
6973 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6974 },
6975 }, {
6976 Prefix: "* +,-./0123456789",
6977 ConfigMapRef: &core.ConfigMapEnvSource{
6978 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6979 },
6980 }, {
6981 SecretRef: &core.SecretEnvSource{
6982 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6983 },
6984 }, {
6985 Prefix: ":;<>?@",
6986 SecretRef: &core.SecretEnvSource{
6987 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6988 },
6989 }, {
6990 Prefix: "[\\]^_`{}|~",
6991 SecretRef: &core.SecretEnvSource{
6992 LocalObjectReference: core.LocalObjectReference{Name: "abc"},
6993 },
6994 },
6995 }
6996 if errs := ValidateEnvFrom(successCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
6997 t.Errorf("expected success: %v", errs)
6998 }
6999
7000 errorCases := []struct {
7001 name string
7002 envs []core.EnvFromSource
7003 expectedError string
7004 }{
7005 {
7006 name: "zero-length name",
7007 envs: []core.EnvFromSource{{
7008 ConfigMapRef: &core.ConfigMapEnvSource{
7009 LocalObjectReference: core.LocalObjectReference{Name: ""}},
7010 }},
7011 expectedError: "field[0].configMapRef.name: Required value",
7012 },
7013 {
7014 name: "invalid prefix",
7015 envs: []core.EnvFromSource{{
7016 Prefix: "=abc",
7017 ConfigMapRef: &core.ConfigMapEnvSource{
7018 LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
7019 }},
7020 expectedError: `field[0].prefix: Invalid value: "=abc": ` + relaxedEnvVarNameFmtErrMsg,
7021 },
7022 {
7023 name: "zero-length name",
7024 envs: []core.EnvFromSource{{
7025 SecretRef: &core.SecretEnvSource{
7026 LocalObjectReference: core.LocalObjectReference{Name: ""}},
7027 }},
7028 expectedError: "field[0].secretRef.name: Required value",
7029 }, {
7030 name: "invalid name",
7031 envs: []core.EnvFromSource{{
7032 SecretRef: &core.SecretEnvSource{
7033 LocalObjectReference: core.LocalObjectReference{Name: "&"}},
7034 }},
7035 expectedError: "field[0].secretRef.name: Invalid value",
7036 }, {
7037 name: "no refs",
7038 envs: []core.EnvFromSource{
7039 {},
7040 },
7041 expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
7042 }, {
7043 name: "multiple refs",
7044 envs: []core.EnvFromSource{{
7045 SecretRef: &core.SecretEnvSource{
7046 LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
7047 ConfigMapRef: &core.ConfigMapEnvSource{
7048 LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
7049 }},
7050 expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
7051 }, {
7052 name: "invalid secret ref name",
7053 envs: []core.EnvFromSource{{
7054 SecretRef: &core.SecretEnvSource{
7055 LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
7056 }},
7057 expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
7058 }, {
7059 name: "invalid config ref name",
7060 envs: []core.EnvFromSource{{
7061 ConfigMapRef: &core.ConfigMapEnvSource{
7062 LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
7063 }},
7064 expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
7065 },
7066 }
7067 for _, tc := range errorCases {
7068 if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) == 0 {
7069 t.Errorf("expected failure for %s", tc.name)
7070 } else {
7071 for i := range errs {
7072 str := errs[i].Error()
7073 if str != "" && !strings.Contains(str, tc.expectedError) {
7074 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
7075 }
7076 }
7077 }
7078 }
7079 }
7080
7081 func TestValidateVolumeMounts(t *testing.T) {
7082 volumes := []core.Volume{
7083 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
7084 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
7085 {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
7086 {Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
7087 Spec: core.PersistentVolumeClaimSpec{
7088 AccessModes: []core.PersistentVolumeAccessMode{
7089 core.ReadWriteOnce,
7090 },
7091 Resources: core.VolumeResourceRequirements{
7092 Requests: core.ResourceList{
7093 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
7094 },
7095 },
7096 },
7097 }}}},
7098 }
7099 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
7100 if len(v1err) > 0 {
7101 t.Errorf("Invalid test volume - expected success %v", v1err)
7102 return
7103 }
7104 container := core.Container{
7105 SecurityContext: nil,
7106 }
7107 propagation := core.MountPropagationBidirectional
7108
7109 successCase := []core.VolumeMount{
7110 {Name: "abc", MountPath: "/foo"},
7111 {Name: "123", MountPath: "/bar"},
7112 {Name: "abc-123", MountPath: "/baz"},
7113 {Name: "abc-123", MountPath: "/baa", SubPath: ""},
7114 {Name: "abc-123", MountPath: "/bab", SubPath: "baz"},
7115 {Name: "abc-123", MountPath: "d:", SubPath: ""},
7116 {Name: "abc-123", MountPath: "F:", SubPath: ""},
7117 {Name: "abc-123", MountPath: "G:\\mount", SubPath: ""},
7118 {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
7119 {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
7120 {Name: "ephemeral", MountPath: "/foobar"},
7121 {Name: "123", MountPath: "/rro-nil", ReadOnly: true, RecursiveReadOnly: nil},
7122 {Name: "123", MountPath: "/rro-disabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)},
7123 {Name: "123", MountPath: "/rro-disabled-2", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)},
7124 {Name: "123", MountPath: "/rro-ifpossible", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)},
7125 {Name: "123", MountPath: "/rro-enabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)},
7126 {Name: "123", MountPath: "/rro-enabled-2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationNone)},
7127 }
7128 goodVolumeDevices := []core.VolumeDevice{
7129 {Name: "xyz", DevicePath: "/foofoo"},
7130 {Name: "uvw", DevicePath: "/foofoo/share/test"},
7131 }
7132 if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
7133 t.Errorf("expected success: %v", errs)
7134 }
7135
7136 errorCases := map[string][]core.VolumeMount{
7137 "empty name": {{Name: "", MountPath: "/foo"}},
7138 "name not found": {{Name: "", MountPath: "/foo"}},
7139 "empty mountpath": {{Name: "abc", MountPath: ""}},
7140 "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
7141 "absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
7142 "subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
7143 "subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
7144 "subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
7145 "disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
7146 "name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}},
7147 "mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}},
7148 "both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}},
7149 "rro but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}},
7150 "rro with incompatible propagation": {{Name: "123", MountPath: "/rro-bad2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationHostToContainer)}},
7151 "rro-if-possible but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}},
7152 }
7153 badVolumeDevice := []core.VolumeDevice{
7154 {Name: "xyz", DevicePath: "/mnt/exists"},
7155 }
7156
7157 for k, v := range errorCases {
7158 if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
7159 t.Errorf("expected failure for %s", k)
7160 }
7161 }
7162 }
7163
7164 func TestValidateSubpathMutuallyExclusive(t *testing.T) {
7165 volumes := []core.Volume{
7166 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
7167 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
7168 {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
7169 }
7170 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
7171 if len(v1err) > 0 {
7172 t.Errorf("Invalid test volume - expected success %v", v1err)
7173 return
7174 }
7175
7176 container := core.Container{
7177 SecurityContext: nil,
7178 }
7179
7180 goodVolumeDevices := []core.VolumeDevice{
7181 {Name: "xyz", DevicePath: "/foofoo"},
7182 {Name: "uvw", DevicePath: "/foofoo/share/test"},
7183 }
7184
7185 cases := map[string]struct {
7186 mounts []core.VolumeMount
7187 expectError bool
7188 }{
7189 "subpath and subpathexpr not specified": {
7190 []core.VolumeMount{{
7191 Name: "abc-123",
7192 MountPath: "/bab",
7193 }},
7194 false,
7195 },
7196 "subpath expr specified": {
7197 []core.VolumeMount{{
7198 Name: "abc-123",
7199 MountPath: "/bab",
7200 SubPathExpr: "$(POD_NAME)",
7201 }},
7202 false,
7203 },
7204 "subpath specified": {
7205 []core.VolumeMount{{
7206 Name: "abc-123",
7207 MountPath: "/bab",
7208 SubPath: "baz",
7209 }},
7210 false,
7211 },
7212 "subpath and subpathexpr specified": {
7213 []core.VolumeMount{{
7214 Name: "abc-123",
7215 MountPath: "/bab",
7216 SubPath: "baz",
7217 SubPathExpr: "$(POD_NAME)",
7218 }},
7219 true,
7220 },
7221 }
7222
7223 for name, test := range cases {
7224 errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
7225
7226 if len(errs) != 0 && !test.expectError {
7227 t.Errorf("test %v failed: %+v", name, errs)
7228 }
7229
7230 if len(errs) == 0 && test.expectError {
7231 t.Errorf("test %v failed, expected error", name)
7232 }
7233 }
7234 }
7235
7236 func TestValidateDisabledSubpathExpr(t *testing.T) {
7237
7238 volumes := []core.Volume{
7239 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
7240 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
7241 {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
7242 }
7243 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
7244 if len(v1err) > 0 {
7245 t.Errorf("Invalid test volume - expected success %v", v1err)
7246 return
7247 }
7248
7249 container := core.Container{
7250 SecurityContext: nil,
7251 }
7252
7253 goodVolumeDevices := []core.VolumeDevice{
7254 {Name: "xyz", DevicePath: "/foofoo"},
7255 {Name: "uvw", DevicePath: "/foofoo/share/test"},
7256 }
7257
7258 cases := map[string]struct {
7259 mounts []core.VolumeMount
7260 expectError bool
7261 }{
7262 "subpath expr not specified": {
7263 []core.VolumeMount{{
7264 Name: "abc-123",
7265 MountPath: "/bab",
7266 }},
7267 false,
7268 },
7269 "subpath expr specified": {
7270 []core.VolumeMount{{
7271 Name: "abc-123",
7272 MountPath: "/bab",
7273 SubPathExpr: "$(POD_NAME)",
7274 }},
7275 false,
7276 },
7277 }
7278
7279 for name, test := range cases {
7280 errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
7281
7282 if len(errs) != 0 && !test.expectError {
7283 t.Errorf("test %v failed: %+v", name, errs)
7284 }
7285
7286 if len(errs) == 0 && test.expectError {
7287 t.Errorf("test %v failed, expected error", name)
7288 }
7289 }
7290 }
7291
7292 func TestValidateMountPropagation(t *testing.T) {
7293 bTrue := true
7294 bFalse := false
7295 privilegedContainer := &core.Container{
7296 SecurityContext: &core.SecurityContext{
7297 Privileged: &bTrue,
7298 },
7299 }
7300 nonPrivilegedContainer := &core.Container{
7301 SecurityContext: &core.SecurityContext{
7302 Privileged: &bFalse,
7303 },
7304 }
7305 defaultContainer := &core.Container{}
7306
7307 propagationBidirectional := core.MountPropagationBidirectional
7308 propagationHostToContainer := core.MountPropagationHostToContainer
7309 propagationNone := core.MountPropagationNone
7310 propagationInvalid := core.MountPropagationMode("invalid")
7311
7312 tests := []struct {
7313 mount core.VolumeMount
7314 container *core.Container
7315 expectError bool
7316 }{{
7317
7318 core.VolumeMount{Name: "foo", MountPath: "/foo"},
7319 defaultContainer,
7320 false,
7321 }, {
7322
7323 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
7324 defaultContainer,
7325 false,
7326 }, {
7327
7328 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone},
7329 defaultContainer,
7330 false,
7331 }, {
7332
7333 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
7334 defaultContainer,
7335 true,
7336 }, {
7337
7338 core.VolumeMount{Name: "foo", MountPath: "/foo"},
7339 nonPrivilegedContainer,
7340 false,
7341 }, {
7342
7343 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
7344 nonPrivilegedContainer,
7345 false,
7346 }, {
7347
7348 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
7349 nonPrivilegedContainer,
7350 true,
7351 }, {
7352
7353 core.VolumeMount{Name: "foo", MountPath: "/foo"},
7354 privilegedContainer,
7355 false,
7356 }, {
7357
7358 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
7359 privilegedContainer,
7360 false,
7361 }, {
7362
7363 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
7364 privilegedContainer,
7365 false,
7366 }, {
7367
7368 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid},
7369 privilegedContainer,
7370 true,
7371 }, {
7372
7373 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
7374 nil,
7375 false,
7376 },
7377 }
7378
7379 volumes := []core.Volume{
7380 {Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
7381 }
7382 vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
7383 if len(v2err) > 0 {
7384 t.Errorf("Invalid test volume - expected success %v", v2err)
7385 return
7386 }
7387 for i, test := range tests {
7388 errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
7389 if test.expectError && len(errs) == 0 {
7390 t.Errorf("test %d expected error, got none", i)
7391 }
7392 if !test.expectError && len(errs) != 0 {
7393 t.Errorf("test %d expected success, got error: %v", i, errs)
7394 }
7395 }
7396 }
7397
7398 func TestAlphaValidateVolumeDevices(t *testing.T) {
7399 volumes := []core.Volume{
7400 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
7401 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
7402 {Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
7403 {Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
7404 Spec: core.PersistentVolumeClaimSpec{
7405 AccessModes: []core.PersistentVolumeAccessMode{
7406 core.ReadWriteOnce,
7407 },
7408 Resources: core.VolumeResourceRequirements{
7409 Requests: core.ResourceList{
7410 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
7411 },
7412 },
7413 },
7414 }}}},
7415 }
7416
7417 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
7418 if len(v1err) > 0 {
7419 t.Errorf("Invalid test volumes - expected success %v", v1err)
7420 return
7421 }
7422
7423 successCase := []core.VolumeDevice{
7424 {Name: "abc", DevicePath: "/foo"},
7425 {Name: "abc-123", DevicePath: "/usr/share/test"},
7426 {Name: "ephemeral", DevicePath: "/disk"},
7427 }
7428 goodVolumeMounts := []core.VolumeMount{
7429 {Name: "xyz", MountPath: "/foofoo"},
7430 {Name: "ghi", MountPath: "/foo/usr/share/test"},
7431 }
7432
7433 errorCases := map[string][]core.VolumeDevice{
7434 "empty name": {{Name: "", DevicePath: "/foo"}},
7435 "duplicate name": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}},
7436 "name not found": {{Name: "not-found", DevicePath: "/usr/share/test"}},
7437 "name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}},
7438 "empty devicepath": {{Name: "abc", DevicePath: ""}},
7439 "relative devicepath": {{Name: "abc-123", DevicePath: "baz"}},
7440 "duplicate devicepath": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}},
7441 "no backsteps": {{Name: "def", DevicePath: "/baz/../"}},
7442 "name exists in volumemounts": {{Name: "abc", DevicePath: "/baz/../"}},
7443 "path exists in volumemounts": {{Name: "xyz", DevicePath: "/this/path/exists"}},
7444 "both exist in volumemounts": {{Name: "abc", DevicePath: "/this/path/exists"}},
7445 }
7446 badVolumeMounts := []core.VolumeMount{
7447 {Name: "abc", MountPath: "/foo"},
7448 {Name: "abc-123", MountPath: "/this/path/exists"},
7449 }
7450
7451
7452
7453 if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 {
7454 t.Errorf("expected success: %v", errs)
7455 }
7456
7457
7458
7459 for k, v := range errorCases {
7460 if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
7461 t.Errorf("expected failure for %s", k)
7462 }
7463 }
7464 }
7465
7466 func TestValidateProbe(t *testing.T) {
7467 handler := core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}
7468
7469 positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"}
7470 successCases := []*core.Probe{nil}
7471 for _, field := range positiveFields {
7472 probe := &core.Probe{ProbeHandler: handler}
7473 reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10)
7474 successCases = append(successCases, probe)
7475 }
7476
7477 for _, p := range successCases {
7478 if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
7479 t.Errorf("expected success: %v", errs)
7480 }
7481 }
7482
7483 errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}}
7484 for _, field := range positiveFields {
7485 probe := &core.Probe{ProbeHandler: handler}
7486 reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10)
7487 errorCases = append(errorCases, probe)
7488 }
7489 for _, p := range errorCases {
7490 if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
7491 t.Errorf("expected failure for %v", p)
7492 }
7493 }
7494 }
7495
7496 func Test_validateProbe(t *testing.T) {
7497 fldPath := field.NewPath("test")
7498 type args struct {
7499 probe *core.Probe
7500 fldPath *field.Path
7501 }
7502 tests := []struct {
7503 name string
7504 args args
7505 want field.ErrorList
7506 }{{
7507 args: args{
7508 probe: &core.Probe{},
7509 fldPath: fldPath,
7510 },
7511 want: field.ErrorList{field.Required(fldPath, "must specify a handler type")},
7512 }, {
7513 args: args{
7514 probe: &core.Probe{
7515 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7516 },
7517 fldPath: fldPath,
7518 },
7519 want: field.ErrorList{},
7520 }, {
7521 args: args{
7522 probe: &core.Probe{
7523 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7524 InitialDelaySeconds: -1,
7525 },
7526 fldPath: fldPath,
7527 },
7528 want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")},
7529 }, {
7530 args: args{
7531 probe: &core.Probe{
7532 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7533 TimeoutSeconds: -1,
7534 },
7535 fldPath: fldPath,
7536 },
7537 want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")},
7538 }, {
7539 args: args{
7540 probe: &core.Probe{
7541 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7542 PeriodSeconds: -1,
7543 },
7544 fldPath: fldPath,
7545 },
7546 want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")},
7547 }, {
7548 args: args{
7549 probe: &core.Probe{
7550 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7551 SuccessThreshold: -1,
7552 },
7553 fldPath: fldPath,
7554 },
7555 want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")},
7556 }, {
7557 args: args{
7558 probe: &core.Probe{
7559 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7560 FailureThreshold: -1,
7561 },
7562 fldPath: fldPath,
7563 },
7564 want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")},
7565 }, {
7566 args: args{
7567 probe: &core.Probe{
7568 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7569 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
7570 },
7571 fldPath: fldPath,
7572 },
7573 want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")},
7574 }, {
7575 args: args{
7576 probe: &core.Probe{
7577 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7578 TerminationGracePeriodSeconds: utilpointer.Int64(0),
7579 },
7580 fldPath: fldPath,
7581 },
7582 want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")},
7583 }, {
7584 args: args{
7585 probe: &core.Probe{
7586 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
7587 TerminationGracePeriodSeconds: utilpointer.Int64(1),
7588 },
7589 fldPath: fldPath,
7590 },
7591 want: field.ErrorList{},
7592 },
7593 }
7594 for _, tt := range tests {
7595 t.Run(tt.name, func(t *testing.T) {
7596 got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath)
7597 if len(got) != len(tt.want) {
7598 t.Errorf("validateProbe() = %v, want %v", got, tt.want)
7599 return
7600 }
7601 for i := range got {
7602 if got[i].Type != tt.want[i].Type ||
7603 got[i].Field != tt.want[i].Field {
7604 t.Errorf("validateProbe()[%d] = %v, want %v", i, got[i], tt.want[i])
7605 }
7606 }
7607 })
7608 }
7609 }
7610
7611 func TestValidateHandler(t *testing.T) {
7612 successCases := []core.ProbeHandler{
7613 {Exec: &core.ExecAction{Command: []string{"echo"}}},
7614 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt32(1), Host: "", Scheme: "HTTP"}},
7615 {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65535), Host: "host", Scheme: "HTTP"}},
7616 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
7617 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}},
7618 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}},
7619 }
7620 for _, h := range successCases {
7621 if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
7622 t.Errorf("expected success: %v", errs)
7623 }
7624 }
7625
7626 errorCases := []core.ProbeHandler{
7627 {},
7628 {Exec: &core.ExecAction{Command: []string{}}},
7629 {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt32(0), Host: ""}},
7630 {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65536), Host: "host"}},
7631 {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
7632 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}},
7633 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
7634 }
7635 for _, h := range errorCases {
7636 if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
7637 t.Errorf("expected failure for %#v", h)
7638 }
7639 }
7640 }
7641
7642 func TestValidatePullPolicy(t *testing.T) {
7643 type T struct {
7644 Container core.Container
7645 ExpectedPolicy core.PullPolicy
7646 }
7647 testCases := map[string]T{
7648 "NotPresent1": {
7649 core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
7650 core.PullIfNotPresent,
7651 },
7652 "NotPresent2": {
7653 core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
7654 core.PullIfNotPresent,
7655 },
7656 "Always1": {
7657 core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"},
7658 core.PullAlways,
7659 },
7660 "Always2": {
7661 core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"},
7662 core.PullAlways,
7663 },
7664 "Never1": {
7665 core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"},
7666 core.PullNever,
7667 },
7668 "Never2": {
7669 core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"},
7670 core.PullNever,
7671 },
7672 }
7673 for k, v := range testCases {
7674 ctr := &v.Container
7675 errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field"))
7676 if len(errs) != 0 {
7677 t.Errorf("case[%s] expected success, got %#v", k, errs)
7678 }
7679 if ctr.ImagePullPolicy != v.ExpectedPolicy {
7680 t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy)
7681 }
7682 }
7683 }
7684
7685 func TestValidateResizePolicy(t *testing.T) {
7686 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)()
7687 tSupportedResizeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
7688 tSupportedResizePolicies := sets.NewString(string(core.NotRequired), string(core.RestartContainer))
7689 type T struct {
7690 PolicyList []core.ContainerResizePolicy
7691 ExpectError bool
7692 Errors field.ErrorList
7693 PodRestartPolicy core.RestartPolicy
7694 }
7695
7696 testCases := map[string]T{
7697 "ValidCPUandMemoryPolicies": {
7698 PolicyList: []core.ContainerResizePolicy{
7699 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
7700 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
7701 },
7702 ExpectError: false,
7703 Errors: nil,
7704 PodRestartPolicy: "Always",
7705 },
7706 "ValidCPUPolicy": {
7707 PolicyList: []core.ContainerResizePolicy{
7708 {ResourceName: "cpu", RestartPolicy: "RestartContainer"},
7709 },
7710 ExpectError: false,
7711 Errors: nil,
7712 PodRestartPolicy: "Always",
7713 },
7714 "ValidMemoryPolicy": {
7715 PolicyList: []core.ContainerResizePolicy{
7716 {ResourceName: "memory", RestartPolicy: "NotRequired"},
7717 },
7718 ExpectError: false,
7719 Errors: nil,
7720 PodRestartPolicy: "Always",
7721 },
7722 "NoPolicy": {
7723 PolicyList: []core.ContainerResizePolicy{},
7724 ExpectError: false,
7725 Errors: nil,
7726 PodRestartPolicy: "Always",
7727 },
7728 "ValidCPUandInvalidMemoryPolicy": {
7729 PolicyList: []core.ContainerResizePolicy{
7730 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
7731 {ResourceName: "memory", RestartPolicy: "Restarrrt"},
7732 },
7733 ExpectError: true,
7734 Errors: field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("Restarrrt"), tSupportedResizePolicies.List())},
7735 PodRestartPolicy: "Always",
7736 },
7737 "ValidMemoryandInvalidCPUPolicy": {
7738 PolicyList: []core.ContainerResizePolicy{
7739 {ResourceName: "cpu", RestartPolicy: "RestartNotRequirrred"},
7740 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
7741 },
7742 ExpectError: true,
7743 Errors: field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartNotRequirrred"), tSupportedResizePolicies.List())},
7744 PodRestartPolicy: "Always",
7745 },
7746 "InvalidResourceNameValidPolicy": {
7747 PolicyList: []core.ContainerResizePolicy{
7748 {ResourceName: "cpuuu", RestartPolicy: "NotRequired"},
7749 },
7750 ExpectError: true,
7751 Errors: field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceName("cpuuu"), tSupportedResizeResources.List())},
7752 PodRestartPolicy: "Always",
7753 },
7754 "ValidResourceNameMissingPolicy": {
7755 PolicyList: []core.ContainerResizePolicy{
7756 {ResourceName: "memory", RestartPolicy: ""},
7757 },
7758 ExpectError: true,
7759 Errors: field.ErrorList{field.Required(field.NewPath("field"), "")},
7760 PodRestartPolicy: "Always",
7761 },
7762 "RepeatedPolicies": {
7763 PolicyList: []core.ContainerResizePolicy{
7764 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
7765 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
7766 {ResourceName: "cpu", RestartPolicy: "RestartContainer"},
7767 },
7768 ExpectError: true,
7769 Errors: field.ErrorList{field.Duplicate(field.NewPath("field").Index(2), core.ResourceCPU)},
7770 PodRestartPolicy: "Always",
7771 },
7772 "InvalidCPUPolicyWithPodRestartPolicy": {
7773 PolicyList: []core.ContainerResizePolicy{
7774 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
7775 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
7776 },
7777 ExpectError: true,
7778 Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
7779 PodRestartPolicy: "Never",
7780 },
7781 "InvalidMemoryPolicyWithPodRestartPolicy": {
7782 PolicyList: []core.ContainerResizePolicy{
7783 {ResourceName: "cpu", RestartPolicy: "RestartContainer"},
7784 {ResourceName: "memory", RestartPolicy: "NotRequired"},
7785 },
7786 ExpectError: true,
7787 Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
7788 PodRestartPolicy: "Never",
7789 },
7790 "InvalidMemoryCPUPolicyWithPodRestartPolicy": {
7791 PolicyList: []core.ContainerResizePolicy{
7792 {ResourceName: "cpu", RestartPolicy: "RestartContainer"},
7793 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
7794 },
7795 ExpectError: true,
7796 Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'"), field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
7797 PodRestartPolicy: "Never",
7798 },
7799 "ValidMemoryCPUPolicyWithPodRestartPolicy": {
7800 PolicyList: []core.ContainerResizePolicy{
7801 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
7802 {ResourceName: "memory", RestartPolicy: "NotRequired"},
7803 },
7804 ExpectError: false,
7805 Errors: nil,
7806 PodRestartPolicy: "Never",
7807 },
7808 }
7809 for k, v := range testCases {
7810 errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy)
7811 if !v.ExpectError && len(errs) > 0 {
7812 t.Errorf("Testcase %s - expected success, got error: %+v", k, errs)
7813 }
7814 if v.ExpectError {
7815 if len(errs) == 0 {
7816 t.Errorf("Testcase %s - expected error, got success", k)
7817 }
7818 delta := cmp.Diff(errs, v.Errors)
7819 if delta != "" {
7820 t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta)
7821 }
7822 }
7823 }
7824 }
7825
7826 func getResourceLimits(cpu, memory string) core.ResourceList {
7827 res := core.ResourceList{}
7828 res[core.ResourceCPU] = resource.MustParse(cpu)
7829 res[core.ResourceMemory] = resource.MustParse(memory)
7830 return res
7831 }
7832
7833 func getResources(cpu, memory, storage string) core.ResourceList {
7834 res := core.ResourceList{}
7835 if cpu != "" {
7836 res[core.ResourceCPU] = resource.MustParse(cpu)
7837 }
7838 if memory != "" {
7839 res[core.ResourceMemory] = resource.MustParse(memory)
7840 }
7841 if storage != "" {
7842 res[core.ResourceEphemeralStorage] = resource.MustParse(storage)
7843 }
7844 return res
7845 }
7846
7847 func TestValidateEphemeralContainers(t *testing.T) {
7848 containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
7849 initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
7850 vols := map[string]core.VolumeSource{
7851 "blk": {PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}},
7852 "vol": {EmptyDir: &core.EmptyDirVolumeSource{}},
7853 }
7854
7855
7856 for title, ephemeralContainers := range map[string][]core.EphemeralContainer{
7857 "Empty Ephemeral Containers": {},
7858 "Single Container": {
7859 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7860 },
7861 "Multiple Containers": {
7862 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7863 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7864 },
7865 "Single Container with Target": {{
7866 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
7867 TargetContainerName: "ctr",
7868 }},
7869 "All allowed fields": {{
7870 EphemeralContainerCommon: core.EphemeralContainerCommon{
7871
7872 Name: "debug",
7873 Image: "image",
7874 Command: []string{"bash"},
7875 Args: []string{"bash"},
7876 WorkingDir: "/",
7877 EnvFrom: []core.EnvFromSource{{
7878 ConfigMapRef: &core.ConfigMapEnvSource{
7879 LocalObjectReference: core.LocalObjectReference{Name: "dummy"},
7880 Optional: &[]bool{true}[0],
7881 },
7882 }},
7883 Env: []core.EnvVar{
7884 {Name: "TEST", Value: "TRUE"},
7885 },
7886 VolumeMounts: []core.VolumeMount{
7887 {Name: "vol", MountPath: "/vol"},
7888 },
7889 VolumeDevices: []core.VolumeDevice{
7890 {Name: "blk", DevicePath: "/dev/block"},
7891 },
7892 TerminationMessagePath: "/dev/termination-log",
7893 TerminationMessagePolicy: "File",
7894 ImagePullPolicy: "IfNotPresent",
7895 SecurityContext: &core.SecurityContext{
7896 Capabilities: &core.Capabilities{
7897 Add: []core.Capability{"SYS_ADMIN"},
7898 },
7899 },
7900 Stdin: true,
7901 StdinOnce: true,
7902 TTY: true,
7903 },
7904 }},
7905 } {
7906 var PodRestartPolicy core.RestartPolicy
7907 PodRestartPolicy = "Never"
7908 if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
7909 t.Errorf("expected success for '%s' but got errors: %v", title, errs)
7910 }
7911
7912 PodRestartPolicy = "Always"
7913 if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
7914 t.Errorf("expected success for '%s' but got errors: %v", title, errs)
7915 }
7916
7917 PodRestartPolicy = "OnFailure"
7918 if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
7919 t.Errorf("expected success for '%s' but got errors: %v", title, errs)
7920 }
7921 }
7922
7923
7924 tcs := []struct {
7925 title, line string
7926 ephemeralContainers []core.EphemeralContainer
7927 expectedErrors field.ErrorList
7928 }{{
7929 "Name Collision with Container.Containers",
7930 line(),
7931 []core.EphemeralContainer{
7932 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7933 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7934 },
7935 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
7936 }, {
7937 "Name Collision with Container.InitContainers",
7938 line(),
7939 []core.EphemeralContainer{
7940 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7941 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7942 },
7943 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
7944 }, {
7945 "Name Collision with EphemeralContainers",
7946 line(),
7947 []core.EphemeralContainer{
7948 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7949 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7950 },
7951 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}},
7952 }, {
7953 "empty Container",
7954 line(),
7955 []core.EphemeralContainer{
7956 {EphemeralContainerCommon: core.EphemeralContainerCommon{}},
7957 },
7958 field.ErrorList{
7959 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"},
7960 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"},
7961 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"},
7962 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"},
7963 },
7964 }, {
7965 "empty Container Name",
7966 line(),
7967 []core.EphemeralContainer{
7968 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7969 },
7970 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}},
7971 }, {
7972 "whitespace padded image name",
7973 line(),
7974 []core.EphemeralContainer{
7975 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
7976 },
7977 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}},
7978 }, {
7979 "invalid image pull policy",
7980 line(),
7981 []core.EphemeralContainer{
7982 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}},
7983 },
7984 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}},
7985 }, {
7986 "TargetContainerName doesn't exist",
7987 line(),
7988 []core.EphemeralContainer{{
7989 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
7990 TargetContainerName: "bogus",
7991 }},
7992 field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}},
7993 }, {
7994 "Targets an ephemeral container",
7995 line(),
7996 []core.EphemeralContainer{{
7997 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
7998 }, {
7999 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8000 TargetContainerName: "debug",
8001 }},
8002 field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}},
8003 }, {
8004 "Container uses disallowed field: Lifecycle",
8005 line(),
8006 []core.EphemeralContainer{{
8007 EphemeralContainerCommon: core.EphemeralContainerCommon{
8008 Name: "debug",
8009 Image: "image",
8010 ImagePullPolicy: "IfNotPresent",
8011 TerminationMessagePolicy: "File",
8012 Lifecycle: &core.Lifecycle{
8013 PreStop: &core.LifecycleHandler{
8014 Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
8015 },
8016 },
8017 },
8018 }},
8019 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
8020 }, {
8021 "Container uses disallowed field: LivenessProbe",
8022 line(),
8023 []core.EphemeralContainer{{
8024 EphemeralContainerCommon: core.EphemeralContainerCommon{
8025 Name: "debug",
8026 Image: "image",
8027 ImagePullPolicy: "IfNotPresent",
8028 TerminationMessagePolicy: "File",
8029 LivenessProbe: &core.Probe{
8030 ProbeHandler: core.ProbeHandler{
8031 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
8032 },
8033 SuccessThreshold: 1,
8034 },
8035 },
8036 }},
8037 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}},
8038 }, {
8039 "Container uses disallowed field: Ports",
8040 line(),
8041 []core.EphemeralContainer{{
8042 EphemeralContainerCommon: core.EphemeralContainerCommon{
8043 Name: "debug",
8044 Image: "image",
8045 ImagePullPolicy: "IfNotPresent",
8046 TerminationMessagePolicy: "File",
8047 Ports: []core.ContainerPort{
8048 {Protocol: "TCP", ContainerPort: 80},
8049 },
8050 },
8051 }},
8052 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}},
8053 }, {
8054 "Container uses disallowed field: ReadinessProbe",
8055 line(),
8056 []core.EphemeralContainer{{
8057 EphemeralContainerCommon: core.EphemeralContainerCommon{
8058 Name: "debug",
8059 Image: "image",
8060 ImagePullPolicy: "IfNotPresent",
8061 TerminationMessagePolicy: "File",
8062 ReadinessProbe: &core.Probe{
8063 ProbeHandler: core.ProbeHandler{
8064 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
8065 },
8066 },
8067 },
8068 }},
8069 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}},
8070 }, {
8071 "Container uses disallowed field: StartupProbe",
8072 line(),
8073 []core.EphemeralContainer{{
8074 EphemeralContainerCommon: core.EphemeralContainerCommon{
8075 Name: "debug",
8076 Image: "image",
8077 ImagePullPolicy: "IfNotPresent",
8078 TerminationMessagePolicy: "File",
8079 StartupProbe: &core.Probe{
8080 ProbeHandler: core.ProbeHandler{
8081 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
8082 },
8083 SuccessThreshold: 1,
8084 },
8085 },
8086 }},
8087 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}},
8088 }, {
8089 "Container uses disallowed field: Resources",
8090 line(),
8091 []core.EphemeralContainer{{
8092 EphemeralContainerCommon: core.EphemeralContainerCommon{
8093 Name: "debug",
8094 Image: "image",
8095 ImagePullPolicy: "IfNotPresent",
8096 TerminationMessagePolicy: "File",
8097 Resources: core.ResourceRequirements{
8098 Limits: core.ResourceList{
8099 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8100 },
8101 },
8102 },
8103 }},
8104 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}},
8105 }, {
8106 "Container uses disallowed field: VolumeMount.SubPath",
8107 line(),
8108 []core.EphemeralContainer{{
8109 EphemeralContainerCommon: core.EphemeralContainerCommon{
8110 Name: "debug",
8111 Image: "image",
8112 ImagePullPolicy: "IfNotPresent",
8113 TerminationMessagePolicy: "File",
8114 VolumeMounts: []core.VolumeMount{
8115 {Name: "vol", MountPath: "/vol"},
8116 {Name: "vol", MountPath: "/volsub", SubPath: "foo"},
8117 },
8118 },
8119 }},
8120 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}},
8121 }, {
8122 "Container uses disallowed field: VolumeMount.SubPathExpr",
8123 line(),
8124 []core.EphemeralContainer{{
8125 EphemeralContainerCommon: core.EphemeralContainerCommon{
8126 Name: "debug",
8127 Image: "image",
8128 ImagePullPolicy: "IfNotPresent",
8129 TerminationMessagePolicy: "File",
8130 VolumeMounts: []core.VolumeMount{
8131 {Name: "vol", MountPath: "/vol"},
8132 {Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"},
8133 },
8134 },
8135 }},
8136 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}},
8137 }, {
8138 "Disallowed field with other errors should only return a single Forbidden",
8139 line(),
8140 []core.EphemeralContainer{{
8141 EphemeralContainerCommon: core.EphemeralContainerCommon{
8142 Name: "debug",
8143 Image: "image",
8144 ImagePullPolicy: "IfNotPresent",
8145 TerminationMessagePolicy: "File",
8146 Lifecycle: &core.Lifecycle{
8147 PreStop: &core.LifecycleHandler{
8148 Exec: &core.ExecAction{Command: []string{}},
8149 },
8150 },
8151 },
8152 }},
8153 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
8154 }, {
8155 "Container uses disallowed field: ResizePolicy",
8156 line(),
8157 []core.EphemeralContainer{{
8158 EphemeralContainerCommon: core.EphemeralContainerCommon{
8159 Name: "resources-resize-policy",
8160 Image: "image",
8161 ImagePullPolicy: "IfNotPresent",
8162 TerminationMessagePolicy: "File",
8163 ResizePolicy: []core.ContainerResizePolicy{
8164 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
8165 },
8166 },
8167 }},
8168 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}},
8169 }, {
8170 "Forbidden RestartPolicy: Always",
8171 line(),
8172 []core.EphemeralContainer{{
8173 EphemeralContainerCommon: core.EphemeralContainerCommon{
8174 Name: "foo",
8175 Image: "image",
8176 ImagePullPolicy: "IfNotPresent",
8177 TerminationMessagePolicy: "File",
8178 RestartPolicy: &containerRestartPolicyAlways,
8179 },
8180 }},
8181 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
8182 }, {
8183 "Forbidden RestartPolicy: OnFailure",
8184 line(),
8185 []core.EphemeralContainer{{
8186 EphemeralContainerCommon: core.EphemeralContainerCommon{
8187 Name: "foo",
8188 Image: "image",
8189 ImagePullPolicy: "IfNotPresent",
8190 TerminationMessagePolicy: "File",
8191 RestartPolicy: &containerRestartPolicyOnFailure,
8192 },
8193 }},
8194 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
8195 }, {
8196 "Forbidden RestartPolicy: Never",
8197 line(),
8198 []core.EphemeralContainer{{
8199 EphemeralContainerCommon: core.EphemeralContainerCommon{
8200 Name: "foo",
8201 Image: "image",
8202 ImagePullPolicy: "IfNotPresent",
8203 TerminationMessagePolicy: "File",
8204 RestartPolicy: &containerRestartPolicyNever,
8205 },
8206 }},
8207 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
8208 }, {
8209 "Forbidden RestartPolicy: invalid",
8210 line(),
8211 []core.EphemeralContainer{{
8212 EphemeralContainerCommon: core.EphemeralContainerCommon{
8213 Name: "foo",
8214 Image: "image",
8215 ImagePullPolicy: "IfNotPresent",
8216 TerminationMessagePolicy: "File",
8217 RestartPolicy: &containerRestartPolicyInvalid,
8218 },
8219 }},
8220 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
8221 }, {
8222 "Forbidden RestartPolicy: empty",
8223 line(),
8224 []core.EphemeralContainer{{
8225 EphemeralContainerCommon: core.EphemeralContainerCommon{
8226 Name: "foo",
8227 Image: "image",
8228 ImagePullPolicy: "IfNotPresent",
8229 TerminationMessagePolicy: "File",
8230 RestartPolicy: &containerRestartPolicyEmpty,
8231 },
8232 }},
8233 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
8234 },
8235 }
8236
8237 var PodRestartPolicy core.RestartPolicy
8238
8239 for _, tc := range tcs {
8240 t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
8241
8242 PodRestartPolicy = "Never"
8243 errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
8244 if len(errs) == 0 {
8245 t.Fatal("expected error but received none")
8246 }
8247
8248 PodRestartPolicy = "Always"
8249 errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
8250 if len(errs) == 0 {
8251 t.Fatal("expected error but received none")
8252 }
8253
8254 PodRestartPolicy = "OnFailure"
8255 errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
8256 if len(errs) == 0 {
8257 t.Fatal("expected error but received none")
8258 }
8259
8260 if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
8261 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
8262 t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
8263 }
8264 })
8265 }
8266 }
8267
8268 func TestValidateWindowsPodSecurityContext(t *testing.T) {
8269 validWindowsSC := &core.PodSecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}
8270 invalidWindowsSC := &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummyRole"}}
8271 cases := map[string]struct {
8272 podSec *core.PodSpec
8273 expectErr bool
8274 errorType field.ErrorType
8275 errorDetail string
8276 }{
8277 "valid SC, windows, no error": {
8278 podSec: &core.PodSpec{SecurityContext: validWindowsSC},
8279 expectErr: false,
8280 },
8281 "invalid SC, windows, error": {
8282 podSec: &core.PodSpec{SecurityContext: invalidWindowsSC},
8283 errorType: "FieldValueForbidden",
8284 errorDetail: "cannot be set for a windows pod",
8285 expectErr: true,
8286 },
8287 }
8288 for k, v := range cases {
8289 t.Run(k, func(t *testing.T) {
8290 errs := validateWindows(v.podSec, field.NewPath("field"))
8291 if v.expectErr && len(errs) > 0 {
8292 if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
8293 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
8294 }
8295 } else if v.expectErr && len(errs) == 0 {
8296 t.Errorf("Unexpected success")
8297 }
8298 if !v.expectErr && len(errs) != 0 {
8299 t.Errorf("Unexpected error(s): %v", errs)
8300 }
8301 })
8302 }
8303 }
8304
8305 func TestValidateLinuxPodSecurityContext(t *testing.T) {
8306 runAsUser := int64(1)
8307 validLinuxSC := &core.PodSecurityContext{
8308 SELinuxOptions: &core.SELinuxOptions{
8309 User: "user",
8310 Role: "role",
8311 Type: "type",
8312 Level: "level",
8313 },
8314 RunAsUser: &runAsUser,
8315 }
8316 invalidLinuxSC := &core.PodSecurityContext{
8317 WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
8318 }
8319
8320 cases := map[string]struct {
8321 podSpec *core.PodSpec
8322 expectErr bool
8323 errorType field.ErrorType
8324 errorDetail string
8325 }{
8326 "valid SC, linux, no error": {
8327 podSpec: &core.PodSpec{SecurityContext: validLinuxSC},
8328 expectErr: false,
8329 },
8330 "invalid SC, linux, error": {
8331 podSpec: &core.PodSpec{SecurityContext: invalidLinuxSC},
8332 errorType: "FieldValueForbidden",
8333 errorDetail: "windows options cannot be set for a linux pod",
8334 expectErr: true,
8335 },
8336 }
8337 for k, v := range cases {
8338 t.Run(k, func(t *testing.T) {
8339 errs := validateLinux(v.podSpec, field.NewPath("field"))
8340 if v.expectErr && len(errs) > 0 {
8341 if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
8342 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
8343 }
8344 } else if v.expectErr && len(errs) == 0 {
8345 t.Errorf("Unexpected success")
8346 }
8347 if !v.expectErr && len(errs) != 0 {
8348 t.Errorf("Unexpected error(s): %v", errs)
8349 }
8350 })
8351 }
8352 }
8353
8354 func TestValidateContainers(t *testing.T) {
8355 volumeDevices := make(map[string]core.VolumeSource)
8356 capabilities.SetForTests(capabilities.Capabilities{
8357 AllowPrivileged: true,
8358 })
8359
8360 successCase := []core.Container{
8361 {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8362
8363 {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8364 {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8365 {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8366 {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {
8367 Name: "life-123",
8368 Image: "image",
8369 Lifecycle: &core.Lifecycle{
8370 PreStop: &core.LifecycleHandler{
8371 Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
8372 },
8373 },
8374 ImagePullPolicy: "IfNotPresent",
8375 TerminationMessagePolicy: "File",
8376 }, {
8377 Name: "resources-test",
8378 Image: "image",
8379 Resources: core.ResourceRequirements{
8380 Limits: core.ResourceList{
8381 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8382 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8383 core.ResourceName("my.org/resource"): resource.MustParse("10"),
8384 },
8385 },
8386 ImagePullPolicy: "IfNotPresent",
8387 TerminationMessagePolicy: "File",
8388 }, {
8389 Name: "resources-test-with-request-and-limit",
8390 Image: "image",
8391 Resources: core.ResourceRequirements{
8392 Requests: core.ResourceList{
8393 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8394 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8395 },
8396 Limits: core.ResourceList{
8397 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8398 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8399 },
8400 },
8401 ImagePullPolicy: "IfNotPresent",
8402 TerminationMessagePolicy: "File",
8403 }, {
8404 Name: "resources-request-limit-simple",
8405 Image: "image",
8406 Resources: core.ResourceRequirements{
8407 Requests: core.ResourceList{
8408 core.ResourceName(core.ResourceCPU): resource.MustParse("8"),
8409 },
8410 Limits: core.ResourceList{
8411 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8412 },
8413 },
8414 ImagePullPolicy: "IfNotPresent",
8415 TerminationMessagePolicy: "File",
8416 }, {
8417 Name: "resources-request-limit-edge",
8418 Image: "image",
8419 Resources: core.ResourceRequirements{
8420 Requests: core.ResourceList{
8421 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8422 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8423 core.ResourceName("my.org/resource"): resource.MustParse("10"),
8424 },
8425 Limits: core.ResourceList{
8426 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8427 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8428 core.ResourceName("my.org/resource"): resource.MustParse("10"),
8429 },
8430 },
8431 ImagePullPolicy: "IfNotPresent",
8432 TerminationMessagePolicy: "File",
8433 }, {
8434 Name: "resources-request-limit-partials",
8435 Image: "image",
8436 Resources: core.ResourceRequirements{
8437 Requests: core.ResourceList{
8438 core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"),
8439 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8440 },
8441 Limits: core.ResourceList{
8442 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
8443 core.ResourceName("my.org/resource"): resource.MustParse("10"),
8444 },
8445 },
8446 ImagePullPolicy: "IfNotPresent",
8447 TerminationMessagePolicy: "File",
8448 }, {
8449 Name: "resources-request",
8450 Image: "image",
8451 Resources: core.ResourceRequirements{
8452 Requests: core.ResourceList{
8453 core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"),
8454 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
8455 },
8456 },
8457 ImagePullPolicy: "IfNotPresent",
8458 TerminationMessagePolicy: "File",
8459 }, {
8460 Name: "resources-resize-policy",
8461 Image: "image",
8462 ResizePolicy: []core.ContainerResizePolicy{
8463 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
8464 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
8465 },
8466 ImagePullPolicy: "IfNotPresent",
8467 TerminationMessagePolicy: "File",
8468 }, {
8469 Name: "same-host-port-different-protocol",
8470 Image: "image",
8471 Ports: []core.ContainerPort{
8472 {ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
8473 {ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
8474 },
8475 ImagePullPolicy: "IfNotPresent",
8476 TerminationMessagePolicy: "File",
8477 }, {
8478 Name: "fallback-to-logs-termination-message",
8479 Image: "image",
8480 ImagePullPolicy: "IfNotPresent",
8481 TerminationMessagePolicy: "FallbackToLogsOnError",
8482 }, {
8483 Name: "file-termination-message",
8484 Image: "image",
8485 ImagePullPolicy: "IfNotPresent",
8486 TerminationMessagePolicy: "File",
8487 }, {
8488 Name: "env-from-source",
8489 Image: "image",
8490 ImagePullPolicy: "IfNotPresent",
8491 TerminationMessagePolicy: "File",
8492 EnvFrom: []core.EnvFromSource{{
8493 ConfigMapRef: &core.ConfigMapEnvSource{
8494 LocalObjectReference: core.LocalObjectReference{
8495 Name: "test",
8496 },
8497 },
8498 }},
8499 },
8500 {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, {
8501 Name: "live-123",
8502 Image: "image",
8503 LivenessProbe: &core.Probe{
8504 ProbeHandler: core.ProbeHandler{
8505 TCPSocket: &core.TCPSocketAction{
8506 Port: intstr.FromInt32(80),
8507 },
8508 },
8509 SuccessThreshold: 1,
8510 },
8511 ImagePullPolicy: "IfNotPresent",
8512 TerminationMessagePolicy: "File",
8513 }, {
8514 Name: "startup-123",
8515 Image: "image",
8516 StartupProbe: &core.Probe{
8517 ProbeHandler: core.ProbeHandler{
8518 TCPSocket: &core.TCPSocketAction{
8519 Port: intstr.FromInt32(80),
8520 },
8521 },
8522 SuccessThreshold: 1,
8523 },
8524 ImagePullPolicy: "IfNotPresent",
8525 TerminationMessagePolicy: "File",
8526 }, {
8527 Name: "resize-policy-cpu",
8528 Image: "image",
8529 ImagePullPolicy: "IfNotPresent",
8530 TerminationMessagePolicy: "File",
8531 ResizePolicy: []core.ContainerResizePolicy{
8532 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
8533 },
8534 }, {
8535 Name: "resize-policy-mem",
8536 Image: "image",
8537 ImagePullPolicy: "IfNotPresent",
8538 TerminationMessagePolicy: "File",
8539 ResizePolicy: []core.ContainerResizePolicy{
8540 {ResourceName: "memory", RestartPolicy: "RestartContainer"},
8541 },
8542 }, {
8543 Name: "resize-policy-cpu-and-mem",
8544 Image: "image",
8545 ImagePullPolicy: "IfNotPresent",
8546 TerminationMessagePolicy: "File",
8547 ResizePolicy: []core.ContainerResizePolicy{
8548 {ResourceName: "memory", RestartPolicy: "NotRequired"},
8549 {ResourceName: "cpu", RestartPolicy: "RestartContainer"},
8550 },
8551 },
8552 }
8553
8554 var PodRestartPolicy core.RestartPolicy = "Always"
8555 if errs := validateContainers(successCase, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
8556 t.Errorf("expected success: %v", errs)
8557 }
8558
8559 capabilities.SetForTests(capabilities.Capabilities{
8560 AllowPrivileged: false,
8561 })
8562 errorCases := []struct {
8563 title, line string
8564 containers []core.Container
8565 expectedErrors field.ErrorList
8566 }{{
8567 "zero-length name",
8568 line(),
8569 []core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
8570 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}},
8571 }, {
8572 "zero-length-image",
8573 line(),
8574 []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
8575 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
8576 }, {
8577 "name > 63 characters",
8578 line(),
8579 []core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
8580 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
8581 }, {
8582 "name not a DNS label",
8583 line(),
8584 []core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
8585 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
8586 }, {
8587 "name not unique",
8588 line(),
8589 []core.Container{
8590 {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8591 {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8592 },
8593 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}},
8594 }, {
8595 "zero-length image",
8596 line(),
8597 []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
8598 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
8599 }, {
8600 "host port not unique",
8601 line(),
8602 []core.Container{
8603 {Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}},
8604 ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8605 {Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}},
8606 ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8607 },
8608 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}},
8609 }, {
8610 "invalid env var name",
8611 line(),
8612 []core.Container{
8613 {Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8614 },
8615 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}},
8616 }, {
8617 "unknown volume name",
8618 line(),
8619 []core.Container{
8620 {Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}},
8621 ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
8622 },
8623 field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}},
8624 }, {
8625 "invalid lifecycle, no exec command.",
8626 line(),
8627 []core.Container{{
8628 Name: "life-123",
8629 Image: "image",
8630 Lifecycle: &core.Lifecycle{
8631 PreStop: &core.LifecycleHandler{
8632 Exec: &core.ExecAction{},
8633 },
8634 },
8635 ImagePullPolicy: "IfNotPresent",
8636 TerminationMessagePolicy: "File",
8637 }},
8638 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}},
8639 }, {
8640 "invalid lifecycle, no http path.",
8641 line(),
8642 []core.Container{{
8643 Name: "life-123",
8644 Image: "image",
8645 Lifecycle: &core.Lifecycle{
8646 PreStop: &core.LifecycleHandler{
8647 HTTPGet: &core.HTTPGetAction{
8648 Port: intstr.FromInt32(80),
8649 Scheme: "HTTP",
8650 },
8651 },
8652 },
8653 ImagePullPolicy: "IfNotPresent",
8654 TerminationMessagePolicy: "File",
8655 }},
8656 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}},
8657 }, {
8658 "invalid lifecycle, no http port.",
8659 line(),
8660 []core.Container{{
8661 Name: "life-123",
8662 Image: "image",
8663 Lifecycle: &core.Lifecycle{
8664 PreStop: &core.LifecycleHandler{
8665 HTTPGet: &core.HTTPGetAction{
8666 Path: "/",
8667 Scheme: "HTTP",
8668 },
8669 },
8670 },
8671 ImagePullPolicy: "IfNotPresent",
8672 TerminationMessagePolicy: "File",
8673 }},
8674 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}},
8675 }, {
8676 "invalid lifecycle, no http scheme.",
8677 line(),
8678 []core.Container{{
8679 Name: "life-123",
8680 Image: "image",
8681 Lifecycle: &core.Lifecycle{
8682 PreStop: &core.LifecycleHandler{
8683 HTTPGet: &core.HTTPGetAction{
8684 Path: "/",
8685 Port: intstr.FromInt32(80),
8686 },
8687 },
8688 },
8689 ImagePullPolicy: "IfNotPresent",
8690 TerminationMessagePolicy: "File",
8691 }},
8692 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}},
8693 }, {
8694 "invalid lifecycle, no tcp socket port.",
8695 line(),
8696 []core.Container{{
8697 Name: "life-123",
8698 Image: "image",
8699 Lifecycle: &core.Lifecycle{
8700 PreStop: &core.LifecycleHandler{
8701 TCPSocket: &core.TCPSocketAction{},
8702 },
8703 },
8704 ImagePullPolicy: "IfNotPresent",
8705 TerminationMessagePolicy: "File",
8706 }},
8707 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
8708 }, {
8709 "invalid lifecycle, zero tcp socket port.",
8710 line(),
8711 []core.Container{{
8712 Name: "life-123",
8713 Image: "image",
8714 Lifecycle: &core.Lifecycle{
8715 PreStop: &core.LifecycleHandler{
8716 TCPSocket: &core.TCPSocketAction{
8717 Port: intstr.FromInt32(0),
8718 },
8719 },
8720 },
8721 ImagePullPolicy: "IfNotPresent",
8722 TerminationMessagePolicy: "File",
8723 }},
8724 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
8725 }, {
8726 "invalid lifecycle, no action.",
8727 line(),
8728 []core.Container{{
8729 Name: "life-123",
8730 Image: "image",
8731 Lifecycle: &core.Lifecycle{
8732 PreStop: &core.LifecycleHandler{},
8733 },
8734 ImagePullPolicy: "IfNotPresent",
8735 TerminationMessagePolicy: "File",
8736 }},
8737 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}},
8738 }, {
8739 "invalid readiness probe, terminationGracePeriodSeconds set.",
8740 line(),
8741 []core.Container{{
8742 Name: "life-123",
8743 Image: "image",
8744 ReadinessProbe: &core.Probe{
8745 ProbeHandler: core.ProbeHandler{
8746 TCPSocket: &core.TCPSocketAction{
8747 Port: intstr.FromInt32(80),
8748 },
8749 },
8750 TerminationGracePeriodSeconds: utilpointer.Int64(10),
8751 },
8752 ImagePullPolicy: "IfNotPresent",
8753 TerminationMessagePolicy: "File",
8754 }},
8755 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}},
8756 }, {
8757 "invalid liveness probe, no tcp socket port.",
8758 line(),
8759 []core.Container{{
8760 Name: "live-123",
8761 Image: "image",
8762 LivenessProbe: &core.Probe{
8763 ProbeHandler: core.ProbeHandler{
8764 TCPSocket: &core.TCPSocketAction{},
8765 },
8766 SuccessThreshold: 1,
8767 },
8768 ImagePullPolicy: "IfNotPresent",
8769 TerminationMessagePolicy: "File",
8770 }},
8771 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}},
8772 }, {
8773 "invalid liveness probe, no action.",
8774 line(),
8775 []core.Container{{
8776 Name: "live-123",
8777 Image: "image",
8778 LivenessProbe: &core.Probe{
8779 ProbeHandler: core.ProbeHandler{},
8780 SuccessThreshold: 1,
8781 },
8782 ImagePullPolicy: "IfNotPresent",
8783 TerminationMessagePolicy: "File",
8784 }},
8785 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}},
8786 }, {
8787 "invalid liveness probe, successThreshold != 1",
8788 line(),
8789 []core.Container{{
8790 Name: "live-123",
8791 Image: "image",
8792 LivenessProbe: &core.Probe{
8793 ProbeHandler: core.ProbeHandler{
8794 TCPSocket: &core.TCPSocketAction{
8795 Port: intstr.FromInt32(80),
8796 },
8797 },
8798 SuccessThreshold: 2,
8799 },
8800 ImagePullPolicy: "IfNotPresent",
8801 TerminationMessagePolicy: "File",
8802 }},
8803 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}},
8804 }, {
8805 "invalid startup probe, successThreshold != 1",
8806 line(),
8807 []core.Container{{
8808 Name: "startup-123",
8809 Image: "image",
8810 StartupProbe: &core.Probe{
8811 ProbeHandler: core.ProbeHandler{
8812 TCPSocket: &core.TCPSocketAction{
8813 Port: intstr.FromInt32(80),
8814 },
8815 },
8816 SuccessThreshold: 2,
8817 },
8818 ImagePullPolicy: "IfNotPresent",
8819 TerminationMessagePolicy: "File",
8820 }},
8821 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}},
8822 }, {
8823 "invalid liveness probe, negative numbers",
8824 line(),
8825 []core.Container{{
8826 Name: "live-123",
8827 Image: "image",
8828 LivenessProbe: &core.Probe{
8829 ProbeHandler: core.ProbeHandler{
8830 TCPSocket: &core.TCPSocketAction{
8831 Port: intstr.FromInt32(80),
8832 },
8833 },
8834 InitialDelaySeconds: -1,
8835 TimeoutSeconds: -1,
8836 PeriodSeconds: -1,
8837 SuccessThreshold: -1,
8838 FailureThreshold: -1,
8839 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
8840 },
8841 ImagePullPolicy: "IfNotPresent",
8842 TerminationMessagePolicy: "File",
8843 }},
8844 field.ErrorList{
8845 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"},
8846 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"},
8847 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"},
8848 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
8849 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"},
8850 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"},
8851 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
8852 },
8853 }, {
8854 "invalid readiness probe, negative numbers",
8855 line(),
8856 []core.Container{{
8857 Name: "ready-123",
8858 Image: "image",
8859 ReadinessProbe: &core.Probe{
8860 ProbeHandler: core.ProbeHandler{
8861 TCPSocket: &core.TCPSocketAction{
8862 Port: intstr.FromInt32(80),
8863 },
8864 },
8865 InitialDelaySeconds: -1,
8866 TimeoutSeconds: -1,
8867 PeriodSeconds: -1,
8868 SuccessThreshold: -1,
8869 FailureThreshold: -1,
8870 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
8871 },
8872 ImagePullPolicy: "IfNotPresent",
8873 TerminationMessagePolicy: "File",
8874 }},
8875 field.ErrorList{
8876 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"},
8877 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"},
8878 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"},
8879 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"},
8880 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"},
8881
8882
8883 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
8884
8885 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
8886 },
8887 }, {
8888 "invalid startup probe, negative numbers",
8889 line(),
8890 []core.Container{{
8891 Name: "startup-123",
8892 Image: "image",
8893 StartupProbe: &core.Probe{
8894 ProbeHandler: core.ProbeHandler{
8895 TCPSocket: &core.TCPSocketAction{
8896 Port: intstr.FromInt32(80),
8897 },
8898 },
8899 InitialDelaySeconds: -1,
8900 TimeoutSeconds: -1,
8901 PeriodSeconds: -1,
8902 SuccessThreshold: -1,
8903 FailureThreshold: -1,
8904 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
8905 },
8906 ImagePullPolicy: "IfNotPresent",
8907 TerminationMessagePolicy: "File",
8908 }},
8909 field.ErrorList{
8910 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"},
8911 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"},
8912 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"},
8913 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
8914 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"},
8915 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"},
8916 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
8917 },
8918 }, {
8919 "invalid message termination policy",
8920 line(),
8921 []core.Container{{
8922 Name: "life-123",
8923 Image: "image",
8924 ImagePullPolicy: "IfNotPresent",
8925 TerminationMessagePolicy: "Unknown",
8926 }},
8927 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}},
8928 }, {
8929 "empty message termination policy",
8930 line(),
8931 []core.Container{{
8932 Name: "life-123",
8933 Image: "image",
8934 ImagePullPolicy: "IfNotPresent",
8935 TerminationMessagePolicy: "",
8936 }},
8937 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}},
8938 }, {
8939 "privilege disabled",
8940 line(),
8941 []core.Container{{
8942 Name: "abc",
8943 Image: "image",
8944 SecurityContext: fakeValidSecurityContext(true),
8945 ImagePullPolicy: "IfNotPresent",
8946 TerminationMessagePolicy: "File",
8947 }},
8948 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}},
8949 }, {
8950 "invalid compute resource",
8951 line(),
8952 []core.Container{{
8953 Name: "abc-123",
8954 Image: "image",
8955 Resources: core.ResourceRequirements{
8956 Limits: core.ResourceList{
8957 "disk": resource.MustParse("10G"),
8958 },
8959 },
8960 ImagePullPolicy: "IfNotPresent",
8961 TerminationMessagePolicy: "File",
8962 }},
8963 field.ErrorList{
8964 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
8965 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
8966 },
8967 }, {
8968 "Resource CPU invalid",
8969 line(),
8970 []core.Container{{
8971 Name: "abc-123",
8972 Image: "image",
8973 Resources: core.ResourceRequirements{
8974 Limits: getResourceLimits("-10", "0"),
8975 },
8976 ImagePullPolicy: "IfNotPresent",
8977 TerminationMessagePolicy: "File",
8978 }},
8979 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}},
8980 }, {
8981 "Resource Requests CPU invalid",
8982 line(),
8983 []core.Container{{
8984 Name: "abc-123",
8985 Image: "image",
8986 Resources: core.ResourceRequirements{
8987 Requests: getResourceLimits("-10", "0"),
8988 },
8989 ImagePullPolicy: "IfNotPresent",
8990 TerminationMessagePolicy: "File",
8991 }},
8992 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}},
8993 }, {
8994 "Resource Memory invalid",
8995 line(),
8996 []core.Container{{
8997 Name: "abc-123",
8998 Image: "image",
8999 Resources: core.ResourceRequirements{
9000 Limits: getResourceLimits("0", "-10"),
9001 },
9002 ImagePullPolicy: "IfNotPresent",
9003 TerminationMessagePolicy: "File",
9004 }},
9005 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}},
9006 }, {
9007 "Request limit simple invalid",
9008 line(),
9009 []core.Container{{
9010 Name: "abc-123",
9011 Image: "image",
9012 Resources: core.ResourceRequirements{
9013 Limits: getResourceLimits("5", "3"),
9014 Requests: getResourceLimits("6", "3"),
9015 },
9016 ImagePullPolicy: "IfNotPresent",
9017 TerminationMessagePolicy: "File",
9018 }},
9019 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
9020 }, {
9021 "Invalid storage limit request",
9022 line(),
9023 []core.Container{{
9024 Name: "abc-123",
9025 Image: "image",
9026 Resources: core.ResourceRequirements{
9027 Limits: core.ResourceList{
9028 core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI),
9029 },
9030 },
9031 ImagePullPolicy: "IfNotPresent",
9032 TerminationMessagePolicy: "File",
9033 }},
9034 field.ErrorList{
9035 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
9036 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
9037 },
9038 }, {
9039 "CPU request limit multiple invalid",
9040 line(),
9041 []core.Container{{
9042 Name: "abc-123",
9043 Image: "image",
9044 Resources: core.ResourceRequirements{
9045 Limits: getResourceLimits("5", "3"),
9046 Requests: getResourceLimits("6", "3"),
9047 },
9048 ImagePullPolicy: "IfNotPresent",
9049 TerminationMessagePolicy: "File",
9050 }},
9051 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
9052 }, {
9053 "Memory request limit multiple invalid",
9054 line(),
9055 []core.Container{{
9056 Name: "abc-123",
9057 Image: "image",
9058 Resources: core.ResourceRequirements{
9059 Limits: getResourceLimits("5", "3"),
9060 Requests: getResourceLimits("5", "4"),
9061 },
9062 ImagePullPolicy: "IfNotPresent",
9063 TerminationMessagePolicy: "File",
9064 }},
9065 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
9066 }, {
9067 "Invalid env from",
9068 line(),
9069 []core.Container{{
9070 Name: "env-from-source",
9071 Image: "image",
9072 ImagePullPolicy: "IfNotPresent",
9073 TerminationMessagePolicy: "File",
9074 EnvFrom: []core.EnvFromSource{{
9075 ConfigMapRef: &core.ConfigMapEnvSource{
9076 LocalObjectReference: core.LocalObjectReference{
9077 Name: "$%^&*#",
9078 },
9079 },
9080 }},
9081 }},
9082 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}},
9083 }, {
9084 "Unsupported resize policy for memory",
9085 line(),
9086 []core.Container{{
9087 Name: "resize-policy-mem-invalid",
9088 Image: "image",
9089 ImagePullPolicy: "IfNotPresent",
9090 TerminationMessagePolicy: "File",
9091 ResizePolicy: []core.ContainerResizePolicy{
9092 {ResourceName: "memory", RestartPolicy: "RestartContainerrrr"},
9093 },
9094 }},
9095 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
9096 }, {
9097 "Unsupported resize policy for CPU",
9098 line(),
9099 []core.Container{{
9100 Name: "resize-policy-cpu-invalid",
9101 Image: "image",
9102 ImagePullPolicy: "IfNotPresent",
9103 TerminationMessagePolicy: "File",
9104 ResizePolicy: []core.ContainerResizePolicy{
9105 {ResourceName: "cpu", RestartPolicy: "RestartNotRequired"},
9106 },
9107 }},
9108 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
9109 }, {
9110 "Forbidden RestartPolicy: Always",
9111 line(),
9112 []core.Container{{
9113 Name: "foo",
9114 Image: "image",
9115 ImagePullPolicy: "IfNotPresent",
9116 TerminationMessagePolicy: "File",
9117 RestartPolicy: &containerRestartPolicyAlways,
9118 }},
9119 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
9120 }, {
9121 "Forbidden RestartPolicy: OnFailure",
9122 line(),
9123 []core.Container{{
9124 Name: "foo",
9125 Image: "image",
9126 ImagePullPolicy: "IfNotPresent",
9127 TerminationMessagePolicy: "File",
9128 RestartPolicy: &containerRestartPolicyOnFailure,
9129 }},
9130 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
9131 }, {
9132 "Forbidden RestartPolicy: Never",
9133 line(),
9134 []core.Container{{
9135 Name: "foo",
9136 Image: "image",
9137 ImagePullPolicy: "IfNotPresent",
9138 TerminationMessagePolicy: "File",
9139 RestartPolicy: &containerRestartPolicyNever,
9140 }},
9141 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
9142 }, {
9143 "Forbidden RestartPolicy: invalid",
9144 line(),
9145 []core.Container{{
9146 Name: "foo",
9147 Image: "image",
9148 ImagePullPolicy: "IfNotPresent",
9149 TerminationMessagePolicy: "File",
9150 RestartPolicy: &containerRestartPolicyInvalid,
9151 }},
9152 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
9153 }, {
9154 "Forbidden RestartPolicy: empty",
9155 line(),
9156 []core.Container{{
9157 Name: "foo",
9158 Image: "image",
9159 ImagePullPolicy: "IfNotPresent",
9160 TerminationMessagePolicy: "File",
9161 RestartPolicy: &containerRestartPolicyEmpty,
9162 }},
9163 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
9164 },
9165 }
9166
9167 for _, tc := range errorCases {
9168 t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
9169 errs := validateContainers(tc.containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("containers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
9170 if len(errs) == 0 {
9171 t.Fatal("expected error but received none")
9172 }
9173
9174 if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
9175 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
9176 t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
9177 }
9178 })
9179 }
9180 }
9181
9182 func TestValidateInitContainers(t *testing.T) {
9183 volumeDevices := make(map[string]core.VolumeSource)
9184 capabilities.SetForTests(capabilities.Capabilities{
9185 AllowPrivileged: true,
9186 })
9187
9188 containers := []core.Container{{
9189 Name: "app",
9190 Image: "nginx",
9191 ImagePullPolicy: "IfNotPresent",
9192 TerminationMessagePolicy: "File",
9193 },
9194 }
9195
9196 successCase := []core.Container{{
9197 Name: "container-1-same-host-port-different-protocol",
9198 Image: "image",
9199 Ports: []core.ContainerPort{
9200 {ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
9201 {ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
9202 },
9203 ImagePullPolicy: "IfNotPresent",
9204 TerminationMessagePolicy: "File",
9205 }, {
9206 Name: "container-2-same-host-port-different-protocol",
9207 Image: "image",
9208 Ports: []core.ContainerPort{
9209 {ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
9210 {ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
9211 },
9212 ImagePullPolicy: "IfNotPresent",
9213 TerminationMessagePolicy: "File",
9214 }, {
9215 Name: "container-3-restart-always-with-lifecycle-hook-and-probes",
9216 Image: "image",
9217 ImagePullPolicy: "IfNotPresent",
9218 TerminationMessagePolicy: "File",
9219 RestartPolicy: &containerRestartPolicyAlways,
9220 Lifecycle: &core.Lifecycle{
9221 PostStart: &core.LifecycleHandler{
9222 Exec: &core.ExecAction{
9223 Command: []string{"echo", "post start"},
9224 },
9225 },
9226 PreStop: &core.LifecycleHandler{
9227 Exec: &core.ExecAction{
9228 Command: []string{"echo", "pre stop"},
9229 },
9230 },
9231 },
9232 LivenessProbe: &core.Probe{
9233 ProbeHandler: core.ProbeHandler{
9234 TCPSocket: &core.TCPSocketAction{
9235 Port: intstr.FromInt32(80),
9236 },
9237 },
9238 SuccessThreshold: 1,
9239 },
9240 ReadinessProbe: &core.Probe{
9241 ProbeHandler: core.ProbeHandler{
9242 TCPSocket: &core.TCPSocketAction{
9243 Port: intstr.FromInt32(80),
9244 },
9245 },
9246 },
9247 StartupProbe: &core.Probe{
9248 ProbeHandler: core.ProbeHandler{
9249 TCPSocket: &core.TCPSocketAction{
9250 Port: intstr.FromInt32(80),
9251 },
9252 },
9253 SuccessThreshold: 1,
9254 },
9255 },
9256 }
9257 var PodRestartPolicy core.RestartPolicy = "Never"
9258 if errs := validateInitContainers(successCase, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
9259 t.Errorf("expected success: %v", errs)
9260 }
9261
9262 capabilities.SetForTests(capabilities.Capabilities{
9263 AllowPrivileged: false,
9264 })
9265 errorCases := []struct {
9266 title, line string
9267 initContainers []core.Container
9268 expectedErrors field.ErrorList
9269 }{{
9270 "empty name",
9271 line(),
9272 []core.Container{{
9273 Name: "",
9274 Image: "image",
9275 ImagePullPolicy: "IfNotPresent",
9276 TerminationMessagePolicy: "File",
9277 }},
9278 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}},
9279 }, {
9280 "name collision with regular container",
9281 line(),
9282 []core.Container{{
9283 Name: "app",
9284 Image: "image",
9285 ImagePullPolicy: "IfNotPresent",
9286 TerminationMessagePolicy: "File",
9287 }},
9288 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}},
9289 }, {
9290 "invalid termination message policy",
9291 line(),
9292 []core.Container{{
9293 Name: "init",
9294 Image: "image",
9295 ImagePullPolicy: "IfNotPresent",
9296 TerminationMessagePolicy: "Unknown",
9297 }},
9298 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}},
9299 }, {
9300 "duplicate names",
9301 line(),
9302 []core.Container{{
9303 Name: "init",
9304 Image: "image",
9305 ImagePullPolicy: "IfNotPresent",
9306 TerminationMessagePolicy: "File",
9307 }, {
9308 Name: "init",
9309 Image: "image",
9310 ImagePullPolicy: "IfNotPresent",
9311 TerminationMessagePolicy: "File",
9312 }},
9313 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}},
9314 }, {
9315 "duplicate ports",
9316 line(),
9317 []core.Container{{
9318 Name: "abc",
9319 Image: "image",
9320 Ports: []core.ContainerPort{{
9321 ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
9322 }, {
9323 ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
9324 }},
9325 ImagePullPolicy: "IfNotPresent",
9326 TerminationMessagePolicy: "File",
9327 }},
9328 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}},
9329 }, {
9330 "uses disallowed field: Lifecycle",
9331 line(),
9332 []core.Container{{
9333 Name: "debug",
9334 Image: "image",
9335 ImagePullPolicy: "IfNotPresent",
9336 TerminationMessagePolicy: "File",
9337 Lifecycle: &core.Lifecycle{
9338 PreStop: &core.LifecycleHandler{
9339 Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
9340 },
9341 },
9342 }},
9343 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}},
9344 }, {
9345 "uses disallowed field: LivenessProbe",
9346 line(),
9347 []core.Container{{
9348 Name: "debug",
9349 Image: "image",
9350 ImagePullPolicy: "IfNotPresent",
9351 TerminationMessagePolicy: "File",
9352 LivenessProbe: &core.Probe{
9353 ProbeHandler: core.ProbeHandler{
9354 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
9355 },
9356 SuccessThreshold: 1,
9357 },
9358 }},
9359 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}},
9360 }, {
9361 "uses disallowed field: ReadinessProbe",
9362 line(),
9363 []core.Container{{
9364 Name: "debug",
9365 Image: "image",
9366 ImagePullPolicy: "IfNotPresent",
9367 TerminationMessagePolicy: "File",
9368 ReadinessProbe: &core.Probe{
9369 ProbeHandler: core.ProbeHandler{
9370 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
9371 },
9372 },
9373 }},
9374 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}},
9375 }, {
9376 "Container uses disallowed field: StartupProbe",
9377 line(),
9378 []core.Container{{
9379 Name: "debug",
9380 Image: "image",
9381 ImagePullPolicy: "IfNotPresent",
9382 TerminationMessagePolicy: "File",
9383 StartupProbe: &core.Probe{
9384 ProbeHandler: core.ProbeHandler{
9385 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
9386 },
9387 SuccessThreshold: 1,
9388 },
9389 }},
9390 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
9391 }, {
9392 "Disallowed field with other errors should only return a single Forbidden",
9393 line(),
9394 []core.Container{{
9395 Name: "debug",
9396 Image: "image",
9397 ImagePullPolicy: "IfNotPresent",
9398 TerminationMessagePolicy: "File",
9399 StartupProbe: &core.Probe{
9400 ProbeHandler: core.ProbeHandler{
9401 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
9402 },
9403 InitialDelaySeconds: -1,
9404 TimeoutSeconds: -1,
9405 PeriodSeconds: -1,
9406 SuccessThreshold: -1,
9407 FailureThreshold: -1,
9408 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
9409 },
9410 }},
9411 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
9412 }, {
9413 "Not supported RestartPolicy: OnFailure",
9414 line(),
9415 []core.Container{{
9416 Name: "init",
9417 Image: "image",
9418 ImagePullPolicy: "IfNotPresent",
9419 TerminationMessagePolicy: "File",
9420 RestartPolicy: &containerRestartPolicyOnFailure,
9421 }},
9422 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyOnFailure}},
9423 }, {
9424 "Not supported RestartPolicy: Never",
9425 line(),
9426 []core.Container{{
9427 Name: "init",
9428 Image: "image",
9429 ImagePullPolicy: "IfNotPresent",
9430 TerminationMessagePolicy: "File",
9431 RestartPolicy: &containerRestartPolicyNever,
9432 }},
9433 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyNever}},
9434 }, {
9435 "Not supported RestartPolicy: invalid",
9436 line(),
9437 []core.Container{{
9438 Name: "init",
9439 Image: "image",
9440 ImagePullPolicy: "IfNotPresent",
9441 TerminationMessagePolicy: "File",
9442 RestartPolicy: &containerRestartPolicyInvalid,
9443 }},
9444 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyInvalid}},
9445 }, {
9446 "Not supported RestartPolicy: empty",
9447 line(),
9448 []core.Container{{
9449 Name: "init",
9450 Image: "image",
9451 ImagePullPolicy: "IfNotPresent",
9452 TerminationMessagePolicy: "File",
9453 RestartPolicy: &containerRestartPolicyEmpty,
9454 }},
9455 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyEmpty}},
9456 }, {
9457 "invalid startup probe in restartable container, successThreshold != 1",
9458 line(),
9459 []core.Container{{
9460 Name: "restartable-init",
9461 Image: "image",
9462 ImagePullPolicy: "IfNotPresent",
9463 TerminationMessagePolicy: "File",
9464 RestartPolicy: &containerRestartPolicyAlways,
9465 StartupProbe: &core.Probe{
9466 ProbeHandler: core.ProbeHandler{
9467 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
9468 },
9469 SuccessThreshold: 2,
9470 },
9471 }},
9472 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}},
9473 }, {
9474 "invalid readiness probe, terminationGracePeriodSeconds set.",
9475 line(),
9476 []core.Container{{
9477 Name: "life-123",
9478 Image: "image",
9479 ImagePullPolicy: "IfNotPresent",
9480 TerminationMessagePolicy: "File",
9481 RestartPolicy: &containerRestartPolicyAlways,
9482 ReadinessProbe: &core.Probe{
9483 ProbeHandler: core.ProbeHandler{
9484 TCPSocket: &core.TCPSocketAction{
9485 Port: intstr.FromInt32(80),
9486 },
9487 },
9488 TerminationGracePeriodSeconds: utilpointer.Int64(10),
9489 },
9490 }},
9491 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].readinessProbe.terminationGracePeriodSeconds", BadValue: utilpointer.Int64(10)}},
9492 }, {
9493 "invalid liveness probe, successThreshold != 1",
9494 line(),
9495 []core.Container{{
9496 Name: "live-123",
9497 Image: "image",
9498 ImagePullPolicy: "IfNotPresent",
9499 TerminationMessagePolicy: "File",
9500 RestartPolicy: &containerRestartPolicyAlways,
9501 LivenessProbe: &core.Probe{
9502 ProbeHandler: core.ProbeHandler{
9503 TCPSocket: &core.TCPSocketAction{
9504 Port: intstr.FromInt32(80),
9505 },
9506 },
9507 SuccessThreshold: 2,
9508 },
9509 }},
9510 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].livenessProbe.successThreshold", BadValue: int32(2)}},
9511 }, {
9512 "invalid lifecycle, no exec command.",
9513 line(),
9514 []core.Container{{
9515 Name: "life-123",
9516 Image: "image",
9517 ImagePullPolicy: "IfNotPresent",
9518 TerminationMessagePolicy: "File",
9519 RestartPolicy: &containerRestartPolicyAlways,
9520 Lifecycle: &core.Lifecycle{
9521 PreStop: &core.LifecycleHandler{
9522 Exec: &core.ExecAction{},
9523 },
9524 },
9525 }},
9526 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.exec.command", BadValue: ""}},
9527 }, {
9528 "invalid lifecycle, no http path.",
9529 line(),
9530 []core.Container{{
9531 Name: "life-123",
9532 Image: "image",
9533 ImagePullPolicy: "IfNotPresent",
9534 TerminationMessagePolicy: "File",
9535 RestartPolicy: &containerRestartPolicyAlways,
9536 Lifecycle: &core.Lifecycle{
9537 PreStop: &core.LifecycleHandler{
9538 HTTPGet: &core.HTTPGetAction{
9539 Port: intstr.FromInt32(80),
9540 Scheme: "HTTP",
9541 },
9542 },
9543 },
9544 }},
9545 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.httpGet.path", BadValue: ""}},
9546 }, {
9547 "invalid lifecycle, no http port.",
9548 line(),
9549 []core.Container{{
9550 Name: "life-123",
9551 Image: "image",
9552 ImagePullPolicy: "IfNotPresent",
9553 TerminationMessagePolicy: "File",
9554 RestartPolicy: &containerRestartPolicyAlways,
9555 Lifecycle: &core.Lifecycle{
9556 PreStop: &core.LifecycleHandler{
9557 HTTPGet: &core.HTTPGetAction{
9558 Path: "/",
9559 Scheme: "HTTP",
9560 },
9561 },
9562 },
9563 }},
9564 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.httpGet.port", BadValue: 0}},
9565 }, {
9566 "invalid lifecycle, no http scheme.",
9567 line(),
9568 []core.Container{{
9569 Name: "life-123",
9570 Image: "image",
9571 ImagePullPolicy: "IfNotPresent",
9572 TerminationMessagePolicy: "File",
9573 RestartPolicy: &containerRestartPolicyAlways,
9574 Lifecycle: &core.Lifecycle{
9575 PreStop: &core.LifecycleHandler{
9576 HTTPGet: &core.HTTPGetAction{
9577 Path: "/",
9578 Port: intstr.FromInt32(80),
9579 },
9580 },
9581 },
9582 }},
9583 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].lifecycle.preStop.httpGet.scheme", BadValue: core.URIScheme("")}},
9584 }, {
9585 "invalid lifecycle, no tcp socket port.",
9586 line(),
9587 []core.Container{{
9588 Name: "life-123",
9589 Image: "image",
9590 ImagePullPolicy: "IfNotPresent",
9591 TerminationMessagePolicy: "File",
9592 RestartPolicy: &containerRestartPolicyAlways,
9593 Lifecycle: &core.Lifecycle{
9594 PreStop: &core.LifecycleHandler{
9595 TCPSocket: &core.TCPSocketAction{},
9596 },
9597 },
9598 }},
9599 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
9600 }, {
9601 "invalid lifecycle, zero tcp socket port.",
9602 line(),
9603 []core.Container{{
9604 Name: "life-123",
9605 Image: "image",
9606 ImagePullPolicy: "IfNotPresent",
9607 TerminationMessagePolicy: "File",
9608 RestartPolicy: &containerRestartPolicyAlways,
9609 Lifecycle: &core.Lifecycle{
9610 PreStop: &core.LifecycleHandler{
9611 TCPSocket: &core.TCPSocketAction{
9612 Port: intstr.FromInt32(0),
9613 },
9614 },
9615 },
9616 }},
9617 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
9618 }, {
9619 "invalid lifecycle, no action.",
9620 line(),
9621 []core.Container{{
9622 Name: "life-123",
9623 Image: "image",
9624 ImagePullPolicy: "IfNotPresent",
9625 TerminationMessagePolicy: "File",
9626 RestartPolicy: &containerRestartPolicyAlways,
9627 Lifecycle: &core.Lifecycle{
9628 PreStop: &core.LifecycleHandler{},
9629 },
9630 }},
9631 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
9632 },
9633 }
9634
9635 for _, tc := range errorCases {
9636 t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
9637 errs := validateInitContainers(tc.initContainers, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("initContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
9638 if len(errs) == 0 {
9639 t.Fatal("expected error but received none")
9640 }
9641
9642 if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "Detail")); diff != "" {
9643 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
9644 t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
9645 }
9646 })
9647 }
9648 }
9649
9650 func TestValidateRestartPolicy(t *testing.T) {
9651 successCases := []core.RestartPolicy{
9652 core.RestartPolicyAlways,
9653 core.RestartPolicyOnFailure,
9654 core.RestartPolicyNever,
9655 }
9656 for _, policy := range successCases {
9657 if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
9658 t.Errorf("expected success: %v", errs)
9659 }
9660 }
9661
9662 errorCases := []core.RestartPolicy{"", "newpolicy"}
9663
9664 for k, policy := range errorCases {
9665 if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
9666 t.Errorf("expected failure for %d", k)
9667 }
9668 }
9669 }
9670
9671 func TestValidateDNSPolicy(t *testing.T) {
9672 successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSClusterFirstWithHostNet, core.DNSNone}
9673 for _, policy := range successCases {
9674 if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
9675 t.Errorf("expected success: %v", errs)
9676 }
9677 }
9678
9679 errorCases := []core.DNSPolicy{core.DNSPolicy("invalid"), core.DNSPolicy("")}
9680 for _, policy := range errorCases {
9681 if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
9682 t.Errorf("expected failure for %v", policy)
9683 }
9684 }
9685 }
9686
9687 func TestValidatePodDNSConfig(t *testing.T) {
9688 generateTestSearchPathFunc := func(numChars int) string {
9689 res := ""
9690 for i := 0; i < numChars; i++ {
9691 res = res + "a"
9692 }
9693 return res
9694 }
9695 testOptionValue := "2"
9696 testDNSNone := core.DNSNone
9697 testDNSClusterFirst := core.DNSClusterFirst
9698
9699 testCases := []struct {
9700 desc string
9701 dnsConfig *core.PodDNSConfig
9702 dnsPolicy *core.DNSPolicy
9703 opts PodValidationOptions
9704 expectedError bool
9705 }{{
9706 desc: "valid: empty DNSConfig",
9707 dnsConfig: &core.PodDNSConfig{},
9708 expectedError: false,
9709 }, {
9710 desc: "valid: 1 option",
9711 dnsConfig: &core.PodDNSConfig{
9712 Options: []core.PodDNSConfigOption{
9713 {Name: "ndots", Value: &testOptionValue},
9714 },
9715 },
9716 expectedError: false,
9717 }, {
9718 desc: "valid: 1 nameserver",
9719 dnsConfig: &core.PodDNSConfig{
9720 Nameservers: []string{"127.0.0.1"},
9721 },
9722 expectedError: false,
9723 }, {
9724 desc: "valid: DNSNone with 1 nameserver",
9725 dnsConfig: &core.PodDNSConfig{
9726 Nameservers: []string{"127.0.0.1"},
9727 },
9728 dnsPolicy: &testDNSNone,
9729 expectedError: false,
9730 }, {
9731 desc: "valid: 1 search path",
9732 dnsConfig: &core.PodDNSConfig{
9733 Searches: []string{"custom"},
9734 },
9735 expectedError: false,
9736 }, {
9737 desc: "valid: 1 search path with trailing period",
9738 dnsConfig: &core.PodDNSConfig{
9739 Searches: []string{"custom."},
9740 },
9741 expectedError: false,
9742 }, {
9743 desc: "valid: 3 nameservers and 6 search paths(legacy)",
9744 dnsConfig: &core.PodDNSConfig{
9745 Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
9746 Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."},
9747 },
9748 expectedError: false,
9749 }, {
9750 desc: "valid: 3 nameservers and 32 search paths",
9751 dnsConfig: &core.PodDNSConfig{
9752 Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
9753 Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"},
9754 },
9755 expectedError: false,
9756 }, {
9757 desc: "valid: 256 characters in search path list(legacy)",
9758 dnsConfig: &core.PodDNSConfig{
9759
9760 Searches: []string{
9761 generateTestSearchPathFunc(1),
9762 generateTestSearchPathFunc(50),
9763 generateTestSearchPathFunc(50),
9764 generateTestSearchPathFunc(50),
9765 generateTestSearchPathFunc(50),
9766 generateTestSearchPathFunc(50),
9767 },
9768 },
9769 expectedError: false,
9770 }, {
9771 desc: "valid: 2048 characters in search path list",
9772 dnsConfig: &core.PodDNSConfig{
9773
9774 Searches: []string{
9775 generateTestSearchPathFunc(64),
9776 generateTestSearchPathFunc(63),
9777 generateTestSearchPathFunc(63),
9778 generateTestSearchPathFunc(63),
9779 generateTestSearchPathFunc(63),
9780 generateTestSearchPathFunc(63),
9781 generateTestSearchPathFunc(63),
9782 generateTestSearchPathFunc(63),
9783 generateTestSearchPathFunc(63),
9784 generateTestSearchPathFunc(63),
9785 generateTestSearchPathFunc(63),
9786 generateTestSearchPathFunc(63),
9787 generateTestSearchPathFunc(63),
9788 generateTestSearchPathFunc(63),
9789 generateTestSearchPathFunc(63),
9790 generateTestSearchPathFunc(63),
9791 generateTestSearchPathFunc(63),
9792 generateTestSearchPathFunc(63),
9793 generateTestSearchPathFunc(63),
9794 generateTestSearchPathFunc(63),
9795 generateTestSearchPathFunc(63),
9796 generateTestSearchPathFunc(63),
9797 generateTestSearchPathFunc(63),
9798 generateTestSearchPathFunc(63),
9799 generateTestSearchPathFunc(63),
9800 generateTestSearchPathFunc(63),
9801 generateTestSearchPathFunc(63),
9802 generateTestSearchPathFunc(63),
9803 generateTestSearchPathFunc(63),
9804 generateTestSearchPathFunc(63),
9805 generateTestSearchPathFunc(63),
9806 generateTestSearchPathFunc(63),
9807 },
9808 },
9809 expectedError: false,
9810 }, {
9811 desc: "valid: ipv6 nameserver",
9812 dnsConfig: &core.PodDNSConfig{
9813 Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"},
9814 },
9815 expectedError: false,
9816 }, {
9817 desc: "invalid: 4 nameservers",
9818 dnsConfig: &core.PodDNSConfig{
9819 Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
9820 },
9821 expectedError: true,
9822 }, {
9823 desc: "valid: 7 search paths",
9824 dnsConfig: &core.PodDNSConfig{
9825 Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"},
9826 },
9827 }, {
9828 desc: "invalid: 33 search paths",
9829 dnsConfig: &core.PodDNSConfig{
9830 Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"},
9831 },
9832 expectedError: true,
9833 }, {
9834 desc: "valid: 257 characters in search path list",
9835 dnsConfig: &core.PodDNSConfig{
9836
9837 Searches: []string{
9838 generateTestSearchPathFunc(2),
9839 generateTestSearchPathFunc(50),
9840 generateTestSearchPathFunc(50),
9841 generateTestSearchPathFunc(50),
9842 generateTestSearchPathFunc(50),
9843 generateTestSearchPathFunc(50),
9844 },
9845 },
9846 }, {
9847 desc: "invalid: 2049 characters in search path list",
9848 dnsConfig: &core.PodDNSConfig{
9849
9850 Searches: []string{
9851 generateTestSearchPathFunc(65),
9852 generateTestSearchPathFunc(63),
9853 generateTestSearchPathFunc(63),
9854 generateTestSearchPathFunc(63),
9855 generateTestSearchPathFunc(63),
9856 generateTestSearchPathFunc(63),
9857 generateTestSearchPathFunc(63),
9858 generateTestSearchPathFunc(63),
9859 generateTestSearchPathFunc(63),
9860 generateTestSearchPathFunc(63),
9861 generateTestSearchPathFunc(63),
9862 generateTestSearchPathFunc(63),
9863 generateTestSearchPathFunc(63),
9864 generateTestSearchPathFunc(63),
9865 generateTestSearchPathFunc(63),
9866 generateTestSearchPathFunc(63),
9867 generateTestSearchPathFunc(63),
9868 generateTestSearchPathFunc(63),
9869 generateTestSearchPathFunc(63),
9870 generateTestSearchPathFunc(63),
9871 generateTestSearchPathFunc(63),
9872 generateTestSearchPathFunc(63),
9873 generateTestSearchPathFunc(63),
9874 generateTestSearchPathFunc(63),
9875 generateTestSearchPathFunc(63),
9876 generateTestSearchPathFunc(63),
9877 generateTestSearchPathFunc(63),
9878 generateTestSearchPathFunc(63),
9879 generateTestSearchPathFunc(63),
9880 generateTestSearchPathFunc(63),
9881 generateTestSearchPathFunc(63),
9882 generateTestSearchPathFunc(63),
9883 },
9884 },
9885 expectedError: true,
9886 }, {
9887 desc: "invalid search path",
9888 dnsConfig: &core.PodDNSConfig{
9889 Searches: []string{"custom?"},
9890 },
9891 expectedError: true,
9892 }, {
9893 desc: "invalid nameserver",
9894 dnsConfig: &core.PodDNSConfig{
9895 Nameservers: []string{"invalid"},
9896 },
9897 expectedError: true,
9898 }, {
9899 desc: "invalid empty option name",
9900 dnsConfig: &core.PodDNSConfig{
9901 Options: []core.PodDNSConfigOption{
9902 {Value: &testOptionValue},
9903 },
9904 },
9905 expectedError: true,
9906 }, {
9907 desc: "invalid: DNSNone with 0 nameserver",
9908 dnsConfig: &core.PodDNSConfig{
9909 Searches: []string{"custom"},
9910 },
9911 dnsPolicy: &testDNSNone,
9912 expectedError: true,
9913 },
9914 }
9915
9916 for _, tc := range testCases {
9917 if tc.dnsPolicy == nil {
9918 tc.dnsPolicy = &testDNSClusterFirst
9919 }
9920
9921 errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"), tc.opts)
9922 if len(errs) != 0 && !tc.expectedError {
9923 t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs)
9924 } else if len(errs) == 0 && tc.expectedError {
9925 t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig)
9926 }
9927 }
9928 }
9929
9930 func TestValidatePodReadinessGates(t *testing.T) {
9931 successCases := []struct {
9932 desc string
9933 readinessGates []core.PodReadinessGate
9934 }{{
9935 "no gate",
9936 []core.PodReadinessGate{},
9937 }, {
9938 "one readiness gate",
9939 []core.PodReadinessGate{{
9940 ConditionType: core.PodConditionType("example.com/condition"),
9941 }},
9942 }, {
9943 "two readiness gates",
9944 []core.PodReadinessGate{{
9945 ConditionType: core.PodConditionType("example.com/condition1"),
9946 }, {
9947 ConditionType: core.PodConditionType("example.com/condition2"),
9948 }},
9949 },
9950 }
9951 for _, tc := range successCases {
9952 if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 {
9953 t.Errorf("expect tc %q to success: %v", tc.desc, errs)
9954 }
9955 }
9956
9957 errorCases := []struct {
9958 desc string
9959 readinessGates []core.PodReadinessGate
9960 }{{
9961 "invalid condition type",
9962 []core.PodReadinessGate{{
9963 ConditionType: core.PodConditionType("invalid/condition/type"),
9964 }},
9965 },
9966 }
9967 for _, tc := range errorCases {
9968 if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 {
9969 t.Errorf("expected tc %q to fail", tc.desc)
9970 }
9971 }
9972 }
9973
9974 func TestValidatePodConditions(t *testing.T) {
9975 successCases := []struct {
9976 desc string
9977 podConditions []core.PodCondition
9978 }{{
9979 "no condition",
9980 []core.PodCondition{},
9981 }, {
9982 "one system condition",
9983 []core.PodCondition{{
9984 Type: core.PodReady,
9985 Status: core.ConditionTrue,
9986 }},
9987 }, {
9988 "one system condition and one custom condition",
9989 []core.PodCondition{{
9990 Type: core.PodReady,
9991 Status: core.ConditionTrue,
9992 }, {
9993 Type: core.PodConditionType("example.com/condition"),
9994 Status: core.ConditionFalse,
9995 }},
9996 }, {
9997 "two custom condition",
9998 []core.PodCondition{{
9999 Type: core.PodConditionType("foobar"),
10000 Status: core.ConditionTrue,
10001 }, {
10002 Type: core.PodConditionType("example.com/condition"),
10003 Status: core.ConditionFalse,
10004 }},
10005 },
10006 }
10007
10008 for _, tc := range successCases {
10009 if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 {
10010 t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs)
10011 }
10012 }
10013
10014 errorCases := []struct {
10015 desc string
10016 podConditions []core.PodCondition
10017 }{{
10018 "one system condition and a invalid custom condition",
10019 []core.PodCondition{{
10020 Type: core.PodReady,
10021 Status: core.ConditionStatus("True"),
10022 }, {
10023 Type: core.PodConditionType("invalid/custom/condition"),
10024 Status: core.ConditionStatus("True"),
10025 }},
10026 },
10027 }
10028 for _, tc := range errorCases {
10029 if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 {
10030 t.Errorf("expected tc %q to fail", tc.desc)
10031 }
10032 }
10033 }
10034
10035 func TestValidatePodSpec(t *testing.T) {
10036 activeDeadlineSeconds := int64(30)
10037 activeDeadlineSecondsMax := int64(math.MaxInt32)
10038
10039 minUserID := int64(0)
10040 maxUserID := int64(2147483647)
10041 minGroupID := int64(0)
10042 maxGroupID := int64(2147483647)
10043 goodfsGroupChangePolicy := core.FSGroupChangeAlways
10044 badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid")
10045 badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("")
10046
10047 successCases := map[string]core.PodSpec{
10048 "populate basic fields, leave defaults for most": {
10049 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10050 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10051 RestartPolicy: core.RestartPolicyAlways,
10052 DNSPolicy: core.DNSClusterFirst,
10053 },
10054 "populate all fields": {
10055 Volumes: []core.Volume{
10056 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
10057 },
10058 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10059 InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10060 RestartPolicy: core.RestartPolicyAlways,
10061 NodeSelector: map[string]string{
10062 "key": "value",
10063 },
10064 NodeName: "foobar",
10065 DNSPolicy: core.DNSClusterFirst,
10066 ActiveDeadlineSeconds: &activeDeadlineSeconds,
10067 ServiceAccountName: "acct",
10068 },
10069 "populate all fields with larger active deadline": {
10070 Volumes: []core.Volume{
10071 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
10072 },
10073 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10074 InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10075 RestartPolicy: core.RestartPolicyAlways,
10076 NodeSelector: map[string]string{
10077 "key": "value",
10078 },
10079 NodeName: "foobar",
10080 DNSPolicy: core.DNSClusterFirst,
10081 ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
10082 ServiceAccountName: "acct",
10083 },
10084 "populate HostNetwork": {
10085 Containers: []core.Container{
10086 {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
10087 Ports: []core.ContainerPort{
10088 {HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
10089 },
10090 },
10091 SecurityContext: &core.PodSecurityContext{
10092 HostNetwork: true,
10093 },
10094 RestartPolicy: core.RestartPolicyAlways,
10095 DNSPolicy: core.DNSClusterFirst,
10096 },
10097 "populate RunAsUser SupplementalGroups FSGroup with minID 0": {
10098 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10099 SecurityContext: &core.PodSecurityContext{
10100 SupplementalGroups: []int64{minGroupID},
10101 RunAsUser: &minUserID,
10102 FSGroup: &minGroupID,
10103 },
10104 RestartPolicy: core.RestartPolicyAlways,
10105 DNSPolicy: core.DNSClusterFirst,
10106 },
10107 "populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": {
10108 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10109 SecurityContext: &core.PodSecurityContext{
10110 SupplementalGroups: []int64{maxGroupID},
10111 RunAsUser: &maxUserID,
10112 FSGroup: &maxGroupID,
10113 },
10114 RestartPolicy: core.RestartPolicyAlways,
10115 DNSPolicy: core.DNSClusterFirst,
10116 },
10117 "populate HostIPC": {
10118 SecurityContext: &core.PodSecurityContext{
10119 HostIPC: true,
10120 },
10121 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10122 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10123 RestartPolicy: core.RestartPolicyAlways,
10124 DNSPolicy: core.DNSClusterFirst,
10125 },
10126 "populate HostPID": {
10127 SecurityContext: &core.PodSecurityContext{
10128 HostPID: true,
10129 },
10130 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10131 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10132 RestartPolicy: core.RestartPolicyAlways,
10133 DNSPolicy: core.DNSClusterFirst,
10134 },
10135 "populate Affinity": {
10136 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10137 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10138 RestartPolicy: core.RestartPolicyAlways,
10139 DNSPolicy: core.DNSClusterFirst,
10140 },
10141 "populate HostAliases": {
10142 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
10143 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10144 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10145 RestartPolicy: core.RestartPolicyAlways,
10146 DNSPolicy: core.DNSClusterFirst,
10147 },
10148 "populate HostAliases with `foo.bar` hostnames": {
10149 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
10150 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10151 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10152 RestartPolicy: core.RestartPolicyAlways,
10153 DNSPolicy: core.DNSClusterFirst,
10154 },
10155 "populate HostAliases with HostNetwork": {
10156 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
10157 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10158 SecurityContext: &core.PodSecurityContext{
10159 HostNetwork: true,
10160 },
10161 RestartPolicy: core.RestartPolicyAlways,
10162 DNSPolicy: core.DNSClusterFirst,
10163 },
10164 "populate PriorityClassName": {
10165 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10166 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10167 RestartPolicy: core.RestartPolicyAlways,
10168 DNSPolicy: core.DNSClusterFirst,
10169 PriorityClassName: "valid-name",
10170 },
10171 "populate ShareProcessNamespace": {
10172 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10173 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10174 RestartPolicy: core.RestartPolicyAlways,
10175 DNSPolicy: core.DNSClusterFirst,
10176 SecurityContext: &core.PodSecurityContext{
10177 ShareProcessNamespace: &[]bool{true}[0],
10178 },
10179 },
10180 "populate RuntimeClassName": {
10181 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10182 RestartPolicy: core.RestartPolicyAlways,
10183 DNSPolicy: core.DNSClusterFirst,
10184 RuntimeClassName: utilpointer.String("valid-sandbox"),
10185 },
10186 "populate Overhead": {
10187 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10188 RestartPolicy: core.RestartPolicyAlways,
10189 DNSPolicy: core.DNSClusterFirst,
10190 RuntimeClassName: utilpointer.String("valid-sandbox"),
10191 Overhead: core.ResourceList{},
10192 },
10193 "populate DNSPolicy": {
10194 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10195 SecurityContext: &core.PodSecurityContext{
10196 FSGroupChangePolicy: &goodfsGroupChangePolicy,
10197 },
10198 RestartPolicy: core.RestartPolicyAlways,
10199 DNSPolicy: core.DNSClusterFirst,
10200 },
10201 }
10202 for k, v := range successCases {
10203 t.Run(k, func(t *testing.T) {
10204 opts := PodValidationOptions{
10205 ResourceIsPod: true,
10206 }
10207 if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) != 0 {
10208 t.Errorf("expected success: %v", errs)
10209 }
10210 })
10211 }
10212
10213 activeDeadlineSeconds = int64(0)
10214 activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1)
10215
10216 minUserID = int64(-1)
10217 maxUserID = int64(2147483648)
10218 minGroupID = int64(-1)
10219 maxGroupID = int64(2147483648)
10220
10221 failureCases := map[string]core.PodSpec{
10222 "bad volume": {
10223 Volumes: []core.Volume{{}},
10224 RestartPolicy: core.RestartPolicyAlways,
10225 DNSPolicy: core.DNSClusterFirst,
10226 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10227 },
10228 "no containers": {
10229 RestartPolicy: core.RestartPolicyAlways,
10230 DNSPolicy: core.DNSClusterFirst,
10231 },
10232 "bad container": {
10233 Containers: []core.Container{{}},
10234 RestartPolicy: core.RestartPolicyAlways,
10235 DNSPolicy: core.DNSClusterFirst,
10236 },
10237 "bad init container": {
10238 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10239 InitContainers: []core.Container{{}},
10240 RestartPolicy: core.RestartPolicyAlways,
10241 DNSPolicy: core.DNSClusterFirst,
10242 },
10243 "bad DNS policy": {
10244 DNSPolicy: core.DNSPolicy("invalid"),
10245 RestartPolicy: core.RestartPolicyAlways,
10246 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10247 },
10248 "bad service account name": {
10249 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10250 RestartPolicy: core.RestartPolicyAlways,
10251 DNSPolicy: core.DNSClusterFirst,
10252 ServiceAccountName: "invalidName",
10253 },
10254 "bad restart policy": {
10255 RestartPolicy: "UnknowPolicy",
10256 DNSPolicy: core.DNSClusterFirst,
10257 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10258 },
10259 "with hostNetwork hostPort unspecified": {
10260 Containers: []core.Container{
10261 {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
10262 {HostPort: 0, ContainerPort: 2600, Protocol: "TCP"}},
10263 },
10264 },
10265 SecurityContext: &core.PodSecurityContext{
10266 HostNetwork: true,
10267 },
10268 RestartPolicy: core.RestartPolicyAlways,
10269 DNSPolicy: core.DNSClusterFirst,
10270 },
10271 "with hostNetwork hostPort not equal to containerPort": {
10272 Containers: []core.Container{
10273 {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
10274 {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}},
10275 },
10276 },
10277 SecurityContext: &core.PodSecurityContext{
10278 HostNetwork: true,
10279 },
10280 RestartPolicy: core.RestartPolicyAlways,
10281 DNSPolicy: core.DNSClusterFirst,
10282 },
10283 "with hostAliases with invalid IP": {
10284 SecurityContext: &core.PodSecurityContext{
10285 HostNetwork: false,
10286 },
10287 HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}},
10288 },
10289 "with hostAliases with invalid hostname": {
10290 SecurityContext: &core.PodSecurityContext{
10291 HostNetwork: false,
10292 },
10293 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}},
10294 },
10295 "bad supplementalGroups large than math.MaxInt32": {
10296 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10297 SecurityContext: &core.PodSecurityContext{
10298 SupplementalGroups: []int64{maxGroupID, 1234},
10299 },
10300 RestartPolicy: core.RestartPolicyAlways,
10301 DNSPolicy: core.DNSClusterFirst,
10302 },
10303 "bad supplementalGroups less than 0": {
10304 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10305 SecurityContext: &core.PodSecurityContext{
10306 SupplementalGroups: []int64{minGroupID, 1234},
10307 },
10308 RestartPolicy: core.RestartPolicyAlways,
10309 DNSPolicy: core.DNSClusterFirst,
10310 },
10311 "bad runAsUser large than math.MaxInt32": {
10312 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10313 SecurityContext: &core.PodSecurityContext{
10314 RunAsUser: &maxUserID,
10315 },
10316 RestartPolicy: core.RestartPolicyAlways,
10317 DNSPolicy: core.DNSClusterFirst,
10318 },
10319 "bad runAsUser less than 0": {
10320 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10321 SecurityContext: &core.PodSecurityContext{
10322 RunAsUser: &minUserID,
10323 },
10324 RestartPolicy: core.RestartPolicyAlways,
10325 DNSPolicy: core.DNSClusterFirst,
10326 },
10327 "bad fsGroup large than math.MaxInt32": {
10328 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10329 SecurityContext: &core.PodSecurityContext{
10330 FSGroup: &maxGroupID,
10331 },
10332 RestartPolicy: core.RestartPolicyAlways,
10333 DNSPolicy: core.DNSClusterFirst,
10334 },
10335 "bad fsGroup less than 0": {
10336 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10337 SecurityContext: &core.PodSecurityContext{
10338 FSGroup: &minGroupID,
10339 },
10340 RestartPolicy: core.RestartPolicyAlways,
10341 DNSPolicy: core.DNSClusterFirst,
10342 },
10343 "bad-active-deadline-seconds": {
10344 Volumes: []core.Volume{
10345 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
10346 },
10347 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10348 RestartPolicy: core.RestartPolicyAlways,
10349 NodeSelector: map[string]string{
10350 "key": "value",
10351 },
10352 NodeName: "foobar",
10353 DNSPolicy: core.DNSClusterFirst,
10354 ActiveDeadlineSeconds: &activeDeadlineSeconds,
10355 },
10356 "active-deadline-seconds-too-large": {
10357 Volumes: []core.Volume{
10358 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
10359 },
10360 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10361 RestartPolicy: core.RestartPolicyAlways,
10362 NodeSelector: map[string]string{
10363 "key": "value",
10364 },
10365 NodeName: "foobar",
10366 DNSPolicy: core.DNSClusterFirst,
10367 ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge,
10368 },
10369 "bad nodeName": {
10370 NodeName: "node name",
10371 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10372 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10373 RestartPolicy: core.RestartPolicyAlways,
10374 DNSPolicy: core.DNSClusterFirst,
10375 },
10376 "bad PriorityClassName": {
10377 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10378 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10379 RestartPolicy: core.RestartPolicyAlways,
10380 DNSPolicy: core.DNSClusterFirst,
10381 PriorityClassName: "InvalidName",
10382 },
10383 "ShareProcessNamespace and HostPID both set": {
10384 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10385 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10386 RestartPolicy: core.RestartPolicyAlways,
10387 DNSPolicy: core.DNSClusterFirst,
10388 SecurityContext: &core.PodSecurityContext{
10389 HostPID: true,
10390 ShareProcessNamespace: &[]bool{true}[0],
10391 },
10392 },
10393 "bad RuntimeClassName": {
10394 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10395 RestartPolicy: core.RestartPolicyAlways,
10396 DNSPolicy: core.DNSClusterFirst,
10397 RuntimeClassName: utilpointer.String("invalid/sandbox"),
10398 },
10399 "bad empty fsGroupchangepolicy": {
10400 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10401 SecurityContext: &core.PodSecurityContext{
10402 FSGroupChangePolicy: &badfsGroupChangePolicy2,
10403 },
10404 RestartPolicy: core.RestartPolicyAlways,
10405 DNSPolicy: core.DNSClusterFirst,
10406 },
10407 "bad invalid fsgroupchangepolicy": {
10408 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10409 SecurityContext: &core.PodSecurityContext{
10410 FSGroupChangePolicy: &badfsGroupChangePolicy1,
10411 },
10412 RestartPolicy: core.RestartPolicyAlways,
10413 DNSPolicy: core.DNSClusterFirst,
10414 },
10415 "disallowed resources resize policy for init containers": {
10416 InitContainers: []core.Container{{
10417 Name: "initctr",
10418 Image: "initimage",
10419 ResizePolicy: []core.ContainerResizePolicy{
10420 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
10421 },
10422 ImagePullPolicy: "IfNotPresent",
10423 TerminationMessagePolicy: "File",
10424 }},
10425 Containers: []core.Container{{
10426 Name: "ctr",
10427 Image: "image",
10428 ResizePolicy: []core.ContainerResizePolicy{
10429 {ResourceName: "cpu", RestartPolicy: "NotRequired"},
10430 },
10431 ImagePullPolicy: "IfNotPresent",
10432 TerminationMessagePolicy: "File",
10433 }},
10434 RestartPolicy: core.RestartPolicyAlways,
10435 DNSPolicy: core.DNSClusterFirst,
10436 },
10437 }
10438 for k, v := range failureCases {
10439 opts := PodValidationOptions{
10440 ResourceIsPod: true,
10441 }
10442 if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) == 0 {
10443 t.Errorf("expected failure for %q", k)
10444 }
10445 }
10446 }
10447
10448 func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
10449 var out core.PodSpec
10450 out.Containers = in.Containers
10451 out.RestartPolicy = in.RestartPolicy
10452 out.DNSPolicy = in.DNSPolicy
10453 out.Tolerations = tolerations
10454 return out
10455 }
10456
10457 func TestValidatePod(t *testing.T) {
10458 validPodSpec := func(affinity *core.Affinity) core.PodSpec {
10459 spec := core.PodSpec{
10460 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10461 RestartPolicy: core.RestartPolicyAlways,
10462 DNSPolicy: core.DNSClusterFirst,
10463 }
10464 if affinity != nil {
10465 spec.Affinity = affinity
10466 }
10467 return spec
10468 }
10469 validPVCSpec := core.PersistentVolumeClaimSpec{
10470 AccessModes: []core.PersistentVolumeAccessMode{
10471 core.ReadWriteOnce,
10472 },
10473 Resources: core.VolumeResourceRequirements{
10474 Requests: core.ResourceList{
10475 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
10476 },
10477 },
10478 }
10479 validPVCTemplate := core.PersistentVolumeClaimTemplate{
10480 Spec: validPVCSpec,
10481 }
10482 longPodName := strings.Repeat("a", 200)
10483 longVolName := strings.Repeat("b", 60)
10484
10485 successCases := map[string]core.Pod{
10486 "basic fields": {
10487 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
10488 Spec: core.PodSpec{
10489 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
10490 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10491 RestartPolicy: core.RestartPolicyAlways,
10492 DNSPolicy: core.DNSClusterFirst,
10493 },
10494 },
10495 "just about everything": {
10496 ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
10497 Spec: core.PodSpec{
10498 Volumes: []core.Volume{
10499 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
10500 },
10501 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10502 RestartPolicy: core.RestartPolicyAlways,
10503 DNSPolicy: core.DNSClusterFirst,
10504 NodeSelector: map[string]string{
10505 "key": "value",
10506 },
10507 NodeName: "foobar",
10508 },
10509 },
10510 "serialized node affinity requirements": {
10511 ObjectMeta: metav1.ObjectMeta{
10512 Name: "123",
10513 Namespace: "ns",
10514 },
10515 Spec: validPodSpec(
10516
10517
10518
10519
10520
10521
10522
10523
10524
10525
10526
10527
10528
10529
10530 &core.Affinity{
10531 NodeAffinity: &core.NodeAffinity{
10532 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
10533 NodeSelectorTerms: []core.NodeSelectorTerm{{
10534 MatchExpressions: []core.NodeSelectorRequirement{{
10535 Key: "key2",
10536 Operator: core.NodeSelectorOpIn,
10537 Values: []string{"value1", "value2"},
10538 }},
10539 MatchFields: []core.NodeSelectorRequirement{{
10540 Key: "metadata.name",
10541 Operator: core.NodeSelectorOpIn,
10542 Values: []string{"host1"},
10543 }},
10544 }},
10545 },
10546 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
10547 Weight: 10,
10548 Preference: core.NodeSelectorTerm{
10549 MatchExpressions: []core.NodeSelectorRequirement{{
10550 Key: "foo",
10551 Operator: core.NodeSelectorOpIn,
10552 Values: []string{"bar"},
10553 }},
10554 },
10555 }},
10556 },
10557 },
10558 ),
10559 },
10560 "serialized node affinity requirements, II": {
10561 ObjectMeta: metav1.ObjectMeta{
10562 Name: "123",
10563 Namespace: "ns",
10564 },
10565 Spec: validPodSpec(
10566
10567
10568
10569
10570
10571
10572
10573
10574
10575
10576
10577
10578
10579
10580 &core.Affinity{
10581 NodeAffinity: &core.NodeAffinity{
10582 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
10583 NodeSelectorTerms: []core.NodeSelectorTerm{{
10584 MatchExpressions: []core.NodeSelectorRequirement{},
10585 }},
10586 },
10587 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
10588 Weight: 10,
10589 Preference: core.NodeSelectorTerm{
10590 MatchExpressions: []core.NodeSelectorRequirement{},
10591 },
10592 }},
10593 },
10594 },
10595 ),
10596 },
10597 "serialized pod affinity in affinity requirements in annotations": {
10598 ObjectMeta: metav1.ObjectMeta{
10599 Name: "123",
10600 Namespace: "ns",
10601
10602
10603
10604
10605
10606
10607
10608
10609
10610
10611
10612
10613
10614 },
10615 Spec: validPodSpec(&core.Affinity{
10616 PodAffinity: &core.PodAffinity{
10617 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
10618 LabelSelector: &metav1.LabelSelector{
10619 MatchExpressions: []metav1.LabelSelectorRequirement{{
10620 Key: "key2",
10621 Operator: metav1.LabelSelectorOpIn,
10622 Values: []string{"value1", "value2"},
10623 }},
10624 },
10625 TopologyKey: "zone",
10626 Namespaces: []string{"ns"},
10627 NamespaceSelector: &metav1.LabelSelector{
10628 MatchExpressions: []metav1.LabelSelectorRequirement{{
10629 Key: "key",
10630 Operator: metav1.LabelSelectorOpIn,
10631 Values: []string{"value1", "value2"},
10632 }},
10633 },
10634 }},
10635 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
10636 Weight: 10,
10637 PodAffinityTerm: core.PodAffinityTerm{
10638 LabelSelector: &metav1.LabelSelector{
10639 MatchExpressions: []metav1.LabelSelectorRequirement{{
10640 Key: "key2",
10641 Operator: metav1.LabelSelectorOpNotIn,
10642 Values: []string{"value1", "value2"},
10643 }},
10644 },
10645 Namespaces: []string{"ns"},
10646 TopologyKey: "region",
10647 },
10648 }},
10649 },
10650 }),
10651 },
10652 "serialized pod anti affinity with different Label Operators in affinity requirements in annotations": {
10653 ObjectMeta: metav1.ObjectMeta{
10654 Name: "123",
10655 Namespace: "ns",
10656
10657
10658
10659
10660
10661
10662
10663
10664
10665
10666
10667
10668
10669 },
10670 Spec: validPodSpec(&core.Affinity{
10671 PodAntiAffinity: &core.PodAntiAffinity{
10672 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
10673 LabelSelector: &metav1.LabelSelector{
10674 MatchExpressions: []metav1.LabelSelectorRequirement{{
10675 Key: "key2",
10676 Operator: metav1.LabelSelectorOpExists,
10677 }},
10678 },
10679 TopologyKey: "zone",
10680 Namespaces: []string{"ns"},
10681 }},
10682 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
10683 Weight: 10,
10684 PodAffinityTerm: core.PodAffinityTerm{
10685 LabelSelector: &metav1.LabelSelector{
10686 MatchExpressions: []metav1.LabelSelectorRequirement{{
10687 Key: "key2",
10688 Operator: metav1.LabelSelectorOpDoesNotExist,
10689 }},
10690 },
10691 Namespaces: []string{"ns"},
10692 TopologyKey: "region",
10693 },
10694 }},
10695 },
10696 }),
10697 },
10698 "populate forgiveness tolerations with exists operator in annotations.": {
10699 ObjectMeta: metav1.ObjectMeta{
10700 Name: "123",
10701 Namespace: "ns",
10702 },
10703 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
10704 },
10705 "populate forgiveness tolerations with equal operator in annotations.": {
10706 ObjectMeta: metav1.ObjectMeta{
10707 Name: "123",
10708 Namespace: "ns",
10709 },
10710 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
10711 },
10712 "populate tolerations equal operator in annotations.": {
10713 ObjectMeta: metav1.ObjectMeta{
10714 Name: "123",
10715 Namespace: "ns",
10716 },
10717 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
10718 },
10719 "populate tolerations exists operator in annotations.": {
10720 ObjectMeta: metav1.ObjectMeta{
10721 Name: "123",
10722 Namespace: "ns",
10723 },
10724 Spec: validPodSpec(nil),
10725 },
10726 "empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": {
10727 ObjectMeta: metav1.ObjectMeta{
10728 Name: "123",
10729 Namespace: "ns",
10730 },
10731 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
10732 },
10733 "empty operator is OK for toleration, defaults to Equal.": {
10734 ObjectMeta: metav1.ObjectMeta{
10735 Name: "123",
10736 Namespace: "ns",
10737 },
10738 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
10739 },
10740 "empty effect is OK for toleration, empty toleration effect means match all taint effects.": {
10741 ObjectMeta: metav1.ObjectMeta{
10742 Name: "123",
10743 Namespace: "ns",
10744 },
10745 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
10746 },
10747 "negative tolerationSeconds is OK for toleration.": {
10748 ObjectMeta: metav1.ObjectMeta{
10749 Name: "pod-forgiveness-invalid",
10750 Namespace: "ns",
10751 },
10752 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
10753 },
10754 "runtime default seccomp profile": {
10755 ObjectMeta: metav1.ObjectMeta{
10756 Name: "123",
10757 Namespace: "ns",
10758 Annotations: map[string]string{
10759 core.SeccompPodAnnotationKey: core.SeccompProfileRuntimeDefault,
10760 },
10761 },
10762 Spec: validPodSpec(nil),
10763 },
10764 "docker default seccomp profile": {
10765 ObjectMeta: metav1.ObjectMeta{
10766 Name: "123",
10767 Namespace: "ns",
10768 Annotations: map[string]string{
10769 core.SeccompPodAnnotationKey: core.DeprecatedSeccompProfileDockerDefault,
10770 },
10771 },
10772 Spec: validPodSpec(nil),
10773 },
10774 "unconfined seccomp profile": {
10775 ObjectMeta: metav1.ObjectMeta{
10776 Name: "123",
10777 Namespace: "ns",
10778 Annotations: map[string]string{
10779 core.SeccompPodAnnotationKey: "unconfined",
10780 },
10781 },
10782 Spec: validPodSpec(nil),
10783 },
10784 "localhost seccomp profile": {
10785 ObjectMeta: metav1.ObjectMeta{
10786 Name: "123",
10787 Namespace: "ns",
10788 Annotations: map[string]string{
10789 core.SeccompPodAnnotationKey: "localhost/foo",
10790 },
10791 },
10792 Spec: validPodSpec(nil),
10793 },
10794 "localhost seccomp profile for a container": {
10795 ObjectMeta: metav1.ObjectMeta{
10796 Name: "123",
10797 Namespace: "ns",
10798 Annotations: map[string]string{
10799 core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo",
10800 },
10801 },
10802 Spec: validPodSpec(nil),
10803 },
10804 "runtime default seccomp profile for a pod": {
10805 ObjectMeta: metav1.ObjectMeta{
10806 Name: "123",
10807 Namespace: "ns",
10808 },
10809 Spec: core.PodSpec{
10810 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10811 RestartPolicy: core.RestartPolicyAlways,
10812 DNSPolicy: core.DNSDefault,
10813 SecurityContext: &core.PodSecurityContext{
10814 SeccompProfile: &core.SeccompProfile{
10815 Type: core.SeccompProfileTypeRuntimeDefault,
10816 },
10817 },
10818 },
10819 },
10820 "runtime default seccomp profile for a container": {
10821 ObjectMeta: metav1.ObjectMeta{
10822 Name: "123",
10823 Namespace: "ns",
10824 },
10825 Spec: core.PodSpec{
10826 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
10827 SecurityContext: &core.SecurityContext{
10828 SeccompProfile: &core.SeccompProfile{
10829 Type: core.SeccompProfileTypeRuntimeDefault,
10830 },
10831 },
10832 }},
10833 RestartPolicy: core.RestartPolicyAlways,
10834 DNSPolicy: core.DNSDefault,
10835 },
10836 },
10837 "unconfined seccomp profile for a pod": {
10838 ObjectMeta: metav1.ObjectMeta{
10839 Name: "123",
10840 Namespace: "ns",
10841 },
10842 Spec: core.PodSpec{
10843 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10844 RestartPolicy: core.RestartPolicyAlways,
10845 DNSPolicy: core.DNSDefault,
10846 SecurityContext: &core.PodSecurityContext{
10847 SeccompProfile: &core.SeccompProfile{
10848 Type: core.SeccompProfileTypeUnconfined,
10849 },
10850 },
10851 },
10852 },
10853 "unconfined seccomp profile for a container": {
10854 ObjectMeta: metav1.ObjectMeta{
10855 Name: "123",
10856 Namespace: "ns",
10857 },
10858 Spec: core.PodSpec{
10859 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
10860 SecurityContext: &core.SecurityContext{
10861 SeccompProfile: &core.SeccompProfile{
10862 Type: core.SeccompProfileTypeUnconfined,
10863 },
10864 },
10865 }},
10866 RestartPolicy: core.RestartPolicyAlways,
10867 DNSPolicy: core.DNSDefault,
10868 },
10869 },
10870 "localhost seccomp profile for a pod": {
10871 ObjectMeta: metav1.ObjectMeta{
10872 Name: "123",
10873 Namespace: "ns",
10874 },
10875 Spec: core.PodSpec{
10876 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10877 RestartPolicy: core.RestartPolicyAlways,
10878 DNSPolicy: core.DNSDefault,
10879 SecurityContext: &core.PodSecurityContext{
10880 SeccompProfile: &core.SeccompProfile{
10881 Type: core.SeccompProfileTypeLocalhost,
10882 LocalhostProfile: utilpointer.String("filename.json"),
10883 },
10884 },
10885 },
10886 },
10887 "localhost seccomp profile for a container, II": {
10888 ObjectMeta: metav1.ObjectMeta{
10889 Name: "123",
10890 Namespace: "ns",
10891 },
10892 Spec: core.PodSpec{
10893 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
10894 SecurityContext: &core.SecurityContext{
10895 SeccompProfile: &core.SeccompProfile{
10896 Type: core.SeccompProfileTypeLocalhost,
10897 LocalhostProfile: utilpointer.String("filename.json"),
10898 },
10899 },
10900 }},
10901 RestartPolicy: core.RestartPolicyAlways,
10902 DNSPolicy: core.DNSDefault,
10903 },
10904 },
10905 "default AppArmor annotation for a container": {
10906 ObjectMeta: metav1.ObjectMeta{
10907 Name: "123",
10908 Namespace: "ns",
10909 Annotations: map[string]string{
10910 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
10911 },
10912 },
10913 Spec: validPodSpec(nil),
10914 },
10915 "default AppArmor annotation for an init container": {
10916 ObjectMeta: metav1.ObjectMeta{
10917 Name: "123",
10918 Namespace: "ns",
10919 Annotations: map[string]string{
10920 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
10921 },
10922 },
10923 Spec: core.PodSpec{
10924 InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10925 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10926 RestartPolicy: core.RestartPolicyAlways,
10927 DNSPolicy: core.DNSClusterFirst,
10928 },
10929 },
10930 "localhost AppArmor annotation for a container": {
10931 ObjectMeta: metav1.ObjectMeta{
10932 Name: "123",
10933 Namespace: "ns",
10934 Annotations: map[string]string{
10935 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo",
10936 },
10937 },
10938 Spec: validPodSpec(nil),
10939 },
10940 "runtime default AppArmor profile for a pod": {
10941 ObjectMeta: metav1.ObjectMeta{
10942 Name: "123",
10943 Namespace: "ns",
10944 },
10945 Spec: core.PodSpec{
10946 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10947 RestartPolicy: core.RestartPolicyAlways,
10948 DNSPolicy: core.DNSDefault,
10949 SecurityContext: &core.PodSecurityContext{
10950 AppArmorProfile: &core.AppArmorProfile{
10951 Type: core.AppArmorProfileTypeRuntimeDefault,
10952 },
10953 },
10954 },
10955 },
10956 "runtime default AppArmor profile for a container": {
10957 ObjectMeta: metav1.ObjectMeta{
10958 Name: "123",
10959 Namespace: "ns",
10960 },
10961 Spec: core.PodSpec{
10962 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
10963 SecurityContext: &core.SecurityContext{
10964 AppArmorProfile: &core.AppArmorProfile{
10965 Type: core.AppArmorProfileTypeRuntimeDefault,
10966 },
10967 },
10968 }},
10969 RestartPolicy: core.RestartPolicyAlways,
10970 DNSPolicy: core.DNSDefault,
10971 },
10972 },
10973 "unconfined AppArmor profile for a pod": {
10974 ObjectMeta: metav1.ObjectMeta{
10975 Name: "123",
10976 Namespace: "ns",
10977 },
10978 Spec: core.PodSpec{
10979 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
10980 RestartPolicy: core.RestartPolicyAlways,
10981 DNSPolicy: core.DNSDefault,
10982 SecurityContext: &core.PodSecurityContext{
10983 AppArmorProfile: &core.AppArmorProfile{
10984 Type: core.AppArmorProfileTypeUnconfined,
10985 },
10986 },
10987 },
10988 },
10989 "unconfined AppArmor profile for a container": {
10990 ObjectMeta: metav1.ObjectMeta{
10991 Name: "123",
10992 Namespace: "ns",
10993 },
10994 Spec: core.PodSpec{
10995 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
10996 SecurityContext: &core.SecurityContext{
10997 AppArmorProfile: &core.AppArmorProfile{
10998 Type: core.AppArmorProfileTypeUnconfined,
10999 },
11000 },
11001 }},
11002 RestartPolicy: core.RestartPolicyAlways,
11003 DNSPolicy: core.DNSDefault,
11004 },
11005 },
11006 "localhost AppArmor profile for a pod": {
11007 ObjectMeta: metav1.ObjectMeta{
11008 Name: "123",
11009 Namespace: "ns",
11010 },
11011 Spec: core.PodSpec{
11012 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11013 RestartPolicy: core.RestartPolicyAlways,
11014 DNSPolicy: core.DNSDefault,
11015 SecurityContext: &core.PodSecurityContext{
11016 AppArmorProfile: &core.AppArmorProfile{
11017 Type: core.AppArmorProfileTypeLocalhost,
11018 LocalhostProfile: ptr.To("example-org/application-foo"),
11019 },
11020 },
11021 },
11022 },
11023 "localhost AppArmor profile for a container field": {
11024 ObjectMeta: metav1.ObjectMeta{
11025 Name: "123",
11026 Namespace: "ns",
11027 },
11028 Spec: core.PodSpec{
11029 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
11030 SecurityContext: &core.SecurityContext{
11031 AppArmorProfile: &core.AppArmorProfile{
11032 Type: core.AppArmorProfileTypeLocalhost,
11033 LocalhostProfile: ptr.To("example-org/application-foo"),
11034 },
11035 },
11036 }},
11037 RestartPolicy: core.RestartPolicyAlways,
11038 DNSPolicy: core.DNSDefault,
11039 },
11040 },
11041 "matching AppArmor fields and annotations": {
11042 ObjectMeta: metav1.ObjectMeta{
11043 Name: "123",
11044 Namespace: "ns",
11045 Annotations: map[string]string{
11046 core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
11047 },
11048 },
11049 Spec: core.PodSpec{
11050 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
11051 SecurityContext: &core.SecurityContext{
11052 AppArmorProfile: &core.AppArmorProfile{
11053 Type: core.AppArmorProfileTypeLocalhost,
11054 LocalhostProfile: ptr.To("foo"),
11055 },
11056 },
11057 }},
11058 RestartPolicy: core.RestartPolicyAlways,
11059 DNSPolicy: core.DNSDefault,
11060 },
11061 },
11062 "matching AppArmor pod field and annotations": {
11063 ObjectMeta: metav1.ObjectMeta{
11064 Name: "123",
11065 Namespace: "ns",
11066 Annotations: map[string]string{
11067 core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
11068 },
11069 },
11070 Spec: core.PodSpec{
11071 SecurityContext: &core.PodSecurityContext{
11072 AppArmorProfile: &core.AppArmorProfile{
11073 Type: core.AppArmorProfileTypeLocalhost,
11074 LocalhostProfile: ptr.To("foo"),
11075 },
11076 },
11077 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11078 RestartPolicy: core.RestartPolicyAlways,
11079 DNSPolicy: core.DNSDefault,
11080 },
11081 },
11082 "syntactically valid sysctls": {
11083 ObjectMeta: metav1.ObjectMeta{
11084 Name: "123",
11085 Namespace: "ns",
11086 },
11087 Spec: core.PodSpec{
11088 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11089 RestartPolicy: core.RestartPolicyAlways,
11090 DNSPolicy: core.DNSClusterFirst,
11091 SecurityContext: &core.PodSecurityContext{
11092 Sysctls: []core.Sysctl{{
11093 Name: "kernel.shmmni",
11094 Value: "32768",
11095 }, {
11096 Name: "kernel.shmmax",
11097 Value: "1000000000",
11098 }, {
11099 Name: "knet.ipv4.route.min_pmtu",
11100 Value: "1000",
11101 }},
11102 },
11103 },
11104 },
11105 "valid extended resources for init container": {
11106 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
11107 Spec: core.PodSpec{
11108 InitContainers: []core.Container{{
11109 Name: "valid-extended",
11110 Image: "image",
11111 ImagePullPolicy: "IfNotPresent",
11112 Resources: core.ResourceRequirements{
11113 Requests: core.ResourceList{
11114 core.ResourceName("example.com/a"): resource.MustParse("10"),
11115 },
11116 Limits: core.ResourceList{
11117 core.ResourceName("example.com/a"): resource.MustParse("10"),
11118 },
11119 },
11120 TerminationMessagePolicy: "File",
11121 }},
11122 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11123 RestartPolicy: core.RestartPolicyAlways,
11124 DNSPolicy: core.DNSClusterFirst,
11125 },
11126 },
11127 "valid extended resources for regular container": {
11128 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
11129 Spec: core.PodSpec{
11130 InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11131 Containers: []core.Container{{
11132 Name: "valid-extended",
11133 Image: "image",
11134 ImagePullPolicy: "IfNotPresent",
11135 Resources: core.ResourceRequirements{
11136 Requests: core.ResourceList{
11137 core.ResourceName("example.com/a"): resource.MustParse("10"),
11138 },
11139 Limits: core.ResourceList{
11140 core.ResourceName("example.com/a"): resource.MustParse("10"),
11141 },
11142 },
11143 TerminationMessagePolicy: "File",
11144 }},
11145 RestartPolicy: core.RestartPolicyAlways,
11146 DNSPolicy: core.DNSClusterFirst,
11147 },
11148 },
11149 "valid serviceaccount token projected volume with serviceaccount name specified": {
11150 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
11151 Spec: core.PodSpec{
11152 ServiceAccountName: "some-service-account",
11153 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11154 RestartPolicy: core.RestartPolicyAlways,
11155 DNSPolicy: core.DNSClusterFirst,
11156 Volumes: []core.Volume{{
11157 Name: "projected-volume",
11158 VolumeSource: core.VolumeSource{
11159 Projected: &core.ProjectedVolumeSource{
11160 Sources: []core.VolumeProjection{{
11161 ServiceAccountToken: &core.ServiceAccountTokenProjection{
11162 Audience: "foo-audience",
11163 ExpirationSeconds: 6000,
11164 Path: "foo-path",
11165 },
11166 }},
11167 },
11168 },
11169 }},
11170 },
11171 },
11172 "valid ClusterTrustBundlePEM projected volume referring to a CTB by name": {
11173 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
11174 Spec: core.PodSpec{
11175 ServiceAccountName: "some-service-account",
11176 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11177 RestartPolicy: core.RestartPolicyAlways,
11178 DNSPolicy: core.DNSClusterFirst,
11179 Volumes: []core.Volume{
11180 {
11181 Name: "projected-volume",
11182 VolumeSource: core.VolumeSource{
11183 Projected: &core.ProjectedVolumeSource{
11184 Sources: []core.VolumeProjection{
11185 {
11186 ClusterTrustBundle: &core.ClusterTrustBundleProjection{
11187 Path: "foo-path",
11188 Name: utilpointer.String("foo"),
11189 },
11190 },
11191 },
11192 },
11193 },
11194 },
11195 },
11196 },
11197 },
11198 "valid ClusterTrustBundlePEM projected volume referring to a CTB by signer name": {
11199 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
11200 Spec: core.PodSpec{
11201 ServiceAccountName: "some-service-account",
11202 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11203 RestartPolicy: core.RestartPolicyAlways,
11204 DNSPolicy: core.DNSClusterFirst,
11205 Volumes: []core.Volume{
11206 {
11207 Name: "projected-volume",
11208 VolumeSource: core.VolumeSource{
11209 Projected: &core.ProjectedVolumeSource{
11210 Sources: []core.VolumeProjection{
11211 {
11212 ClusterTrustBundle: &core.ClusterTrustBundleProjection{
11213 Path: "foo-path",
11214 SignerName: utilpointer.String("example.com/foo"),
11215 LabelSelector: &metav1.LabelSelector{
11216 MatchLabels: map[string]string{
11217 "version": "live",
11218 },
11219 },
11220 },
11221 },
11222 },
11223 },
11224 },
11225 },
11226 },
11227 },
11228 },
11229 "ephemeral volume + PVC, no conflict between them": {
11230 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
11231 Spec: core.PodSpec{
11232 Volumes: []core.Volume{
11233 {Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
11234 {Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
11235 },
11236 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11237 RestartPolicy: core.RestartPolicyAlways,
11238 DNSPolicy: core.DNSClusterFirst,
11239 },
11240 },
11241 "negative pod-deletion-cost": {
11242 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "-100"}},
11243 Spec: core.PodSpec{
11244 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11245 RestartPolicy: core.RestartPolicyAlways,
11246 DNSPolicy: core.DNSClusterFirst,
11247 },
11248 },
11249 "positive pod-deletion-cost": {
11250 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "100"}},
11251 Spec: core.PodSpec{
11252 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11253 RestartPolicy: core.RestartPolicyAlways,
11254 DNSPolicy: core.DNSClusterFirst,
11255 },
11256 },
11257 "MatchLabelKeys/MismatchLabelKeys in required PodAffinity": {
11258 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
11259 Spec: core.PodSpec{
11260 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11261 RestartPolicy: core.RestartPolicyAlways,
11262 DNSPolicy: core.DNSClusterFirst,
11263 Affinity: &core.Affinity{
11264 PodAffinity: &core.PodAffinity{
11265 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
11266 {
11267 LabelSelector: &metav1.LabelSelector{
11268 MatchExpressions: []metav1.LabelSelectorRequirement{
11269 {
11270 Key: "key",
11271 Operator: metav1.LabelSelectorOpNotIn,
11272 Values: []string{"value1", "value2"},
11273 },
11274 {
11275 Key: "key2",
11276 Operator: metav1.LabelSelectorOpIn,
11277 Values: []string{"value1"},
11278 },
11279 {
11280 Key: "key3",
11281 Operator: metav1.LabelSelectorOpNotIn,
11282 Values: []string{"value1"},
11283 },
11284 },
11285 },
11286 TopologyKey: "k8s.io/zone",
11287 MatchLabelKeys: []string{"key2"},
11288 MismatchLabelKeys: []string{"key3"},
11289 },
11290 },
11291 },
11292 },
11293 },
11294 },
11295 "MatchLabelKeys/MismatchLabelKeys in preferred PodAffinity": {
11296 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
11297 Spec: core.PodSpec{
11298 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11299 RestartPolicy: core.RestartPolicyAlways,
11300 DNSPolicy: core.DNSClusterFirst,
11301 Affinity: &core.Affinity{
11302 PodAffinity: &core.PodAffinity{
11303 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
11304 {
11305 Weight: 10,
11306 PodAffinityTerm: core.PodAffinityTerm{
11307 LabelSelector: &metav1.LabelSelector{
11308 MatchExpressions: []metav1.LabelSelectorRequirement{
11309 {
11310 Key: "key",
11311 Operator: metav1.LabelSelectorOpNotIn,
11312 Values: []string{"value1", "value2"},
11313 },
11314 {
11315 Key: "key2",
11316 Operator: metav1.LabelSelectorOpIn,
11317 Values: []string{"value1"},
11318 },
11319 {
11320 Key: "key3",
11321 Operator: metav1.LabelSelectorOpNotIn,
11322 Values: []string{"value1"},
11323 },
11324 },
11325 },
11326 TopologyKey: "k8s.io/zone",
11327 MatchLabelKeys: []string{"key2"},
11328 MismatchLabelKeys: []string{"key3"},
11329 },
11330 },
11331 },
11332 },
11333 },
11334 },
11335 },
11336 "MatchLabelKeys/MismatchLabelKeys in required PodAntiAffinity": {
11337 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
11338 Spec: core.PodSpec{
11339 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11340 RestartPolicy: core.RestartPolicyAlways,
11341 DNSPolicy: core.DNSClusterFirst,
11342 Affinity: &core.Affinity{
11343 PodAntiAffinity: &core.PodAntiAffinity{
11344 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
11345 {
11346 LabelSelector: &metav1.LabelSelector{
11347 MatchExpressions: []metav1.LabelSelectorRequirement{
11348 {
11349 Key: "key",
11350 Operator: metav1.LabelSelectorOpNotIn,
11351 Values: []string{"value1", "value2"},
11352 },
11353 {
11354 Key: "key2",
11355 Operator: metav1.LabelSelectorOpIn,
11356 Values: []string{"value1"},
11357 },
11358 {
11359 Key: "key3",
11360 Operator: metav1.LabelSelectorOpNotIn,
11361 Values: []string{"value1"},
11362 },
11363 },
11364 },
11365 TopologyKey: "k8s.io/zone",
11366 MatchLabelKeys: []string{"key2"},
11367 MismatchLabelKeys: []string{"key3"},
11368 },
11369 },
11370 },
11371 },
11372 },
11373 },
11374 "MatchLabelKeys/MismatchLabelKeys in preferred PodAntiAffinity": {
11375 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
11376 Spec: core.PodSpec{
11377 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11378 RestartPolicy: core.RestartPolicyAlways,
11379 DNSPolicy: core.DNSClusterFirst,
11380 Affinity: &core.Affinity{
11381 PodAntiAffinity: &core.PodAntiAffinity{
11382 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
11383 {
11384 Weight: 10,
11385 PodAffinityTerm: core.PodAffinityTerm{
11386 LabelSelector: &metav1.LabelSelector{
11387 MatchExpressions: []metav1.LabelSelectorRequirement{
11388 {
11389 Key: "key",
11390 Operator: metav1.LabelSelectorOpNotIn,
11391 Values: []string{"value1", "value2"},
11392 },
11393 {
11394 Key: "key2",
11395 Operator: metav1.LabelSelectorOpIn,
11396 Values: []string{"value1"},
11397 },
11398 {
11399 Key: "key3",
11400 Operator: metav1.LabelSelectorOpNotIn,
11401 Values: []string{"value1"},
11402 },
11403 },
11404 },
11405 TopologyKey: "k8s.io/zone",
11406 MatchLabelKeys: []string{"key2"},
11407 MismatchLabelKeys: []string{"key3"},
11408 },
11409 },
11410 },
11411 },
11412 },
11413 },
11414 },
11415 "LabelSelector can have the same key as MismatchLabelKeys": {
11416
11417 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
11418 Spec: core.PodSpec{
11419 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11420 RestartPolicy: core.RestartPolicyAlways,
11421 DNSPolicy: core.DNSClusterFirst,
11422 Affinity: &core.Affinity{
11423 PodAffinity: &core.PodAffinity{
11424 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
11425 {
11426 LabelSelector: &metav1.LabelSelector{
11427 MatchExpressions: []metav1.LabelSelectorRequirement{
11428 {
11429 Key: "key",
11430 Operator: metav1.LabelSelectorOpNotIn,
11431 Values: []string{"value1", "value2"},
11432 },
11433 {
11434
11435
11436 Key: "key2",
11437 Operator: metav1.LabelSelectorOpIn,
11438 Values: []string{"value1"},
11439 },
11440 {
11441 Key: "key2",
11442 Operator: metav1.LabelSelectorOpNotIn,
11443 Values: []string{"value1"},
11444 },
11445 },
11446 },
11447 TopologyKey: "k8s.io/zone",
11448 MismatchLabelKeys: []string{"key2"},
11449 },
11450 },
11451 },
11452 },
11453 },
11454 },
11455 }
11456
11457 for k, v := range successCases {
11458 t.Run(k, func(t *testing.T) {
11459 if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
11460 t.Errorf("expected success: %v", errs)
11461 }
11462 })
11463 }
11464
11465 errorCases := map[string]struct {
11466 spec core.Pod
11467 expectedError string
11468 }{
11469 "bad name": {
11470 expectedError: "metadata.name",
11471 spec: core.Pod{
11472 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"},
11473 Spec: core.PodSpec{
11474 RestartPolicy: core.RestartPolicyAlways,
11475 DNSPolicy: core.DNSClusterFirst,
11476 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11477 },
11478 },
11479 },
11480 "image whitespace": {
11481 expectedError: "spec.containers[0].image",
11482 spec: core.Pod{
11483 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
11484 Spec: core.PodSpec{
11485 RestartPolicy: core.RestartPolicyAlways,
11486 DNSPolicy: core.DNSClusterFirst,
11487 Containers: []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11488 },
11489 },
11490 },
11491 "image leading and trailing whitespace": {
11492 expectedError: "spec.containers[0].image",
11493 spec: core.Pod{
11494 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
11495 Spec: core.PodSpec{
11496 RestartPolicy: core.RestartPolicyAlways,
11497 DNSPolicy: core.DNSClusterFirst,
11498 Containers: []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11499 },
11500 },
11501 },
11502 "bad namespace": {
11503 expectedError: "metadata.namespace",
11504 spec: core.Pod{
11505 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""},
11506 Spec: core.PodSpec{
11507 RestartPolicy: core.RestartPolicyAlways,
11508 DNSPolicy: core.DNSClusterFirst,
11509 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11510 },
11511 },
11512 },
11513 "bad spec": {
11514 expectedError: "spec.containers[0].name",
11515 spec: core.Pod{
11516 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
11517 Spec: core.PodSpec{
11518 Containers: []core.Container{{}},
11519 },
11520 },
11521 },
11522 "bad label": {
11523 expectedError: "NoUppercaseOrSpecialCharsLike=Equals",
11524 spec: core.Pod{
11525 ObjectMeta: metav1.ObjectMeta{
11526 Name: "abc",
11527 Namespace: "ns",
11528 Labels: map[string]string{
11529 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
11530 },
11531 },
11532 Spec: core.PodSpec{
11533 RestartPolicy: core.RestartPolicyAlways,
11534 DNSPolicy: core.DNSClusterFirst,
11535 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
11536 },
11537 },
11538 },
11539 "invalid node selector requirement in node affinity, operator can't be null": {
11540 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator",
11541 spec: core.Pod{
11542 ObjectMeta: metav1.ObjectMeta{
11543 Name: "123",
11544 Namespace: "ns",
11545 },
11546 Spec: validPodSpec(&core.Affinity{
11547 NodeAffinity: &core.NodeAffinity{
11548 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
11549 NodeSelectorTerms: []core.NodeSelectorTerm{{
11550 MatchExpressions: []core.NodeSelectorRequirement{{
11551 Key: "key1",
11552 }},
11553 }},
11554 },
11555 },
11556 }),
11557 },
11558 },
11559 "invalid node selector requirement in node affinity, key is invalid": {
11560 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key",
11561 spec: core.Pod{
11562 ObjectMeta: metav1.ObjectMeta{
11563 Name: "123",
11564 Namespace: "ns",
11565 },
11566 Spec: validPodSpec(&core.Affinity{
11567 NodeAffinity: &core.NodeAffinity{
11568 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
11569 NodeSelectorTerms: []core.NodeSelectorTerm{{
11570 MatchExpressions: []core.NodeSelectorRequirement{{
11571 Key: "invalid key ___@#",
11572 Operator: core.NodeSelectorOpExists,
11573 }},
11574 }},
11575 },
11576 },
11577 }),
11578 },
11579 },
11580 "invalid node field selector requirement in node affinity, more values for field selector": {
11581 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values",
11582 spec: core.Pod{
11583 ObjectMeta: metav1.ObjectMeta{
11584 Name: "123",
11585 Namespace: "ns",
11586 },
11587 Spec: validPodSpec(&core.Affinity{
11588 NodeAffinity: &core.NodeAffinity{
11589 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
11590 NodeSelectorTerms: []core.NodeSelectorTerm{{
11591 MatchFields: []core.NodeSelectorRequirement{{
11592 Key: "metadata.name",
11593 Operator: core.NodeSelectorOpIn,
11594 Values: []string{"host1", "host2"},
11595 }},
11596 }},
11597 },
11598 },
11599 }),
11600 },
11601 },
11602 "invalid node field selector requirement in node affinity, invalid operator": {
11603 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].operator",
11604 spec: core.Pod{
11605 ObjectMeta: metav1.ObjectMeta{
11606 Name: "123",
11607 Namespace: "ns",
11608 },
11609 Spec: validPodSpec(&core.Affinity{
11610 NodeAffinity: &core.NodeAffinity{
11611 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
11612 NodeSelectorTerms: []core.NodeSelectorTerm{{
11613 MatchFields: []core.NodeSelectorRequirement{{
11614 Key: "metadata.name",
11615 Operator: core.NodeSelectorOpExists,
11616 }},
11617 }},
11618 },
11619 },
11620 }),
11621 },
11622 },
11623 "invalid node field selector requirement in node affinity, invalid key": {
11624 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key",
11625 spec: core.Pod{
11626 ObjectMeta: metav1.ObjectMeta{
11627 Name: "123",
11628 Namespace: "ns",
11629 },
11630 Spec: validPodSpec(&core.Affinity{
11631 NodeAffinity: &core.NodeAffinity{
11632 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
11633 NodeSelectorTerms: []core.NodeSelectorTerm{{
11634 MatchFields: []core.NodeSelectorRequirement{{
11635 Key: "metadata.namespace",
11636 Operator: core.NodeSelectorOpIn,
11637 Values: []string{"ns1"},
11638 }},
11639 }},
11640 },
11641 },
11642 }),
11643 },
11644 },
11645 "invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": {
11646 expectedError: "must be in the range 1-100",
11647 spec: core.Pod{
11648 ObjectMeta: metav1.ObjectMeta{
11649 Name: "123",
11650 Namespace: "ns",
11651 },
11652 Spec: validPodSpec(&core.Affinity{
11653 NodeAffinity: &core.NodeAffinity{
11654 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
11655 Weight: 199,
11656 Preference: core.NodeSelectorTerm{
11657 MatchExpressions: []core.NodeSelectorRequirement{{
11658 Key: "foo",
11659 Operator: core.NodeSelectorOpIn,
11660 Values: []string{"bar"},
11661 }},
11662 },
11663 }},
11664 },
11665 }),
11666 },
11667 },
11668 "invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": {
11669 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms",
11670 spec: core.Pod{
11671 ObjectMeta: metav1.ObjectMeta{
11672 Name: "123",
11673 Namespace: "ns",
11674 },
11675 Spec: validPodSpec(&core.Affinity{
11676 NodeAffinity: &core.NodeAffinity{
11677 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
11678 NodeSelectorTerms: []core.NodeSelectorTerm{},
11679 },
11680 },
11681 }),
11682 },
11683 },
11684 "invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": {
11685 expectedError: "must be in the range 1-100",
11686 spec: core.Pod{
11687 ObjectMeta: metav1.ObjectMeta{
11688 Name: "123",
11689 Namespace: "ns",
11690 },
11691 Spec: validPodSpec(&core.Affinity{
11692 PodAffinity: &core.PodAffinity{
11693 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11694 Weight: 109,
11695 PodAffinityTerm: core.PodAffinityTerm{
11696 LabelSelector: &metav1.LabelSelector{
11697 MatchExpressions: []metav1.LabelSelectorRequirement{{
11698 Key: "key2",
11699 Operator: metav1.LabelSelectorOpNotIn,
11700 Values: []string{"value1", "value2"},
11701 }},
11702 },
11703 Namespaces: []string{"ns"},
11704 TopologyKey: "region",
11705 },
11706 }},
11707 },
11708 }),
11709 },
11710 },
11711 "invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": {
11712 expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values",
11713 spec: core.Pod{
11714 ObjectMeta: metav1.ObjectMeta{
11715 Name: "123",
11716 Namespace: "ns",
11717 },
11718 Spec: validPodSpec(&core.Affinity{
11719 PodAntiAffinity: &core.PodAntiAffinity{
11720 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11721 Weight: 10,
11722 PodAffinityTerm: core.PodAffinityTerm{
11723 LabelSelector: &metav1.LabelSelector{
11724 MatchExpressions: []metav1.LabelSelectorRequirement{{
11725 Key: "key2",
11726 Operator: metav1.LabelSelectorOpExists,
11727 Values: []string{"value1", "value2"},
11728 }},
11729 },
11730 Namespaces: []string{"ns"},
11731 TopologyKey: "region",
11732 },
11733 }},
11734 },
11735 }),
11736 },
11737 },
11738 "invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, In operator must include Values": {
11739 expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
11740 spec: core.Pod{
11741 ObjectMeta: metav1.ObjectMeta{
11742 Name: "123",
11743 Namespace: "ns",
11744 },
11745 Spec: validPodSpec(&core.Affinity{
11746 PodAntiAffinity: &core.PodAntiAffinity{
11747 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11748 Weight: 10,
11749 PodAffinityTerm: core.PodAffinityTerm{
11750 NamespaceSelector: &metav1.LabelSelector{
11751 MatchExpressions: []metav1.LabelSelectorRequirement{{
11752 Key: "key2",
11753 Operator: metav1.LabelSelectorOpIn,
11754 }},
11755 },
11756 Namespaces: []string{"ns"},
11757 TopologyKey: "region",
11758 },
11759 }},
11760 },
11761 }),
11762 },
11763 },
11764 "invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, Exists operator can not have values": {
11765 expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
11766 spec: core.Pod{
11767 ObjectMeta: metav1.ObjectMeta{
11768 Name: "123",
11769 Namespace: "ns",
11770 },
11771 Spec: validPodSpec(&core.Affinity{
11772 PodAntiAffinity: &core.PodAntiAffinity{
11773 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11774 Weight: 10,
11775 PodAffinityTerm: core.PodAffinityTerm{
11776 NamespaceSelector: &metav1.LabelSelector{
11777 MatchExpressions: []metav1.LabelSelectorRequirement{{
11778 Key: "key2",
11779 Operator: metav1.LabelSelectorOpExists,
11780 Values: []string{"value1", "value2"},
11781 }},
11782 },
11783 Namespaces: []string{"ns"},
11784 TopologyKey: "region",
11785 },
11786 }},
11787 },
11788 }),
11789 },
11790 },
11791 "invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, namespace should be valid": {
11792 expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace",
11793 spec: core.Pod{
11794 ObjectMeta: metav1.ObjectMeta{
11795 Name: "123",
11796 Namespace: "ns",
11797 },
11798 Spec: validPodSpec(&core.Affinity{
11799 PodAffinity: &core.PodAffinity{
11800 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11801 Weight: 10,
11802 PodAffinityTerm: core.PodAffinityTerm{
11803 LabelSelector: &metav1.LabelSelector{
11804 MatchExpressions: []metav1.LabelSelectorRequirement{{
11805 Key: "key2",
11806 Operator: metav1.LabelSelectorOpExists,
11807 }},
11808 },
11809 Namespaces: []string{"INVALID_NAMESPACE"},
11810 TopologyKey: "region",
11811 },
11812 }},
11813 },
11814 }),
11815 },
11816 },
11817 "invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": {
11818 expectedError: "can not be empty",
11819 spec: core.Pod{
11820 ObjectMeta: metav1.ObjectMeta{
11821 Name: "123",
11822 Namespace: "ns",
11823 },
11824 Spec: validPodSpec(&core.Affinity{
11825 PodAffinity: &core.PodAffinity{
11826 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
11827 LabelSelector: &metav1.LabelSelector{
11828 MatchExpressions: []metav1.LabelSelectorRequirement{{
11829 Key: "key2",
11830 Operator: metav1.LabelSelectorOpIn,
11831 Values: []string{"value1", "value2"},
11832 }},
11833 },
11834 Namespaces: []string{"ns"},
11835 }},
11836 },
11837 }),
11838 },
11839 },
11840 "invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": {
11841 expectedError: "can not be empty",
11842 spec: core.Pod{
11843 ObjectMeta: metav1.ObjectMeta{
11844 Name: "123",
11845 Namespace: "ns",
11846 },
11847 Spec: validPodSpec(&core.Affinity{
11848 PodAntiAffinity: &core.PodAntiAffinity{
11849 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
11850 LabelSelector: &metav1.LabelSelector{
11851 MatchExpressions: []metav1.LabelSelectorRequirement{{
11852 Key: "key2",
11853 Operator: metav1.LabelSelectorOpIn,
11854 Values: []string{"value1", "value2"},
11855 }},
11856 },
11857 Namespaces: []string{"ns"},
11858 }},
11859 },
11860 }),
11861 },
11862 },
11863 "invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": {
11864 expectedError: "can not be empty",
11865 spec: core.Pod{
11866 ObjectMeta: metav1.ObjectMeta{
11867 Name: "123",
11868 Namespace: "ns",
11869 },
11870 Spec: validPodSpec(&core.Affinity{
11871 PodAffinity: &core.PodAffinity{
11872 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11873 Weight: 10,
11874 PodAffinityTerm: core.PodAffinityTerm{
11875 LabelSelector: &metav1.LabelSelector{
11876 MatchExpressions: []metav1.LabelSelectorRequirement{{
11877 Key: "key2",
11878 Operator: metav1.LabelSelectorOpNotIn,
11879 Values: []string{"value1", "value2"},
11880 }},
11881 },
11882 Namespaces: []string{"ns"},
11883 },
11884 }},
11885 },
11886 }),
11887 },
11888 },
11889 "invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": {
11890 expectedError: "can not be empty",
11891 spec: core.Pod{
11892 ObjectMeta: metav1.ObjectMeta{
11893 Name: "123",
11894 Namespace: "ns",
11895 },
11896 Spec: validPodSpec(&core.Affinity{
11897 PodAntiAffinity: &core.PodAntiAffinity{
11898 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
11899 Weight: 10,
11900 PodAffinityTerm: core.PodAffinityTerm{
11901 LabelSelector: &metav1.LabelSelector{
11902 MatchExpressions: []metav1.LabelSelectorRequirement{{
11903 Key: "key2",
11904 Operator: metav1.LabelSelectorOpNotIn,
11905 Values: []string{"value1", "value2"},
11906 }},
11907 },
11908 Namespaces: []string{"ns"},
11909 },
11910 }},
11911 },
11912 }),
11913 },
11914 },
11915 "invalid soft pod affinity, key in MatchLabelKeys isn't correctly defined": {
11916 expectedError: "prefix part must be non-empty",
11917 spec: core.Pod{
11918 ObjectMeta: metav1.ObjectMeta{
11919 Name: "123",
11920 Namespace: "ns",
11921 },
11922 Spec: validPodSpec(&core.Affinity{
11923 PodAffinity: &core.PodAffinity{
11924 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
11925 {
11926 Weight: 10,
11927 PodAffinityTerm: core.PodAffinityTerm{
11928 LabelSelector: &metav1.LabelSelector{
11929 MatchExpressions: []metav1.LabelSelectorRequirement{
11930 {
11931 Key: "key",
11932 Operator: metav1.LabelSelectorOpNotIn,
11933 Values: []string{"value1", "value2"},
11934 },
11935 },
11936 },
11937 TopologyKey: "k8s.io/zone",
11938 MatchLabelKeys: []string{"/simple"},
11939 },
11940 },
11941 },
11942 },
11943 }),
11944 },
11945 },
11946 "invalid hard pod affinity, key in MatchLabelKeys isn't correctly defined": {
11947 expectedError: "prefix part must be non-empty",
11948 spec: core.Pod{
11949 ObjectMeta: metav1.ObjectMeta{
11950 Name: "123",
11951 Namespace: "ns",
11952 },
11953 Spec: validPodSpec(&core.Affinity{
11954 PodAffinity: &core.PodAffinity{
11955 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
11956 {
11957 LabelSelector: &metav1.LabelSelector{
11958 MatchExpressions: []metav1.LabelSelectorRequirement{
11959 {
11960 Key: "key",
11961 Operator: metav1.LabelSelectorOpNotIn,
11962 Values: []string{"value1", "value2"},
11963 },
11964 },
11965 },
11966 TopologyKey: "k8s.io/zone",
11967 MatchLabelKeys: []string{"/simple"},
11968 },
11969 },
11970 },
11971 }),
11972 },
11973 },
11974 "invalid soft pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
11975 expectedError: "prefix part must be non-empty",
11976 spec: core.Pod{
11977 ObjectMeta: metav1.ObjectMeta{
11978 Name: "123",
11979 Namespace: "ns",
11980 },
11981 Spec: validPodSpec(&core.Affinity{
11982 PodAntiAffinity: &core.PodAntiAffinity{
11983 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
11984 {
11985 Weight: 10,
11986 PodAffinityTerm: core.PodAffinityTerm{
11987 LabelSelector: &metav1.LabelSelector{
11988 MatchExpressions: []metav1.LabelSelectorRequirement{
11989 {
11990 Key: "key",
11991 Operator: metav1.LabelSelectorOpNotIn,
11992 Values: []string{"value1", "value2"},
11993 },
11994 },
11995 },
11996 TopologyKey: "k8s.io/zone",
11997 MatchLabelKeys: []string{"/simple"},
11998 },
11999 },
12000 },
12001 },
12002 }),
12003 },
12004 },
12005 "invalid hard pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
12006 expectedError: "prefix part must be non-empty",
12007 spec: core.Pod{
12008 ObjectMeta: metav1.ObjectMeta{
12009 Name: "123",
12010 Namespace: "ns",
12011 },
12012 Spec: validPodSpec(&core.Affinity{
12013 PodAntiAffinity: &core.PodAntiAffinity{
12014 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12015 {
12016 LabelSelector: &metav1.LabelSelector{
12017 MatchExpressions: []metav1.LabelSelectorRequirement{
12018 {
12019 Key: "key",
12020 Operator: metav1.LabelSelectorOpNotIn,
12021 Values: []string{"value1", "value2"},
12022 },
12023 },
12024 },
12025 TopologyKey: "k8s.io/zone",
12026 MatchLabelKeys: []string{"/simple"},
12027 },
12028 },
12029 },
12030 }),
12031 },
12032 },
12033 "invalid soft pod affinity, key in MismatchLabelKeys isn't correctly defined": {
12034 expectedError: "prefix part must be non-empty",
12035 spec: core.Pod{
12036 ObjectMeta: metav1.ObjectMeta{
12037 Name: "123",
12038 Namespace: "ns",
12039 },
12040 Spec: validPodSpec(&core.Affinity{
12041 PodAffinity: &core.PodAffinity{
12042 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
12043 {
12044 Weight: 10,
12045 PodAffinityTerm: core.PodAffinityTerm{
12046 LabelSelector: &metav1.LabelSelector{
12047 MatchExpressions: []metav1.LabelSelectorRequirement{
12048 {
12049 Key: "key",
12050 Operator: metav1.LabelSelectorOpNotIn,
12051 Values: []string{"value1", "value2"},
12052 },
12053 },
12054 },
12055 TopologyKey: "k8s.io/zone",
12056 MismatchLabelKeys: []string{"/simple"},
12057 },
12058 },
12059 },
12060 },
12061 }),
12062 },
12063 },
12064 "invalid hard pod affinity, key in MismatchLabelKeys isn't correctly defined": {
12065 expectedError: "prefix part must be non-empty",
12066 spec: core.Pod{
12067 ObjectMeta: metav1.ObjectMeta{
12068 Name: "123",
12069 Namespace: "ns",
12070 },
12071 Spec: validPodSpec(&core.Affinity{
12072 PodAffinity: &core.PodAffinity{
12073 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12074 {
12075 LabelSelector: &metav1.LabelSelector{
12076 MatchExpressions: []metav1.LabelSelectorRequirement{
12077 {
12078 Key: "key",
12079 Operator: metav1.LabelSelectorOpNotIn,
12080 Values: []string{"value1", "value2"},
12081 },
12082 },
12083 },
12084 TopologyKey: "k8s.io/zone",
12085 MismatchLabelKeys: []string{"/simple"},
12086 },
12087 },
12088 },
12089 }),
12090 },
12091 },
12092 "invalid soft pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
12093 expectedError: "prefix part must be non-empty",
12094 spec: core.Pod{
12095 ObjectMeta: metav1.ObjectMeta{
12096 Name: "123",
12097 Namespace: "ns",
12098 },
12099 Spec: validPodSpec(&core.Affinity{
12100 PodAntiAffinity: &core.PodAntiAffinity{
12101 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
12102 {
12103 Weight: 10,
12104 PodAffinityTerm: core.PodAffinityTerm{
12105 LabelSelector: &metav1.LabelSelector{
12106 MatchExpressions: []metav1.LabelSelectorRequirement{
12107 {
12108 Key: "key",
12109 Operator: metav1.LabelSelectorOpNotIn,
12110 Values: []string{"value1", "value2"},
12111 },
12112 },
12113 },
12114 TopologyKey: "k8s.io/zone",
12115 MismatchLabelKeys: []string{"/simple"},
12116 },
12117 },
12118 },
12119 },
12120 }),
12121 },
12122 },
12123 "invalid hard pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
12124 expectedError: "prefix part must be non-empty",
12125 spec: core.Pod{
12126 ObjectMeta: metav1.ObjectMeta{
12127 Name: "123",
12128 Namespace: "ns",
12129 },
12130 Spec: validPodSpec(&core.Affinity{
12131 PodAntiAffinity: &core.PodAntiAffinity{
12132 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12133 {
12134 LabelSelector: &metav1.LabelSelector{
12135 MatchExpressions: []metav1.LabelSelectorRequirement{
12136 {
12137 Key: "key",
12138 Operator: metav1.LabelSelectorOpNotIn,
12139 Values: []string{"value1", "value2"},
12140 },
12141 },
12142 },
12143 TopologyKey: "k8s.io/zone",
12144 MismatchLabelKeys: []string{"/simple"},
12145 },
12146 },
12147 },
12148 }),
12149 },
12150 },
12151 "invalid soft pod affinity, key exists in both matchLabelKeys and labelSelector": {
12152 expectedError: "exists in both matchLabelKeys and labelSelector",
12153 spec: core.Pod{
12154 ObjectMeta: metav1.ObjectMeta{
12155 Name: "123",
12156 Namespace: "ns",
12157 Labels: map[string]string{"key": "value1"},
12158 },
12159 Spec: validPodSpec(&core.Affinity{
12160 PodAffinity: &core.PodAffinity{
12161 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
12162 {
12163 Weight: 10,
12164 PodAffinityTerm: core.PodAffinityTerm{
12165 LabelSelector: &metav1.LabelSelector{
12166 MatchExpressions: []metav1.LabelSelectorRequirement{
12167
12168 {
12169 Key: "key",
12170 Operator: metav1.LabelSelectorOpIn,
12171 Values: []string{"value1"},
12172 },
12173 {
12174 Key: "key",
12175 Operator: metav1.LabelSelectorOpNotIn,
12176 Values: []string{"value2"},
12177 },
12178 },
12179 },
12180 TopologyKey: "k8s.io/zone",
12181 MatchLabelKeys: []string{"key"},
12182 },
12183 },
12184 },
12185 },
12186 }),
12187 },
12188 },
12189 "invalid hard pod affinity, key exists in both matchLabelKeys and labelSelector": {
12190 expectedError: "exists in both matchLabelKeys and labelSelector",
12191 spec: core.Pod{
12192 ObjectMeta: metav1.ObjectMeta{
12193 Name: "123",
12194 Namespace: "ns",
12195 Labels: map[string]string{"key": "value1"},
12196 },
12197 Spec: validPodSpec(&core.Affinity{
12198 PodAffinity: &core.PodAffinity{
12199 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12200 {
12201 LabelSelector: &metav1.LabelSelector{
12202 MatchExpressions: []metav1.LabelSelectorRequirement{
12203
12204 {
12205 Key: "key",
12206 Operator: metav1.LabelSelectorOpIn,
12207 Values: []string{"value1"},
12208 },
12209 {
12210 Key: "key",
12211 Operator: metav1.LabelSelectorOpNotIn,
12212 Values: []string{"value2"},
12213 },
12214 },
12215 },
12216 TopologyKey: "k8s.io/zone",
12217 MatchLabelKeys: []string{"key"},
12218 },
12219 },
12220 },
12221 }),
12222 },
12223 },
12224 "invalid soft pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
12225 expectedError: "exists in both matchLabelKeys and labelSelector",
12226 spec: core.Pod{
12227 ObjectMeta: metav1.ObjectMeta{
12228 Name: "123",
12229 Namespace: "ns",
12230 Labels: map[string]string{"key": "value1"},
12231 },
12232 Spec: validPodSpec(&core.Affinity{
12233 PodAntiAffinity: &core.PodAntiAffinity{
12234 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
12235 {
12236 Weight: 10,
12237 PodAffinityTerm: core.PodAffinityTerm{
12238 LabelSelector: &metav1.LabelSelector{
12239 MatchExpressions: []metav1.LabelSelectorRequirement{
12240
12241 {
12242 Key: "key",
12243 Operator: metav1.LabelSelectorOpIn,
12244 Values: []string{"value1"},
12245 },
12246 {
12247 Key: "key",
12248 Operator: metav1.LabelSelectorOpNotIn,
12249 Values: []string{"value2"},
12250 },
12251 },
12252 },
12253 TopologyKey: "k8s.io/zone",
12254 MatchLabelKeys: []string{"key"},
12255 },
12256 },
12257 },
12258 },
12259 }),
12260 },
12261 },
12262 "invalid hard pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
12263 expectedError: "exists in both matchLabelKeys and labelSelector",
12264 spec: core.Pod{
12265 ObjectMeta: metav1.ObjectMeta{
12266 Name: "123",
12267 Namespace: "ns",
12268 Labels: map[string]string{"key": "value1"},
12269 },
12270 Spec: validPodSpec(&core.Affinity{
12271 PodAntiAffinity: &core.PodAntiAffinity{
12272 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12273 {
12274 LabelSelector: &metav1.LabelSelector{
12275 MatchExpressions: []metav1.LabelSelectorRequirement{
12276
12277 {
12278 Key: "key",
12279 Operator: metav1.LabelSelectorOpIn,
12280 Values: []string{"value1"},
12281 },
12282 {
12283 Key: "key",
12284 Operator: metav1.LabelSelectorOpNotIn,
12285 Values: []string{"value2"},
12286 },
12287 },
12288 },
12289 TopologyKey: "k8s.io/zone",
12290 MatchLabelKeys: []string{"key"},
12291 },
12292 },
12293 },
12294 }),
12295 },
12296 },
12297 "invalid soft pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
12298 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
12299 spec: core.Pod{
12300 ObjectMeta: metav1.ObjectMeta{
12301 Name: "123",
12302 Namespace: "ns",
12303 },
12304 Spec: validPodSpec(&core.Affinity{
12305 PodAffinity: &core.PodAffinity{
12306 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
12307 {
12308 Weight: 10,
12309 PodAffinityTerm: core.PodAffinityTerm{
12310 LabelSelector: &metav1.LabelSelector{
12311 MatchExpressions: []metav1.LabelSelectorRequirement{
12312 {
12313 Key: "key",
12314 Operator: metav1.LabelSelectorOpNotIn,
12315 Values: []string{"value1", "value2"},
12316 },
12317 },
12318 },
12319 TopologyKey: "k8s.io/zone",
12320 MatchLabelKeys: []string{"samekey"},
12321 MismatchLabelKeys: []string{"samekey"},
12322 },
12323 },
12324 },
12325 },
12326 }),
12327 },
12328 },
12329 "invalid hard pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
12330 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
12331 spec: core.Pod{
12332 ObjectMeta: metav1.ObjectMeta{
12333 Name: "123",
12334 Namespace: "ns",
12335 },
12336 Spec: validPodSpec(&core.Affinity{
12337 PodAffinity: &core.PodAffinity{
12338 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12339 {
12340 LabelSelector: &metav1.LabelSelector{
12341 MatchExpressions: []metav1.LabelSelectorRequirement{
12342 {
12343 Key: "key",
12344 Operator: metav1.LabelSelectorOpNotIn,
12345 Values: []string{"value1", "value2"},
12346 },
12347 },
12348 },
12349 TopologyKey: "k8s.io/zone",
12350 MatchLabelKeys: []string{"samekey"},
12351 MismatchLabelKeys: []string{"samekey"},
12352 },
12353 },
12354 },
12355 }),
12356 },
12357 },
12358 "invalid soft pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
12359 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
12360 spec: core.Pod{
12361 ObjectMeta: metav1.ObjectMeta{
12362 Name: "123",
12363 Namespace: "ns",
12364 },
12365 Spec: validPodSpec(&core.Affinity{
12366 PodAntiAffinity: &core.PodAntiAffinity{
12367 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
12368 {
12369 Weight: 10,
12370 PodAffinityTerm: core.PodAffinityTerm{
12371 LabelSelector: &metav1.LabelSelector{
12372 MatchExpressions: []metav1.LabelSelectorRequirement{
12373 {
12374 Key: "key",
12375 Operator: metav1.LabelSelectorOpNotIn,
12376 Values: []string{"value1", "value2"},
12377 },
12378 },
12379 },
12380 TopologyKey: "k8s.io/zone",
12381 MatchLabelKeys: []string{"samekey"},
12382 MismatchLabelKeys: []string{"samekey"},
12383 },
12384 },
12385 },
12386 },
12387 }),
12388 },
12389 },
12390 "invalid hard pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
12391 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
12392 spec: core.Pod{
12393 ObjectMeta: metav1.ObjectMeta{
12394 Name: "123",
12395 Namespace: "ns",
12396 },
12397 Spec: validPodSpec(&core.Affinity{
12398 PodAntiAffinity: &core.PodAntiAffinity{
12399 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
12400 {
12401 LabelSelector: &metav1.LabelSelector{
12402 MatchExpressions: []metav1.LabelSelectorRequirement{
12403 {
12404 Key: "key",
12405 Operator: metav1.LabelSelectorOpNotIn,
12406 Values: []string{"value1", "value2"},
12407 },
12408 },
12409 },
12410 TopologyKey: "k8s.io/zone",
12411 MatchLabelKeys: []string{"samekey"},
12412 MismatchLabelKeys: []string{"samekey"},
12413 },
12414 },
12415 },
12416 }),
12417 },
12418 },
12419 "invalid toleration key": {
12420 expectedError: "spec.tolerations[0].key",
12421 spec: core.Pod{
12422 ObjectMeta: metav1.ObjectMeta{
12423 Name: "123",
12424 Namespace: "ns",
12425 },
12426 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
12427 },
12428 },
12429 "invalid toleration operator": {
12430 expectedError: "spec.tolerations[0].operator",
12431 spec: core.Pod{
12432 ObjectMeta: metav1.ObjectMeta{
12433 Name: "123",
12434 Namespace: "ns",
12435 },
12436 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}),
12437 },
12438 },
12439 "value must be empty when `operator` is 'Exists'": {
12440 expectedError: "spec.tolerations[0].operator",
12441 spec: core.Pod{
12442 ObjectMeta: metav1.ObjectMeta{
12443 Name: "123",
12444 Namespace: "ns",
12445 },
12446 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}),
12447 },
12448 },
12449
12450 "operator must be 'Exists' when `key` is empty": {
12451 expectedError: "spec.tolerations[0].operator",
12452 spec: core.Pod{
12453 ObjectMeta: metav1.ObjectMeta{
12454 Name: "123",
12455 Namespace: "ns",
12456 },
12457 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
12458 },
12459 },
12460 "effect must be 'NoExecute' when `TolerationSeconds` is set": {
12461 expectedError: "spec.tolerations[0].effect",
12462 spec: core.Pod{
12463 ObjectMeta: metav1.ObjectMeta{
12464 Name: "pod-forgiveness-invalid",
12465 Namespace: "ns",
12466 },
12467 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}),
12468 },
12469 },
12470 "must be a valid pod seccomp profile": {
12471 expectedError: "must be a valid seccomp profile",
12472 spec: core.Pod{
12473 ObjectMeta: metav1.ObjectMeta{
12474 Name: "123",
12475 Namespace: "ns",
12476 Annotations: map[string]string{
12477 core.SeccompPodAnnotationKey: "foo",
12478 },
12479 },
12480 Spec: validPodSpec(nil),
12481 },
12482 },
12483 "must be a valid container seccomp profile": {
12484 expectedError: "must be a valid seccomp profile",
12485 spec: core.Pod{
12486 ObjectMeta: metav1.ObjectMeta{
12487 Name: "123",
12488 Namespace: "ns",
12489 Annotations: map[string]string{
12490 core.SeccompContainerAnnotationKeyPrefix + "foo": "foo",
12491 },
12492 },
12493 Spec: validPodSpec(nil),
12494 },
12495 },
12496 "must be a non-empty container name in seccomp annotation": {
12497 expectedError: "name part must be non-empty",
12498 spec: core.Pod{
12499 ObjectMeta: metav1.ObjectMeta{
12500 Name: "123",
12501 Namespace: "ns",
12502 Annotations: map[string]string{
12503 core.SeccompContainerAnnotationKeyPrefix: "foo",
12504 },
12505 },
12506 Spec: validPodSpec(nil),
12507 },
12508 },
12509 "must be a non-empty container profile in seccomp annotation": {
12510 expectedError: "must be a valid seccomp profile",
12511 spec: core.Pod{
12512 ObjectMeta: metav1.ObjectMeta{
12513 Name: "123",
12514 Namespace: "ns",
12515 Annotations: map[string]string{
12516 core.SeccompContainerAnnotationKeyPrefix + "foo": "",
12517 },
12518 },
12519 Spec: validPodSpec(nil),
12520 },
12521 },
12522 "must match seccomp profile type and pod annotation": {
12523 expectedError: "seccomp type in annotation and field must match",
12524 spec: core.Pod{
12525 ObjectMeta: metav1.ObjectMeta{
12526 Name: "123",
12527 Namespace: "ns",
12528 Annotations: map[string]string{
12529 core.SeccompPodAnnotationKey: "unconfined",
12530 },
12531 },
12532 Spec: core.PodSpec{
12533 SecurityContext: &core.PodSecurityContext{
12534 SeccompProfile: &core.SeccompProfile{
12535 Type: core.SeccompProfileTypeRuntimeDefault,
12536 },
12537 },
12538 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12539 RestartPolicy: core.RestartPolicyAlways,
12540 DNSPolicy: core.DNSClusterFirst,
12541 },
12542 },
12543 },
12544 "must match seccomp profile type and container annotation": {
12545 expectedError: "seccomp type in annotation and field must match",
12546 spec: core.Pod{
12547 ObjectMeta: metav1.ObjectMeta{
12548 Name: "123",
12549 Namespace: "ns",
12550 Annotations: map[string]string{
12551 core.SeccompContainerAnnotationKeyPrefix + "ctr": "unconfined",
12552 },
12553 },
12554 Spec: core.PodSpec{
12555 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
12556 SecurityContext: &core.SecurityContext{
12557 SeccompProfile: &core.SeccompProfile{
12558 Type: core.SeccompProfileTypeRuntimeDefault,
12559 },
12560 }}},
12561 RestartPolicy: core.RestartPolicyAlways,
12562 DNSPolicy: core.DNSClusterFirst,
12563 },
12564 },
12565 },
12566 "must be a relative path in a node-local seccomp profile annotation": {
12567 expectedError: "must be a relative path",
12568 spec: core.Pod{
12569 ObjectMeta: metav1.ObjectMeta{
12570 Name: "123",
12571 Namespace: "ns",
12572 Annotations: map[string]string{
12573 core.SeccompPodAnnotationKey: "localhost//foo",
12574 },
12575 },
12576 Spec: validPodSpec(nil),
12577 },
12578 },
12579 "must not start with '../'": {
12580 expectedError: "must not contain '..'",
12581 spec: core.Pod{
12582 ObjectMeta: metav1.ObjectMeta{
12583 Name: "123",
12584 Namespace: "ns",
12585 Annotations: map[string]string{
12586 core.SeccompPodAnnotationKey: "localhost/../foo",
12587 },
12588 },
12589 Spec: validPodSpec(nil),
12590 },
12591 },
12592 "AppArmor profile must apply to a container": {
12593 expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]",
12594 spec: core.Pod{
12595 ObjectMeta: metav1.ObjectMeta{
12596 Name: "123",
12597 Namespace: "ns",
12598 Annotations: map[string]string{
12599 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
12600 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
12601 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
12602 },
12603 },
12604 Spec: core.PodSpec{
12605 InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12606 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12607 RestartPolicy: core.RestartPolicyAlways,
12608 DNSPolicy: core.DNSClusterFirst,
12609 },
12610 },
12611 },
12612 "AppArmor profile format must be valid": {
12613 expectedError: "invalid AppArmor profile name",
12614 spec: core.Pod{
12615 ObjectMeta: metav1.ObjectMeta{
12616 Name: "123",
12617 Namespace: "ns",
12618 Annotations: map[string]string{
12619 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name",
12620 },
12621 },
12622 Spec: validPodSpec(nil),
12623 },
12624 },
12625 "only default AppArmor profile may start with runtime/": {
12626 expectedError: "invalid AppArmor profile name",
12627 spec: core.Pod{
12628 ObjectMeta: metav1.ObjectMeta{
12629 Name: "123",
12630 Namespace: "ns",
12631 Annotations: map[string]string{
12632 v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
12633 },
12634 },
12635 Spec: validPodSpec(nil),
12636 },
12637 },
12638 "unsupported pod AppArmor profile type": {
12639 expectedError: `Unsupported value: "test"`,
12640 spec: core.Pod{
12641 ObjectMeta: metav1.ObjectMeta{
12642 Name: "123",
12643 Namespace: "ns",
12644 },
12645 Spec: core.PodSpec{
12646 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12647 RestartPolicy: core.RestartPolicyAlways,
12648 DNSPolicy: core.DNSDefault,
12649 SecurityContext: &core.PodSecurityContext{
12650 AppArmorProfile: &core.AppArmorProfile{
12651 Type: "test",
12652 },
12653 },
12654 },
12655 },
12656 },
12657 "unsupported container AppArmor profile type": {
12658 expectedError: `Unsupported value: "test"`,
12659 spec: core.Pod{
12660 ObjectMeta: metav1.ObjectMeta{
12661 Name: "123",
12662 Namespace: "ns",
12663 },
12664 Spec: core.PodSpec{
12665 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
12666 SecurityContext: &core.SecurityContext{
12667 AppArmorProfile: &core.AppArmorProfile{
12668 Type: "test",
12669 },
12670 },
12671 }},
12672 RestartPolicy: core.RestartPolicyAlways,
12673 DNSPolicy: core.DNSDefault,
12674 },
12675 },
12676 },
12677 "missing pod AppArmor profile type": {
12678 expectedError: "Required value: type is required when appArmorProfile is set",
12679 spec: core.Pod{
12680 ObjectMeta: metav1.ObjectMeta{
12681 Name: "123",
12682 Namespace: "ns",
12683 },
12684 Spec: core.PodSpec{
12685 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12686 RestartPolicy: core.RestartPolicyAlways,
12687 DNSPolicy: core.DNSDefault,
12688 SecurityContext: &core.PodSecurityContext{
12689 AppArmorProfile: &core.AppArmorProfile{
12690 Type: "",
12691 },
12692 },
12693 },
12694 },
12695 },
12696 "missing AppArmor localhost profile": {
12697 expectedError: "Required value: must be set when AppArmor type is Localhost",
12698 spec: core.Pod{
12699 ObjectMeta: metav1.ObjectMeta{
12700 Name: "123",
12701 Namespace: "ns",
12702 },
12703 Spec: core.PodSpec{
12704 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12705 RestartPolicy: core.RestartPolicyAlways,
12706 DNSPolicy: core.DNSDefault,
12707 SecurityContext: &core.PodSecurityContext{
12708 AppArmorProfile: &core.AppArmorProfile{
12709 Type: core.AppArmorProfileTypeLocalhost,
12710 },
12711 },
12712 },
12713 },
12714 },
12715 "empty AppArmor localhost profile": {
12716 expectedError: "Required value: must be set when AppArmor type is Localhost",
12717 spec: core.Pod{
12718 ObjectMeta: metav1.ObjectMeta{
12719 Name: "123",
12720 Namespace: "ns",
12721 },
12722 Spec: core.PodSpec{
12723 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12724 RestartPolicy: core.RestartPolicyAlways,
12725 DNSPolicy: core.DNSDefault,
12726 SecurityContext: &core.PodSecurityContext{
12727 AppArmorProfile: &core.AppArmorProfile{
12728 Type: core.AppArmorProfileTypeLocalhost,
12729 LocalhostProfile: ptr.To(""),
12730 },
12731 },
12732 },
12733 },
12734 },
12735 "invalid AppArmor localhost profile type": {
12736 expectedError: `Invalid value: "foo-bar"`,
12737 spec: core.Pod{
12738 ObjectMeta: metav1.ObjectMeta{
12739 Name: "123",
12740 Namespace: "ns",
12741 },
12742 Spec: core.PodSpec{
12743 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12744 RestartPolicy: core.RestartPolicyAlways,
12745 DNSPolicy: core.DNSDefault,
12746 SecurityContext: &core.PodSecurityContext{
12747 AppArmorProfile: &core.AppArmorProfile{
12748 Type: core.AppArmorProfileTypeRuntimeDefault,
12749 LocalhostProfile: ptr.To("foo-bar"),
12750 },
12751 },
12752 },
12753 },
12754 },
12755 "invalid AppArmor localhost profile": {
12756 expectedError: `Invalid value: "foo-bar "`,
12757 spec: core.Pod{
12758 ObjectMeta: metav1.ObjectMeta{
12759 Name: "123",
12760 Namespace: "ns",
12761 },
12762 Spec: core.PodSpec{
12763 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12764 RestartPolicy: core.RestartPolicyAlways,
12765 DNSPolicy: core.DNSDefault,
12766 SecurityContext: &core.PodSecurityContext{
12767 AppArmorProfile: &core.AppArmorProfile{
12768 Type: core.AppArmorProfileTypeLocalhost,
12769 LocalhostProfile: ptr.To("foo-bar "),
12770 },
12771 },
12772 },
12773 },
12774 },
12775 "too long AppArmor localhost profile": {
12776 expectedError: "Too long: may not be longer than 4095",
12777 spec: core.Pod{
12778 ObjectMeta: metav1.ObjectMeta{
12779 Name: "123",
12780 Namespace: "ns",
12781 },
12782 Spec: core.PodSpec{
12783 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12784 RestartPolicy: core.RestartPolicyAlways,
12785 DNSPolicy: core.DNSDefault,
12786 SecurityContext: &core.PodSecurityContext{
12787 AppArmorProfile: &core.AppArmorProfile{
12788 Type: core.AppArmorProfileTypeLocalhost,
12789 LocalhostProfile: ptr.To(strings.Repeat("a", 4096)),
12790 },
12791 },
12792 },
12793 },
12794 },
12795 "mismatched AppArmor field and annotation types": {
12796 expectedError: "Forbidden: apparmor type in annotation and field must match",
12797 spec: core.Pod{
12798 ObjectMeta: metav1.ObjectMeta{
12799 Name: "123",
12800 Namespace: "ns",
12801 Annotations: map[string]string{
12802 core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
12803 },
12804 },
12805 Spec: core.PodSpec{
12806 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
12807 SecurityContext: &core.SecurityContext{
12808 AppArmorProfile: &core.AppArmorProfile{
12809 Type: core.AppArmorProfileTypeUnconfined,
12810 },
12811 },
12812 }},
12813 RestartPolicy: core.RestartPolicyAlways,
12814 DNSPolicy: core.DNSDefault,
12815 },
12816 },
12817 },
12818 "mismatched AppArmor pod field and annotation types": {
12819 expectedError: "Forbidden: apparmor type in annotation and field must match",
12820 spec: core.Pod{
12821 ObjectMeta: metav1.ObjectMeta{
12822 Name: "123",
12823 Namespace: "ns",
12824 Annotations: map[string]string{
12825 core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
12826 },
12827 },
12828 Spec: core.PodSpec{
12829 SecurityContext: &core.PodSecurityContext{
12830 AppArmorProfile: &core.AppArmorProfile{
12831 Type: core.AppArmorProfileTypeUnconfined,
12832 },
12833 },
12834 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12835 RestartPolicy: core.RestartPolicyAlways,
12836 DNSPolicy: core.DNSDefault,
12837 },
12838 },
12839 },
12840 "mismatched AppArmor localhost profiles": {
12841 expectedError: "Forbidden: apparmor profile in annotation and field must match",
12842 spec: core.Pod{
12843 ObjectMeta: metav1.ObjectMeta{
12844 Name: "123",
12845 Namespace: "ns",
12846 Annotations: map[string]string{
12847 core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
12848 },
12849 },
12850 Spec: core.PodSpec{
12851 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
12852 SecurityContext: &core.SecurityContext{
12853 AppArmorProfile: &core.AppArmorProfile{
12854 Type: core.AppArmorProfileTypeLocalhost,
12855 LocalhostProfile: ptr.To("bar"),
12856 },
12857 },
12858 }},
12859 RestartPolicy: core.RestartPolicyAlways,
12860 DNSPolicy: core.DNSDefault,
12861 },
12862 },
12863 },
12864 "invalid extended resource name in container request": {
12865 expectedError: "must be a standard resource for containers",
12866 spec: core.Pod{
12867 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12868 Spec: core.PodSpec{
12869 Containers: []core.Container{{
12870 Name: "invalid",
12871 Image: "image",
12872 ImagePullPolicy: "IfNotPresent",
12873 Resources: core.ResourceRequirements{
12874 Requests: core.ResourceList{
12875 core.ResourceName("invalid-name"): resource.MustParse("2"),
12876 },
12877 Limits: core.ResourceList{
12878 core.ResourceName("invalid-name"): resource.MustParse("2"),
12879 },
12880 },
12881 }},
12882 RestartPolicy: core.RestartPolicyAlways,
12883 DNSPolicy: core.DNSClusterFirst,
12884 },
12885 },
12886 },
12887 "invalid extended resource requirement: request must be == limit": {
12888 expectedError: "must be equal to example.com/a",
12889 spec: core.Pod{
12890 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12891 Spec: core.PodSpec{
12892 Containers: []core.Container{{
12893 Name: "invalid",
12894 Image: "image",
12895 ImagePullPolicy: "IfNotPresent",
12896 Resources: core.ResourceRequirements{
12897 Requests: core.ResourceList{
12898 core.ResourceName("example.com/a"): resource.MustParse("2"),
12899 },
12900 Limits: core.ResourceList{
12901 core.ResourceName("example.com/a"): resource.MustParse("1"),
12902 },
12903 },
12904 }},
12905 RestartPolicy: core.RestartPolicyAlways,
12906 DNSPolicy: core.DNSClusterFirst,
12907 },
12908 },
12909 },
12910 "invalid extended resource requirement without limit": {
12911 expectedError: "Limit must be set",
12912 spec: core.Pod{
12913 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12914 Spec: core.PodSpec{
12915 Containers: []core.Container{{
12916 Name: "invalid",
12917 Image: "image",
12918 ImagePullPolicy: "IfNotPresent",
12919 Resources: core.ResourceRequirements{
12920 Requests: core.ResourceList{
12921 core.ResourceName("example.com/a"): resource.MustParse("2"),
12922 },
12923 },
12924 }},
12925 RestartPolicy: core.RestartPolicyAlways,
12926 DNSPolicy: core.DNSClusterFirst,
12927 },
12928 },
12929 },
12930 "invalid fractional extended resource in container request": {
12931 expectedError: "must be an integer",
12932 spec: core.Pod{
12933 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12934 Spec: core.PodSpec{
12935 Containers: []core.Container{{
12936 Name: "invalid",
12937 Image: "image",
12938 ImagePullPolicy: "IfNotPresent",
12939 Resources: core.ResourceRequirements{
12940 Requests: core.ResourceList{
12941 core.ResourceName("example.com/a"): resource.MustParse("500m"),
12942 },
12943 },
12944 }},
12945 RestartPolicy: core.RestartPolicyAlways,
12946 DNSPolicy: core.DNSClusterFirst,
12947 },
12948 },
12949 },
12950 "invalid fractional extended resource in init container request": {
12951 expectedError: "must be an integer",
12952 spec: core.Pod{
12953 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12954 Spec: core.PodSpec{
12955 InitContainers: []core.Container{{
12956 Name: "invalid",
12957 Image: "image",
12958 ImagePullPolicy: "IfNotPresent",
12959 Resources: core.ResourceRequirements{
12960 Requests: core.ResourceList{
12961 core.ResourceName("example.com/a"): resource.MustParse("500m"),
12962 },
12963 },
12964 }},
12965 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
12966 RestartPolicy: core.RestartPolicyAlways,
12967 DNSPolicy: core.DNSClusterFirst,
12968 },
12969 },
12970 },
12971 "invalid fractional extended resource in container limit": {
12972 expectedError: "must be an integer",
12973 spec: core.Pod{
12974 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12975 Spec: core.PodSpec{
12976 Containers: []core.Container{{
12977 Name: "invalid",
12978 Image: "image",
12979 ImagePullPolicy: "IfNotPresent",
12980 Resources: core.ResourceRequirements{
12981 Requests: core.ResourceList{
12982 core.ResourceName("example.com/a"): resource.MustParse("5"),
12983 },
12984 Limits: core.ResourceList{
12985 core.ResourceName("example.com/a"): resource.MustParse("2.5"),
12986 },
12987 },
12988 }},
12989 RestartPolicy: core.RestartPolicyAlways,
12990 DNSPolicy: core.DNSClusterFirst,
12991 },
12992 },
12993 },
12994 "invalid fractional extended resource in init container limit": {
12995 expectedError: "must be an integer",
12996 spec: core.Pod{
12997 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
12998 Spec: core.PodSpec{
12999 InitContainers: []core.Container{{
13000 Name: "invalid",
13001 Image: "image",
13002 ImagePullPolicy: "IfNotPresent",
13003 Resources: core.ResourceRequirements{
13004 Requests: core.ResourceList{
13005 core.ResourceName("example.com/a"): resource.MustParse("2.5"),
13006 },
13007 Limits: core.ResourceList{
13008 core.ResourceName("example.com/a"): resource.MustParse("2.5"),
13009 },
13010 },
13011 }},
13012 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13013 RestartPolicy: core.RestartPolicyAlways,
13014 DNSPolicy: core.DNSClusterFirst,
13015 },
13016 },
13017 },
13018 "mirror-pod present without nodeName": {
13019 expectedError: "mirror",
13020 spec: core.Pod{
13021 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}},
13022 Spec: core.PodSpec{
13023 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13024 RestartPolicy: core.RestartPolicyAlways,
13025 DNSPolicy: core.DNSClusterFirst,
13026 },
13027 },
13028 },
13029 "mirror-pod populated without nodeName": {
13030 expectedError: "mirror",
13031 spec: core.Pod{
13032 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}},
13033 Spec: core.PodSpec{
13034 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13035 RestartPolicy: core.RestartPolicyAlways,
13036 DNSPolicy: core.DNSClusterFirst,
13037 },
13038 },
13039 },
13040 "serviceaccount token projected volume with no serviceaccount name specified": {
13041 expectedError: "must not be specified when serviceAccountName is not set",
13042 spec: core.Pod{
13043 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
13044 Spec: core.PodSpec{
13045 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13046 RestartPolicy: core.RestartPolicyAlways,
13047 DNSPolicy: core.DNSClusterFirst,
13048 Volumes: []core.Volume{{
13049 Name: "projected-volume",
13050 VolumeSource: core.VolumeSource{
13051 Projected: &core.ProjectedVolumeSource{
13052 Sources: []core.VolumeProjection{{
13053 ServiceAccountToken: &core.ServiceAccountTokenProjection{
13054 Audience: "foo-audience",
13055 ExpirationSeconds: 6000,
13056 Path: "foo-path",
13057 },
13058 }},
13059 },
13060 },
13061 }},
13062 },
13063 },
13064 },
13065 "ClusterTrustBundlePEM projected volume using both byName and bySigner": {
13066 expectedError: "only one of name and signerName may be used",
13067 spec: core.Pod{
13068 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
13069 Spec: core.PodSpec{
13070 ServiceAccountName: "some-service-account",
13071 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13072 RestartPolicy: core.RestartPolicyAlways,
13073 DNSPolicy: core.DNSClusterFirst,
13074 Volumes: []core.Volume{
13075 {
13076 Name: "projected-volume",
13077 VolumeSource: core.VolumeSource{
13078 Projected: &core.ProjectedVolumeSource{
13079 Sources: []core.VolumeProjection{
13080 {
13081 ClusterTrustBundle: &core.ClusterTrustBundleProjection{
13082 Path: "foo-path",
13083 SignerName: utilpointer.String("example.com/foo"),
13084 LabelSelector: &metav1.LabelSelector{
13085 MatchLabels: map[string]string{
13086 "version": "live",
13087 },
13088 },
13089 Name: utilpointer.String("foo"),
13090 },
13091 },
13092 },
13093 },
13094 },
13095 },
13096 },
13097 },
13098 },
13099 },
13100 "ClusterTrustBundlePEM projected volume byName with no name": {
13101 expectedError: "must be a valid object name",
13102 spec: core.Pod{
13103 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
13104 Spec: core.PodSpec{
13105 ServiceAccountName: "some-service-account",
13106 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13107 RestartPolicy: core.RestartPolicyAlways,
13108 DNSPolicy: core.DNSClusterFirst,
13109 Volumes: []core.Volume{
13110 {
13111 Name: "projected-volume",
13112 VolumeSource: core.VolumeSource{
13113 Projected: &core.ProjectedVolumeSource{
13114 Sources: []core.VolumeProjection{
13115 {
13116 ClusterTrustBundle: &core.ClusterTrustBundleProjection{
13117 Path: "foo-path",
13118 Name: utilpointer.String(""),
13119 },
13120 },
13121 },
13122 },
13123 },
13124 },
13125 },
13126 },
13127 },
13128 },
13129 "ClusterTrustBundlePEM projected volume bySigner with no signer name": {
13130 expectedError: "must be a valid signer name",
13131 spec: core.Pod{
13132 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
13133 Spec: core.PodSpec{
13134 ServiceAccountName: "some-service-account",
13135 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13136 RestartPolicy: core.RestartPolicyAlways,
13137 DNSPolicy: core.DNSClusterFirst,
13138 Volumes: []core.Volume{
13139 {
13140 Name: "projected-volume",
13141 VolumeSource: core.VolumeSource{
13142 Projected: &core.ProjectedVolumeSource{
13143 Sources: []core.VolumeProjection{
13144 {
13145 ClusterTrustBundle: &core.ClusterTrustBundleProjection{
13146 Path: "foo-path",
13147 SignerName: utilpointer.String(""),
13148 LabelSelector: &metav1.LabelSelector{
13149 MatchLabels: map[string]string{
13150 "foo": "bar",
13151 },
13152 },
13153 },
13154 },
13155 },
13156 },
13157 },
13158 },
13159 },
13160 },
13161 },
13162 },
13163 "ClusterTrustBundlePEM projected volume bySigner with invalid signer name": {
13164 expectedError: "must be a fully qualified domain and path of the form",
13165 spec: core.Pod{
13166 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
13167 Spec: core.PodSpec{
13168 ServiceAccountName: "some-service-account",
13169 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13170 RestartPolicy: core.RestartPolicyAlways,
13171 DNSPolicy: core.DNSClusterFirst,
13172 Volumes: []core.Volume{
13173 {
13174 Name: "projected-volume",
13175 VolumeSource: core.VolumeSource{
13176 Projected: &core.ProjectedVolumeSource{
13177 Sources: []core.VolumeProjection{
13178 {
13179 ClusterTrustBundle: &core.ClusterTrustBundleProjection{
13180 Path: "foo-path",
13181 SignerName: utilpointer.String("example.com/foo/invalid"),
13182 },
13183 },
13184 },
13185 },
13186 },
13187 },
13188 },
13189 },
13190 },
13191 },
13192 "final PVC name for ephemeral volume must be valid": {
13193 expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters",
13194 spec: core.Pod{
13195 ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"},
13196 Spec: core.PodSpec{
13197 Volumes: []core.Volume{
13198 {Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
13199 {Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
13200 },
13201 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13202 RestartPolicy: core.RestartPolicyAlways,
13203 DNSPolicy: core.DNSClusterFirst,
13204 },
13205 },
13206 },
13207 "PersistentVolumeClaimVolumeSource must not reference a generated PVC": {
13208 expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume",
13209 spec: core.Pod{
13210 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
13211 Spec: core.PodSpec{
13212 Volumes: []core.Volume{
13213 {Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}},
13214 {Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
13215 },
13216 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13217 RestartPolicy: core.RestartPolicyAlways,
13218 DNSPolicy: core.DNSClusterFirst,
13219 },
13220 },
13221 },
13222 "invalid pod-deletion-cost": {
13223 expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"text\": must be a 32bit integer",
13224 spec: core.Pod{
13225 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "text"}},
13226 Spec: core.PodSpec{
13227 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13228 RestartPolicy: core.RestartPolicyAlways,
13229 DNSPolicy: core.DNSClusterFirst,
13230 },
13231 },
13232 },
13233 "invalid leading zeros pod-deletion-cost": {
13234 expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"008\": must be a 32bit integer",
13235 spec: core.Pod{
13236 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "008"}},
13237 Spec: core.PodSpec{
13238 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13239 RestartPolicy: core.RestartPolicyAlways,
13240 DNSPolicy: core.DNSClusterFirst,
13241 },
13242 },
13243 },
13244 "invalid leading plus sign pod-deletion-cost": {
13245 expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"+10\": must be a 32bit integer",
13246 spec: core.Pod{
13247 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "+10"}},
13248 Spec: core.PodSpec{
13249 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
13250 RestartPolicy: core.RestartPolicyAlways,
13251 DNSPolicy: core.DNSClusterFirst,
13252 },
13253 },
13254 },
13255 }
13256 for k, v := range errorCases {
13257 t.Run(k, func(t *testing.T) {
13258 if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
13259 t.Errorf("expected failure")
13260 } else if v.expectedError == "" {
13261 t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
13262 } else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
13263 t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError)
13264 }
13265 })
13266 }
13267 }
13268
13269 func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
13270 applyEssentials := func(pod *core.Pod) {
13271 pod.Spec.Containers = []core.Container{
13272 {Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
13273 }
13274 pod.Spec.RestartPolicy = core.RestartPolicyAlways
13275 pod.Spec.DNSPolicy = core.DNSClusterFirst
13276 }
13277 fldPath := field.NewPath("spec")
13278
13279 tests := []struct {
13280 name string
13281 pod *core.Pod
13282 wantFieldErrors field.ErrorList
13283 }{{
13284 name: "create a Pod with nodeName and schedulingGates, feature enabled",
13285 pod: &core.Pod{
13286 ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
13287 Spec: core.PodSpec{
13288 NodeName: "node",
13289 SchedulingGates: []core.PodSchedulingGate{
13290 {Name: "foo"},
13291 },
13292 },
13293 },
13294 wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
13295 }, {
13296 name: "create a Pod with schedulingGates, feature enabled",
13297 pod: &core.Pod{
13298 ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
13299 Spec: core.PodSpec{
13300 SchedulingGates: []core.PodSchedulingGate{
13301 {Name: "foo"},
13302 },
13303 },
13304 },
13305 wantFieldErrors: nil,
13306 },
13307 }
13308
13309 for _, tt := range tests {
13310 t.Run(tt.name, func(t *testing.T) {
13311 applyEssentials(tt.pod)
13312 errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
13313 if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
13314 t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
13315 }
13316 })
13317 }
13318 }
13319
13320 func TestValidatePodUpdate(t *testing.T) {
13321 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)()
13322 var (
13323 activeDeadlineSecondsZero = int64(0)
13324 activeDeadlineSecondsNegative = int64(-30)
13325 activeDeadlineSecondsPositive = int64(30)
13326 activeDeadlineSecondsLarger = int64(31)
13327 validfsGroupChangePolicy = core.FSGroupChangeOnRootMismatch
13328
13329 now = metav1.Now()
13330 grace = int64(30)
13331 grace2 = int64(31)
13332 )
13333
13334 tests := []struct {
13335 new core.Pod
13336 old core.Pod
13337 err string
13338 test string
13339 }{
13340 {new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, {
13341 new: core.Pod{
13342 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13343 },
13344 old: core.Pod{
13345 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
13346 },
13347 err: "metadata.name",
13348 test: "ids",
13349 }, {
13350 new: core.Pod{
13351 ObjectMeta: metav1.ObjectMeta{
13352 Name: "foo",
13353 Labels: map[string]string{
13354 "foo": "bar",
13355 },
13356 },
13357 },
13358 old: core.Pod{
13359 ObjectMeta: metav1.ObjectMeta{
13360 Name: "foo",
13361 Labels: map[string]string{
13362 "bar": "foo",
13363 },
13364 },
13365 },
13366 err: "",
13367 test: "labels",
13368 }, {
13369 new: core.Pod{
13370 ObjectMeta: metav1.ObjectMeta{
13371 Name: "foo",
13372 Annotations: map[string]string{
13373 "foo": "bar",
13374 },
13375 },
13376 },
13377 old: core.Pod{
13378 ObjectMeta: metav1.ObjectMeta{
13379 Name: "foo",
13380 Annotations: map[string]string{
13381 "bar": "foo",
13382 },
13383 },
13384 },
13385 err: "",
13386 test: "annotations",
13387 }, {
13388 new: core.Pod{
13389 ObjectMeta: metav1.ObjectMeta{
13390 Name: "foo",
13391 },
13392 Spec: core.PodSpec{
13393 Containers: []core.Container{{
13394 Image: "foo:V1",
13395 }},
13396 },
13397 },
13398 old: core.Pod{
13399 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13400 Spec: core.PodSpec{
13401 Containers: []core.Container{{
13402 Image: "foo:V2",
13403 }, {
13404 Image: "bar:V2",
13405 }},
13406 },
13407 },
13408 err: "may not add or remove containers",
13409 test: "less containers",
13410 }, {
13411 new: core.Pod{
13412 ObjectMeta: metav1.ObjectMeta{
13413 Name: "foo",
13414 },
13415 Spec: core.PodSpec{
13416 Containers: []core.Container{{
13417 Image: "foo:V1",
13418 }, {
13419 Image: "bar:V2",
13420 }},
13421 },
13422 },
13423 old: core.Pod{
13424 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13425 Spec: core.PodSpec{
13426 Containers: []core.Container{{
13427 Image: "foo:V2",
13428 }},
13429 },
13430 },
13431 err: "may not add or remove containers",
13432 test: "more containers",
13433 }, {
13434 new: core.Pod{
13435 ObjectMeta: metav1.ObjectMeta{
13436 Name: "foo",
13437 },
13438 Spec: core.PodSpec{
13439 InitContainers: []core.Container{{
13440 Image: "foo:V1",
13441 }},
13442 },
13443 },
13444 old: core.Pod{
13445 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13446 Spec: core.PodSpec{
13447 InitContainers: []core.Container{{
13448 Image: "foo:V2",
13449 }, {
13450 Image: "bar:V2",
13451 }},
13452 },
13453 },
13454 err: "may not add or remove containers",
13455 test: "more init containers",
13456 }, {
13457 new: core.Pod{
13458 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13459 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
13460 },
13461 old: core.Pod{
13462 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
13463 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
13464 },
13465 err: "metadata.deletionTimestamp",
13466 test: "deletion timestamp removed",
13467 }, {
13468 new: core.Pod{
13469 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
13470 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
13471 },
13472 old: core.Pod{
13473 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13474 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
13475 },
13476 err: "metadata.deletionTimestamp",
13477 test: "deletion timestamp added",
13478 }, {
13479 new: core.Pod{
13480 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace},
13481 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
13482 },
13483 old: core.Pod{
13484 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2},
13485 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
13486 },
13487 err: "metadata.deletionGracePeriodSeconds",
13488 test: "deletion grace period seconds changed",
13489 }, {
13490 new: core.Pod{
13491 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13492 Spec: core.PodSpec{
13493 Containers: []core.Container{{
13494 Name: "container",
13495 Image: "foo:V1",
13496 TerminationMessagePolicy: "File",
13497 ImagePullPolicy: "Always",
13498 }},
13499 },
13500 },
13501 old: core.Pod{
13502 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13503 Spec: core.PodSpec{
13504 Containers: []core.Container{{
13505 Name: "container",
13506 Image: "foo:V2",
13507 TerminationMessagePolicy: "File",
13508 ImagePullPolicy: "Always",
13509 }},
13510 },
13511 },
13512 err: "",
13513 test: "image change",
13514 }, {
13515 new: core.Pod{
13516 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13517 Spec: core.PodSpec{
13518 InitContainers: []core.Container{{
13519 Name: "container",
13520 Image: "foo:V1",
13521 TerminationMessagePolicy: "File",
13522 ImagePullPolicy: "Always",
13523 }},
13524 },
13525 },
13526 old: core.Pod{
13527 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13528 Spec: core.PodSpec{
13529 InitContainers: []core.Container{{
13530 Name: "container",
13531 Image: "foo:V2",
13532 TerminationMessagePolicy: "File",
13533 ImagePullPolicy: "Always",
13534 }},
13535 },
13536 },
13537 err: "",
13538 test: "init container image change",
13539 }, {
13540 new: core.Pod{
13541 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13542 Spec: core.PodSpec{
13543 Containers: []core.Container{{
13544 Name: "container",
13545 TerminationMessagePolicy: "File",
13546 ImagePullPolicy: "Always",
13547 }},
13548 },
13549 },
13550 old: core.Pod{
13551 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13552 Spec: core.PodSpec{
13553 Containers: []core.Container{{
13554 Name: "container",
13555 Image: "foo:V2",
13556 TerminationMessagePolicy: "File",
13557 ImagePullPolicy: "Always",
13558 }},
13559 },
13560 },
13561 err: "spec.containers[0].image",
13562 test: "image change to empty",
13563 }, {
13564 new: core.Pod{
13565 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13566 Spec: core.PodSpec{
13567 InitContainers: []core.Container{{
13568 Name: "container",
13569 TerminationMessagePolicy: "File",
13570 ImagePullPolicy: "Always",
13571 }},
13572 },
13573 },
13574 old: core.Pod{
13575 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13576 Spec: core.PodSpec{
13577 InitContainers: []core.Container{{
13578 Name: "container",
13579 Image: "foo:V2",
13580 TerminationMessagePolicy: "File",
13581 ImagePullPolicy: "Always",
13582 }},
13583 },
13584 },
13585 err: "spec.initContainers[0].image",
13586 test: "init container image change to empty",
13587 }, {
13588 new: core.Pod{
13589 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13590 Spec: core.PodSpec{
13591 EphemeralContainers: []core.EphemeralContainer{{
13592 EphemeralContainerCommon: core.EphemeralContainerCommon{
13593 Name: "ephemeral",
13594 Image: "busybox",
13595 },
13596 }},
13597 },
13598 },
13599 old: core.Pod{
13600 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
13601 Spec: core.PodSpec{},
13602 },
13603 err: "Forbidden: pod updates may not change fields other than",
13604 test: "ephemeralContainer changes are not allowed via normal pod update",
13605 }, {
13606 new: core.Pod{
13607 Spec: core.PodSpec{},
13608 },
13609 old: core.Pod{
13610 Spec: core.PodSpec{},
13611 },
13612 err: "",
13613 test: "activeDeadlineSeconds no change, nil",
13614 }, {
13615 new: core.Pod{
13616 Spec: core.PodSpec{
13617 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13618 },
13619 },
13620 old: core.Pod{
13621 Spec: core.PodSpec{
13622 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13623 },
13624 },
13625 err: "",
13626 test: "activeDeadlineSeconds no change, set",
13627 }, {
13628 new: core.Pod{
13629 Spec: core.PodSpec{
13630 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13631 },
13632 },
13633 old: core.Pod{},
13634 err: "",
13635 test: "activeDeadlineSeconds change to positive from nil",
13636 }, {
13637 new: core.Pod{
13638 Spec: core.PodSpec{
13639 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13640 },
13641 },
13642 old: core.Pod{
13643 Spec: core.PodSpec{
13644 ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
13645 },
13646 },
13647 err: "",
13648 test: "activeDeadlineSeconds change to smaller positive",
13649 }, {
13650 new: core.Pod{
13651 Spec: core.PodSpec{
13652 ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
13653 },
13654 },
13655 old: core.Pod{
13656 Spec: core.PodSpec{
13657 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13658 },
13659 },
13660 err: "spec.activeDeadlineSeconds",
13661 test: "activeDeadlineSeconds change to larger positive",
13662 },
13663
13664 {
13665 new: core.Pod{
13666 Spec: core.PodSpec{
13667 ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
13668 },
13669 },
13670 old: core.Pod{},
13671 err: "spec.activeDeadlineSeconds",
13672 test: "activeDeadlineSeconds change to negative from nil",
13673 }, {
13674 new: core.Pod{
13675 Spec: core.PodSpec{
13676 ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
13677 },
13678 },
13679 old: core.Pod{
13680 Spec: core.PodSpec{
13681 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13682 },
13683 },
13684 err: "spec.activeDeadlineSeconds",
13685 test: "activeDeadlineSeconds change to negative from positive",
13686 }, {
13687 new: core.Pod{
13688 Spec: core.PodSpec{
13689 ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
13690 },
13691 },
13692 old: core.Pod{
13693 Spec: core.PodSpec{
13694 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13695 },
13696 },
13697 err: "spec.activeDeadlineSeconds",
13698 test: "activeDeadlineSeconds change to zero from positive",
13699 }, {
13700 new: core.Pod{
13701 Spec: core.PodSpec{
13702 ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
13703 },
13704 },
13705 old: core.Pod{},
13706 err: "spec.activeDeadlineSeconds",
13707 test: "activeDeadlineSeconds change to zero from nil",
13708 }, {
13709 new: core.Pod{},
13710 old: core.Pod{
13711 Spec: core.PodSpec{
13712 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
13713 },
13714 },
13715 err: "spec.activeDeadlineSeconds",
13716 test: "activeDeadlineSeconds change to nil from positive",
13717 }, {
13718 new: core.Pod{
13719 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13720 Spec: core.PodSpec{
13721 Containers: []core.Container{{
13722 Name: "container",
13723 TerminationMessagePolicy: "File",
13724 ImagePullPolicy: "Always",
13725 Image: "foo:V2",
13726 Resources: core.ResourceRequirements{
13727 Limits: getResources("200m", "0", "1Gi"),
13728 },
13729 }},
13730 },
13731 },
13732 old: core.Pod{
13733 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13734 Spec: core.PodSpec{
13735 Containers: []core.Container{{
13736 Name: "container",
13737 TerminationMessagePolicy: "File",
13738 ImagePullPolicy: "Always",
13739 Image: "foo:V2",
13740 Resources: core.ResourceRequirements{
13741 Limits: getResources("100m", "0", "1Gi"),
13742 },
13743 }},
13744 },
13745 },
13746 err: "",
13747 test: "cpu limit change",
13748 }, {
13749 new: core.Pod{
13750 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13751 Spec: core.PodSpec{
13752 Containers: []core.Container{{
13753 Name: "container",
13754 TerminationMessagePolicy: "File",
13755 ImagePullPolicy: "Always",
13756 Image: "foo:V1",
13757 Resources: core.ResourceRequirements{
13758 Limits: getResourceLimits("100m", "100Mi"),
13759 },
13760 }},
13761 },
13762 },
13763 old: core.Pod{
13764 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13765 Spec: core.PodSpec{
13766 Containers: []core.Container{{
13767 Name: "container",
13768 TerminationMessagePolicy: "File",
13769 ImagePullPolicy: "Always",
13770 Image: "foo:V2",
13771 Resources: core.ResourceRequirements{
13772 Limits: getResourceLimits("100m", "200Mi"),
13773 },
13774 }},
13775 },
13776 },
13777 err: "",
13778 test: "memory limit change",
13779 }, {
13780 new: core.Pod{
13781 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13782 Spec: core.PodSpec{
13783 Containers: []core.Container{{
13784 Name: "container",
13785 TerminationMessagePolicy: "File",
13786 ImagePullPolicy: "Always",
13787 Image: "foo:V1",
13788 Resources: core.ResourceRequirements{
13789 Limits: getResources("100m", "100Mi", "1Gi"),
13790 },
13791 }},
13792 },
13793 },
13794 old: core.Pod{
13795 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13796 Spec: core.PodSpec{
13797 Containers: []core.Container{{
13798 Name: "container",
13799 TerminationMessagePolicy: "File",
13800 ImagePullPolicy: "Always",
13801 Image: "foo:V2",
13802 Resources: core.ResourceRequirements{
13803 Limits: getResources("100m", "100Mi", "2Gi"),
13804 },
13805 }},
13806 },
13807 },
13808 err: "Forbidden: pod updates may not change fields other than",
13809 test: "storage limit change",
13810 }, {
13811 new: core.Pod{
13812 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13813 Spec: core.PodSpec{
13814 Containers: []core.Container{{
13815 Name: "container",
13816 TerminationMessagePolicy: "File",
13817 ImagePullPolicy: "Always",
13818 Image: "foo:V1",
13819 Resources: core.ResourceRequirements{
13820 Requests: getResourceLimits("100m", "0"),
13821 },
13822 }},
13823 },
13824 },
13825 old: core.Pod{
13826 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13827 Spec: core.PodSpec{
13828 Containers: []core.Container{{
13829 Name: "container",
13830 TerminationMessagePolicy: "File",
13831 ImagePullPolicy: "Always",
13832 Image: "foo:V2",
13833 Resources: core.ResourceRequirements{
13834 Requests: getResourceLimits("200m", "0"),
13835 },
13836 }},
13837 },
13838 },
13839 err: "",
13840 test: "cpu request change",
13841 }, {
13842 new: core.Pod{
13843 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13844 Spec: core.PodSpec{
13845 Containers: []core.Container{{
13846 Name: "container",
13847 TerminationMessagePolicy: "File",
13848 ImagePullPolicy: "Always",
13849 Image: "foo:V1",
13850 Resources: core.ResourceRequirements{
13851 Requests: getResourceLimits("0", "200Mi"),
13852 },
13853 }},
13854 },
13855 },
13856 old: core.Pod{
13857 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13858 Spec: core.PodSpec{
13859 Containers: []core.Container{{
13860 Name: "container",
13861 TerminationMessagePolicy: "File",
13862 ImagePullPolicy: "Always",
13863 Image: "foo:V2",
13864 Resources: core.ResourceRequirements{
13865 Requests: getResourceLimits("0", "100Mi"),
13866 },
13867 }},
13868 },
13869 },
13870 err: "",
13871 test: "memory request change",
13872 }, {
13873 new: core.Pod{
13874 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13875 Spec: core.PodSpec{
13876 Containers: []core.Container{{
13877 Name: "container",
13878 TerminationMessagePolicy: "File",
13879 ImagePullPolicy: "Always",
13880 Image: "foo:V1",
13881 Resources: core.ResourceRequirements{
13882 Requests: getResources("100m", "0", "2Gi"),
13883 },
13884 }},
13885 },
13886 },
13887 old: core.Pod{
13888 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13889 Spec: core.PodSpec{
13890 Containers: []core.Container{{
13891 Name: "container",
13892 TerminationMessagePolicy: "File",
13893 ImagePullPolicy: "Always",
13894 Image: "foo:V2",
13895 Resources: core.ResourceRequirements{
13896 Requests: getResources("100m", "0", "1Gi"),
13897 },
13898 }},
13899 },
13900 },
13901 err: "Forbidden: pod updates may not change fields other than",
13902 test: "storage request change",
13903 }, {
13904 new: core.Pod{
13905 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13906 Spec: core.PodSpec{
13907 Containers: []core.Container{{
13908 Name: "container",
13909 TerminationMessagePolicy: "File",
13910 ImagePullPolicy: "Always",
13911 Image: "foo:V1",
13912 Resources: core.ResourceRequirements{
13913 Limits: getResources("200m", "400Mi", "1Gi"),
13914 Requests: getResources("200m", "400Mi", "1Gi"),
13915 },
13916 }},
13917 },
13918 },
13919 old: core.Pod{
13920 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13921 Spec: core.PodSpec{
13922 Containers: []core.Container{{
13923 Name: "container",
13924 TerminationMessagePolicy: "File",
13925 ImagePullPolicy: "Always",
13926 Image: "foo:V1",
13927 Resources: core.ResourceRequirements{
13928 Limits: getResources("100m", "100Mi", "1Gi"),
13929 Requests: getResources("100m", "100Mi", "1Gi"),
13930 },
13931 }},
13932 },
13933 },
13934 err: "",
13935 test: "Pod QoS unchanged, guaranteed -> guaranteed",
13936 }, {
13937 new: core.Pod{
13938 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13939 Spec: core.PodSpec{
13940 Containers: []core.Container{{
13941 Name: "container",
13942 TerminationMessagePolicy: "File",
13943 ImagePullPolicy: "Always",
13944 Image: "foo:V1",
13945 Resources: core.ResourceRequirements{
13946 Limits: getResources("200m", "200Mi", "2Gi"),
13947 Requests: getResources("100m", "100Mi", "1Gi"),
13948 },
13949 }},
13950 },
13951 },
13952 old: core.Pod{
13953 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13954 Spec: core.PodSpec{
13955 Containers: []core.Container{{
13956 Name: "container",
13957 TerminationMessagePolicy: "File",
13958 ImagePullPolicy: "Always",
13959 Image: "foo:V1",
13960 Resources: core.ResourceRequirements{
13961 Limits: getResources("400m", "400Mi", "2Gi"),
13962 Requests: getResources("200m", "200Mi", "1Gi"),
13963 },
13964 }},
13965 },
13966 },
13967 err: "",
13968 test: "Pod QoS unchanged, burstable -> burstable",
13969 }, {
13970 new: core.Pod{
13971 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13972 Spec: core.PodSpec{
13973 Containers: []core.Container{{
13974 Name: "container",
13975 TerminationMessagePolicy: "File",
13976 ImagePullPolicy: "Always",
13977 Image: "foo:V2",
13978 Resources: core.ResourceRequirements{
13979 Limits: getResourceLimits("200m", "200Mi"),
13980 Requests: getResourceLimits("100m", "100Mi"),
13981 },
13982 }},
13983 },
13984 },
13985 old: core.Pod{
13986 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
13987 Spec: core.PodSpec{
13988 Containers: []core.Container{{
13989 Name: "container",
13990 TerminationMessagePolicy: "File",
13991 ImagePullPolicy: "Always",
13992 Image: "foo:V2",
13993 Resources: core.ResourceRequirements{
13994 Requests: getResourceLimits("100m", "100Mi"),
13995 },
13996 }},
13997 },
13998 },
13999 err: "",
14000 test: "Pod QoS unchanged, burstable -> burstable, add limits",
14001 }, {
14002 new: core.Pod{
14003 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14004 Spec: core.PodSpec{
14005 Containers: []core.Container{{
14006 Name: "container",
14007 TerminationMessagePolicy: "File",
14008 ImagePullPolicy: "Always",
14009 Image: "foo:V2",
14010 Resources: core.ResourceRequirements{
14011 Requests: getResourceLimits("100m", "100Mi"),
14012 },
14013 }},
14014 },
14015 },
14016 old: core.Pod{
14017 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14018 Spec: core.PodSpec{
14019 Containers: []core.Container{{
14020 Name: "container",
14021 TerminationMessagePolicy: "File",
14022 ImagePullPolicy: "Always",
14023 Image: "foo:V2",
14024 Resources: core.ResourceRequirements{
14025 Limits: getResourceLimits("200m", "200Mi"),
14026 Requests: getResourceLimits("100m", "100Mi"),
14027 },
14028 }},
14029 },
14030 },
14031 err: "",
14032 test: "Pod QoS unchanged, burstable -> burstable, remove limits",
14033 }, {
14034 new: core.Pod{
14035 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14036 Spec: core.PodSpec{
14037 Containers: []core.Container{{
14038 Name: "container",
14039 TerminationMessagePolicy: "File",
14040 ImagePullPolicy: "Always",
14041 Image: "foo:V2",
14042 Resources: core.ResourceRequirements{
14043 Limits: getResources("400m", "", "1Gi"),
14044 Requests: getResources("300m", "", "1Gi"),
14045 },
14046 }},
14047 },
14048 },
14049 old: core.Pod{
14050 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14051 Spec: core.PodSpec{
14052 Containers: []core.Container{{
14053 Name: "container",
14054 TerminationMessagePolicy: "File",
14055 ImagePullPolicy: "Always",
14056 Image: "foo:V2",
14057 Resources: core.ResourceRequirements{
14058 Limits: getResources("200m", "500Mi", "1Gi"),
14059 },
14060 }},
14061 },
14062 },
14063 err: "",
14064 test: "Pod QoS unchanged, burstable -> burstable, add requests",
14065 }, {
14066 new: core.Pod{
14067 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14068 Spec: core.PodSpec{
14069 Containers: []core.Container{{
14070 Name: "container",
14071 TerminationMessagePolicy: "File",
14072 ImagePullPolicy: "Always",
14073 Image: "foo:V2",
14074 Resources: core.ResourceRequirements{
14075 Limits: getResources("400m", "500Mi", "2Gi"),
14076 },
14077 }},
14078 },
14079 },
14080 old: core.Pod{
14081 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14082 Spec: core.PodSpec{
14083 Containers: []core.Container{{
14084 Name: "container",
14085 TerminationMessagePolicy: "File",
14086 ImagePullPolicy: "Always",
14087 Image: "foo:V2",
14088 Resources: core.ResourceRequirements{
14089 Limits: getResources("200m", "300Mi", "2Gi"),
14090 Requests: getResourceLimits("100m", "200Mi"),
14091 },
14092 }},
14093 },
14094 },
14095 err: "",
14096 test: "Pod QoS unchanged, burstable -> burstable, remove requests",
14097 }, {
14098 new: core.Pod{
14099 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14100 Spec: core.PodSpec{
14101 Containers: []core.Container{{
14102 Name: "container",
14103 TerminationMessagePolicy: "File",
14104 ImagePullPolicy: "Always",
14105 Image: "foo:V2",
14106 Resources: core.ResourceRequirements{
14107 Limits: getResourceLimits("200m", "200Mi"),
14108 Requests: getResourceLimits("100m", "100Mi"),
14109 },
14110 }},
14111 },
14112 },
14113 old: core.Pod{
14114 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14115 Spec: core.PodSpec{
14116 Containers: []core.Container{{
14117 Name: "container",
14118 TerminationMessagePolicy: "File",
14119 ImagePullPolicy: "Always",
14120 Image: "foo:V2",
14121 Resources: core.ResourceRequirements{
14122 Limits: getResourceLimits("100m", "100Mi"),
14123 Requests: getResourceLimits("100m", "100Mi"),
14124 },
14125 }},
14126 },
14127 },
14128 err: "Pod QoS is immutable",
14129 test: "Pod QoS change, guaranteed -> burstable",
14130 }, {
14131 new: core.Pod{
14132 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14133 Spec: core.PodSpec{
14134 Containers: []core.Container{{
14135 Name: "container",
14136 TerminationMessagePolicy: "File",
14137 ImagePullPolicy: "Always",
14138 Image: "foo:V2",
14139 Resources: core.ResourceRequirements{
14140 Limits: getResourceLimits("100m", "100Mi"),
14141 Requests: getResourceLimits("100m", "100Mi"),
14142 },
14143 }},
14144 },
14145 },
14146 old: core.Pod{
14147 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14148 Spec: core.PodSpec{
14149 Containers: []core.Container{{
14150 Name: "container",
14151 TerminationMessagePolicy: "File",
14152 ImagePullPolicy: "Always",
14153 Image: "foo:V2",
14154 Resources: core.ResourceRequirements{
14155 Requests: getResourceLimits("100m", "100Mi"),
14156 },
14157 }},
14158 },
14159 },
14160 err: "Pod QoS is immutable",
14161 test: "Pod QoS change, burstable -> guaranteed",
14162 }, {
14163 new: core.Pod{
14164 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14165 Spec: core.PodSpec{
14166 Containers: []core.Container{{
14167 Name: "container",
14168 TerminationMessagePolicy: "File",
14169 ImagePullPolicy: "Always",
14170 Image: "foo:V2",
14171 Resources: core.ResourceRequirements{
14172 Limits: getResourceLimits("200m", "200Mi"),
14173 Requests: getResourceLimits("100m", "100Mi"),
14174 },
14175 }},
14176 },
14177 },
14178 old: core.Pod{
14179 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14180 Spec: core.PodSpec{
14181 Containers: []core.Container{{
14182 Name: "container",
14183 TerminationMessagePolicy: "File",
14184 ImagePullPolicy: "Always",
14185 Image: "foo:V2",
14186 }},
14187 },
14188 },
14189 err: "Pod QoS is immutable",
14190 test: "Pod QoS change, besteffort -> burstable",
14191 }, {
14192 new: core.Pod{
14193 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14194 Spec: core.PodSpec{
14195 Containers: []core.Container{{
14196 Name: "container",
14197 TerminationMessagePolicy: "File",
14198 ImagePullPolicy: "Always",
14199 Image: "foo:V2",
14200 }},
14201 },
14202 },
14203 old: core.Pod{
14204 ObjectMeta: metav1.ObjectMeta{Name: "pod"},
14205 Spec: core.PodSpec{
14206 Containers: []core.Container{{
14207 Name: "container",
14208 TerminationMessagePolicy: "File",
14209 ImagePullPolicy: "Always",
14210 Image: "foo:V2",
14211 Resources: core.ResourceRequirements{
14212 Limits: getResourceLimits("200m", "200Mi"),
14213 Requests: getResourceLimits("100m", "100Mi"),
14214 },
14215 }},
14216 },
14217 },
14218 err: "Pod QoS is immutable",
14219 test: "Pod QoS change, burstable -> besteffort",
14220 }, {
14221 new: core.Pod{
14222 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
14223 Spec: core.PodSpec{
14224 Containers: []core.Container{{
14225 Image: "foo:V1",
14226 }},
14227 SecurityContext: &core.PodSecurityContext{
14228 FSGroupChangePolicy: &validfsGroupChangePolicy,
14229 },
14230 },
14231 },
14232 old: core.Pod{
14233 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
14234 Spec: core.PodSpec{
14235 Containers: []core.Container{{
14236 Image: "foo:V2",
14237 }},
14238 SecurityContext: &core.PodSecurityContext{
14239 FSGroupChangePolicy: nil,
14240 },
14241 },
14242 },
14243 err: "spec: Forbidden: pod updates may not change fields",
14244 test: "fsGroupChangePolicy change",
14245 }, {
14246 new: core.Pod{
14247 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
14248 Spec: core.PodSpec{
14249 Containers: []core.Container{{
14250 Image: "foo:V1",
14251 Ports: []core.ContainerPort{
14252 {HostPort: 8080, ContainerPort: 80},
14253 },
14254 }},
14255 },
14256 },
14257 old: core.Pod{
14258 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
14259 Spec: core.PodSpec{
14260 Containers: []core.Container{{
14261 Image: "foo:V2",
14262 Ports: []core.ContainerPort{
14263 {HostPort: 8000, ContainerPort: 80},
14264 },
14265 }},
14266 },
14267 },
14268 err: "spec: Forbidden: pod updates may not change fields",
14269 test: "port change",
14270 }, {
14271 new: core.Pod{
14272 ObjectMeta: metav1.ObjectMeta{
14273 Name: "foo",
14274 Labels: map[string]string{
14275 "foo": "bar",
14276 },
14277 },
14278 },
14279 old: core.Pod{
14280 ObjectMeta: metav1.ObjectMeta{
14281 Name: "foo",
14282 Labels: map[string]string{
14283 "Bar": "foo",
14284 },
14285 },
14286 },
14287 err: "",
14288 test: "bad label change",
14289 }, {
14290 new: core.Pod{
14291 ObjectMeta: metav1.ObjectMeta{
14292 Name: "foo",
14293 },
14294 Spec: core.PodSpec{
14295 NodeName: "node1",
14296 Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
14297 },
14298 },
14299 old: core.Pod{
14300 ObjectMeta: metav1.ObjectMeta{
14301 Name: "foo",
14302 },
14303 Spec: core.PodSpec{
14304 NodeName: "node1",
14305 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
14306 },
14307 },
14308 err: "spec.tolerations: Forbidden",
14309 test: "existing toleration value modified in pod spec updates",
14310 }, {
14311 new: core.Pod{
14312 ObjectMeta: metav1.ObjectMeta{
14313 Name: "foo",
14314 },
14315 Spec: core.PodSpec{
14316 NodeName: "node1",
14317 Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}},
14318 },
14319 },
14320 old: core.Pod{
14321 ObjectMeta: metav1.ObjectMeta{
14322 Name: "foo",
14323 },
14324 Spec: core.PodSpec{
14325 NodeName: "node1",
14326 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
14327 },
14328 },
14329 err: "spec.tolerations: Forbidden",
14330 test: "existing toleration value modified in pod spec updates with modified tolerationSeconds",
14331 }, {
14332 new: core.Pod{
14333 ObjectMeta: metav1.ObjectMeta{
14334 Name: "foo",
14335 },
14336 Spec: core.PodSpec{
14337 NodeName: "node1",
14338 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
14339 },
14340 },
14341 old: core.Pod{
14342 ObjectMeta: metav1.ObjectMeta{
14343 Name: "foo",
14344 },
14345 Spec: core.PodSpec{
14346 NodeName: "node1",
14347 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}},
14348 }},
14349 err: "",
14350 test: "modified tolerationSeconds in existing toleration value in pod spec updates",
14351 }, {
14352 new: core.Pod{
14353 ObjectMeta: metav1.ObjectMeta{
14354 Name: "foo",
14355 },
14356 Spec: core.PodSpec{
14357 Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
14358 },
14359 },
14360 old: core.Pod{
14361 ObjectMeta: metav1.ObjectMeta{
14362 Name: "foo",
14363 },
14364 Spec: core.PodSpec{
14365 NodeName: "",
14366 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
14367 },
14368 },
14369 err: "spec.tolerations: Forbidden",
14370 test: "toleration modified in updates to an unscheduled pod",
14371 }, {
14372 new: core.Pod{
14373 ObjectMeta: metav1.ObjectMeta{
14374 Name: "foo",
14375 },
14376 Spec: core.PodSpec{
14377 NodeName: "node1",
14378 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
14379 },
14380 },
14381 old: core.Pod{
14382 ObjectMeta: metav1.ObjectMeta{
14383 Name: "foo",
14384 },
14385 Spec: core.PodSpec{
14386 NodeName: "node1",
14387 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
14388 },
14389 },
14390 err: "",
14391 test: "tolerations unmodified in updates to a scheduled pod",
14392 }, {
14393 new: core.Pod{
14394 ObjectMeta: metav1.ObjectMeta{
14395 Name: "foo",
14396 },
14397 Spec: core.PodSpec{
14398 NodeName: "node1",
14399 Tolerations: []core.Toleration{
14400 {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
14401 {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]},
14402 },
14403 }},
14404 old: core.Pod{
14405 ObjectMeta: metav1.ObjectMeta{
14406 Name: "foo",
14407 },
14408 Spec: core.PodSpec{
14409 NodeName: "node1",
14410 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
14411 },
14412 },
14413 err: "",
14414 test: "added valid new toleration to existing tolerations in pod spec updates",
14415 }, {
14416 new: core.Pod{
14417 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{
14418 NodeName: "node1",
14419 Tolerations: []core.Toleration{
14420 {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
14421 {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]},
14422 },
14423 }},
14424 old: core.Pod{
14425 ObjectMeta: metav1.ObjectMeta{
14426 Name: "foo",
14427 },
14428 Spec: core.PodSpec{
14429 NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
14430 }},
14431 err: "spec.tolerations[1].effect",
14432 test: "added invalid new toleration to existing tolerations in pod spec updates",
14433 }, {
14434 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
14435 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
14436 err: "spec: Forbidden: pod updates may not change fields",
14437 test: "removed nodeName from pod spec",
14438 }, {
14439 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
14440 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
14441 err: "metadata.annotations[kubernetes.io/config.mirror]",
14442 test: "added mirror pod annotation",
14443 }, {
14444 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
14445 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
14446 err: "metadata.annotations[kubernetes.io/config.mirror]",
14447 test: "removed mirror pod annotation",
14448 }, {
14449 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}},
14450 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}},
14451 err: "metadata.annotations[kubernetes.io/config.mirror]",
14452 test: "changed mirror pod annotation",
14453 }, {
14454 new: core.Pod{
14455 ObjectMeta: metav1.ObjectMeta{
14456 Name: "foo",
14457 },
14458 Spec: core.PodSpec{
14459 NodeName: "node1",
14460 PriorityClassName: "bar-priority",
14461 },
14462 },
14463 old: core.Pod{
14464 ObjectMeta: metav1.ObjectMeta{
14465 Name: "foo",
14466 },
14467 Spec: core.PodSpec{
14468 NodeName: "node1",
14469 PriorityClassName: "foo-priority",
14470 },
14471 },
14472 err: "spec: Forbidden: pod updates",
14473 test: "changed priority class name",
14474 }, {
14475 new: core.Pod{
14476 ObjectMeta: metav1.ObjectMeta{
14477 Name: "foo",
14478 },
14479 Spec: core.PodSpec{
14480 NodeName: "node1",
14481 PriorityClassName: "",
14482 },
14483 },
14484 old: core.Pod{
14485 ObjectMeta: metav1.ObjectMeta{
14486 Name: "foo",
14487 },
14488 Spec: core.PodSpec{
14489 NodeName: "node1",
14490 PriorityClassName: "foo-priority",
14491 },
14492 },
14493 err: "spec: Forbidden: pod updates",
14494 test: "removed priority class name",
14495 }, {
14496 new: core.Pod{
14497 ObjectMeta: metav1.ObjectMeta{
14498 Name: "foo",
14499 },
14500 Spec: core.PodSpec{
14501 TerminationGracePeriodSeconds: utilpointer.Int64(1),
14502 },
14503 },
14504 old: core.Pod{
14505 ObjectMeta: metav1.ObjectMeta{
14506 Name: "foo",
14507 },
14508 Spec: core.PodSpec{
14509 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
14510 },
14511 },
14512 err: "",
14513 test: "update termination grace period seconds",
14514 }, {
14515 new: core.Pod{
14516 ObjectMeta: metav1.ObjectMeta{
14517 Name: "foo",
14518 },
14519 Spec: core.PodSpec{
14520 TerminationGracePeriodSeconds: utilpointer.Int64(0),
14521 },
14522 },
14523 old: core.Pod{
14524 ObjectMeta: metav1.ObjectMeta{
14525 Name: "foo",
14526 },
14527 Spec: core.PodSpec{
14528 TerminationGracePeriodSeconds: utilpointer.Int64(-1),
14529 },
14530 },
14531 err: "spec: Forbidden: pod updates",
14532 test: "update termination grace period seconds not 1",
14533 }, {
14534 new: core.Pod{
14535 ObjectMeta: metav1.ObjectMeta{
14536 Name: "foo",
14537 },
14538 Spec: core.PodSpec{
14539 OS: &core.PodOS{Name: core.Windows},
14540 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
14541 },
14542 },
14543 old: core.Pod{
14544 ObjectMeta: metav1.ObjectMeta{
14545 Name: "foo",
14546 },
14547 Spec: core.PodSpec{
14548 OS: &core.PodOS{Name: core.Linux},
14549 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
14550 },
14551 },
14552 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
14553 test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set",
14554 }, {
14555 new: core.Pod{
14556 ObjectMeta: metav1.ObjectMeta{
14557 Name: "foo",
14558 },
14559 Spec: core.PodSpec{
14560 OS: &core.PodOS{Name: core.Windows},
14561 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
14562 },
14563 },
14564 old: core.Pod{
14565 ObjectMeta: metav1.ObjectMeta{
14566 Name: "foo",
14567 },
14568 Spec: core.PodSpec{
14569 OS: &core.PodOS{Name: core.Linux},
14570 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
14571 },
14572 },
14573 err: "spec.securityContext.seLinuxOptions: Forbidden",
14574 test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set, we'd get SELinux errors as well",
14575 }, {
14576 new: core.Pod{
14577 ObjectMeta: metav1.ObjectMeta{
14578 Name: "foo",
14579 },
14580 Spec: core.PodSpec{
14581 OS: &core.PodOS{Name: "dummy"},
14582 },
14583 },
14584 old: core.Pod{
14585 ObjectMeta: metav1.ObjectMeta{
14586 Name: "foo",
14587 },
14588 Spec: core.PodSpec{},
14589 },
14590 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
14591 test: "invalid PodOS update, IdentifyPodOS featuregate set",
14592 }, {
14593 new: core.Pod{
14594 ObjectMeta: metav1.ObjectMeta{
14595 Name: "foo",
14596 },
14597 Spec: core.PodSpec{
14598 OS: &core.PodOS{Name: core.Linux},
14599 },
14600 },
14601 old: core.Pod{
14602 ObjectMeta: metav1.ObjectMeta{
14603 Name: "foo",
14604 },
14605 Spec: core.PodSpec{
14606 OS: &core.PodOS{Name: core.Windows},
14607 },
14608 },
14609 err: "Forbidden: pod updates may not change fields other than ",
14610 test: "update pod spec OS to a valid value, featuregate disabled",
14611 }, {
14612 new: core.Pod{
14613 Spec: core.PodSpec{
14614 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
14615 },
14616 },
14617 old: core.Pod{},
14618 err: "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
14619 test: "update pod spec schedulingGates: add new scheduling gate",
14620 }, {
14621 new: core.Pod{
14622 Spec: core.PodSpec{
14623 SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
14624 },
14625 },
14626 old: core.Pod{
14627 Spec: core.PodSpec{
14628 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
14629 },
14630 },
14631 err: "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
14632 test: "update pod spec schedulingGates: mutating an existing scheduling gate",
14633 }, {
14634 new: core.Pod{
14635 Spec: core.PodSpec{
14636 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14637 },
14638 },
14639 old: core.Pod{
14640 Spec: core.PodSpec{
14641 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
14642 },
14643 },
14644 err: "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
14645 test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
14646 }, {
14647 new: core.Pod{},
14648 old: core.Pod{
14649 Spec: core.PodSpec{
14650 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
14651 },
14652 },
14653 err: "",
14654 test: "update pod spec schedulingGates: legal deletion",
14655 }, {
14656 old: core.Pod{
14657 Spec: core.PodSpec{
14658 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14659 },
14660 },
14661 new: core.Pod{
14662 Spec: core.PodSpec{
14663 NodeSelector: map[string]string{
14664 "foo": "bar",
14665 },
14666 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14667 },
14668 },
14669 test: "adding node selector is allowed for gated pods",
14670 }, {
14671 old: core.Pod{
14672 Spec: core.PodSpec{
14673 NodeSelector: map[string]string{
14674 "foo": "bar",
14675 },
14676 },
14677 },
14678 new: core.Pod{
14679 Spec: core.PodSpec{
14680 NodeSelector: map[string]string{
14681 "foo": "bar",
14682 "foo2": "bar2",
14683 },
14684 },
14685 },
14686 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
14687 test: "adding node selector is not allowed for non-gated pods",
14688 }, {
14689 old: core.Pod{
14690 Spec: core.PodSpec{
14691 NodeSelector: map[string]string{
14692 "foo": "bar",
14693 },
14694 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14695 },
14696 },
14697 new: core.Pod{
14698 Spec: core.PodSpec{
14699 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14700 },
14701 },
14702 err: "spec.nodeSelector: Invalid value:",
14703 test: "removing node selector is not allowed for gated pods",
14704 }, {
14705 old: core.Pod{
14706 Spec: core.PodSpec{
14707 NodeSelector: map[string]string{
14708 "foo": "bar",
14709 },
14710 },
14711 },
14712 new: core.Pod{},
14713 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
14714 test: "removing node selector is not allowed for non-gated pods",
14715 }, {
14716 old: core.Pod{
14717 Spec: core.PodSpec{
14718 NodeSelector: map[string]string{
14719 "foo": "bar",
14720 },
14721 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14722 },
14723 },
14724 new: core.Pod{
14725 Spec: core.PodSpec{
14726 NodeSelector: map[string]string{
14727 "foo": "bar",
14728 "foo2": "bar2",
14729 },
14730 },
14731 },
14732 test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added",
14733 }, {
14734 old: core.Pod{
14735 Spec: core.PodSpec{
14736 NodeSelector: map[string]string{
14737 "foo": "bar",
14738 },
14739 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14740 },
14741 },
14742 new: core.Pod{
14743 Spec: core.PodSpec{
14744 NodeSelector: map[string]string{
14745 "foo": "new value",
14746 },
14747 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14748 },
14749 },
14750 err: "spec.nodeSelector: Invalid value:",
14751 test: "modifying value of existing node selector is not allowed",
14752 }, {
14753 old: core.Pod{
14754 Spec: core.PodSpec{
14755 Affinity: &core.Affinity{
14756 NodeAffinity: &core.NodeAffinity{
14757 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14758 NodeSelectorTerms: []core.NodeSelectorTerm{{
14759 MatchExpressions: []core.NodeSelectorRequirement{{
14760 Key: "expr",
14761 Operator: core.NodeSelectorOpIn,
14762 Values: []string{"foo"},
14763 }},
14764 }},
14765 },
14766 },
14767 },
14768 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14769 },
14770 },
14771 new: core.Pod{
14772 Spec: core.PodSpec{
14773 Affinity: &core.Affinity{
14774 NodeAffinity: &core.NodeAffinity{
14775 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14776
14777 NodeSelectorTerms: []core.NodeSelectorTerm{{
14778 MatchExpressions: []core.NodeSelectorRequirement{{
14779 Key: "expr",
14780 Operator: core.NodeSelectorOpIn,
14781 Values: []string{"foo"},
14782 }, {
14783 Key: "expr2",
14784 Operator: core.NodeSelectorOpIn,
14785 Values: []string{"foo2"},
14786 }},
14787 MatchFields: []core.NodeSelectorRequirement{{
14788 Key: "metadata.name",
14789 Operator: core.NodeSelectorOpIn,
14790 Values: []string{"foo"},
14791 }},
14792 }},
14793 },
14794 },
14795 },
14796 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14797 },
14798 },
14799 test: "addition to nodeAffinity is allowed for gated pods",
14800 }, {
14801 old: core.Pod{
14802 Spec: core.PodSpec{
14803 Affinity: &core.Affinity{
14804 NodeAffinity: &core.NodeAffinity{
14805 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14806 NodeSelectorTerms: []core.NodeSelectorTerm{{
14807 MatchExpressions: []core.NodeSelectorRequirement{{
14808 Key: "expr",
14809 Operator: core.NodeSelectorOpIn,
14810 Values: []string{"foo"},
14811 }},
14812 }},
14813 },
14814 },
14815 },
14816 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14817 },
14818 },
14819 new: core.Pod{
14820 Spec: core.PodSpec{
14821 Affinity: &core.Affinity{
14822 NodeAffinity: &core.NodeAffinity{},
14823 },
14824 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14825 },
14826 },
14827 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
14828 test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated",
14829 }, {
14830 old: core.Pod{
14831 Spec: core.PodSpec{
14832 Affinity: &core.Affinity{
14833 NodeAffinity: &core.NodeAffinity{
14834 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14835 NodeSelectorTerms: []core.NodeSelectorTerm{{
14836 MatchExpressions: []core.NodeSelectorRequirement{{
14837 Key: "expr",
14838 Operator: core.NodeSelectorOpIn,
14839 Values: []string{"foo"},
14840 }},
14841 }},
14842 },
14843 },
14844 },
14845 },
14846 },
14847 new: core.Pod{
14848 Spec: core.PodSpec{
14849 Affinity: &core.Affinity{
14850 NodeAffinity: &core.NodeAffinity{
14851 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14852
14853 NodeSelectorTerms: []core.NodeSelectorTerm{{
14854 MatchExpressions: []core.NodeSelectorRequirement{{
14855 Key: "expr",
14856 Operator: core.NodeSelectorOpIn,
14857 Values: []string{"foo"},
14858 }, {
14859 Key: "expr2",
14860 Operator: core.NodeSelectorOpIn,
14861 Values: []string{"foo2"},
14862 }},
14863 MatchFields: []core.NodeSelectorRequirement{{
14864 Key: "metadata.name",
14865 Operator: core.NodeSelectorOpIn,
14866 Values: []string{"foo"},
14867 }},
14868 }},
14869 },
14870 },
14871 },
14872 },
14873 },
14874 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
14875 test: "addition to nodeAffinity is not allowed for non-gated pods",
14876 }, {
14877 old: core.Pod{
14878 Spec: core.PodSpec{
14879 Affinity: &core.Affinity{
14880 NodeAffinity: &core.NodeAffinity{
14881 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14882 NodeSelectorTerms: []core.NodeSelectorTerm{{
14883 MatchExpressions: []core.NodeSelectorRequirement{{
14884 Key: "expr",
14885 Operator: core.NodeSelectorOpIn,
14886 Values: []string{"foo"},
14887 }},
14888 }},
14889 },
14890 },
14891 },
14892 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14893 },
14894 },
14895 new: core.Pod{
14896 Spec: core.PodSpec{
14897 Affinity: &core.Affinity{
14898 NodeAffinity: &core.NodeAffinity{
14899 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14900
14901 NodeSelectorTerms: []core.NodeSelectorTerm{{
14902 MatchExpressions: []core.NodeSelectorRequirement{{
14903 Key: "expr",
14904 Operator: core.NodeSelectorOpIn,
14905 Values: []string{"foo"},
14906 }, {
14907 Key: "expr2",
14908 Operator: core.NodeSelectorOpIn,
14909 Values: []string{"foo2"},
14910 }},
14911 MatchFields: []core.NodeSelectorRequirement{{
14912 Key: "metadata.name",
14913 Operator: core.NodeSelectorOpIn,
14914 Values: []string{"foo"},
14915 }},
14916 }},
14917 },
14918 },
14919 },
14920 },
14921 },
14922 test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs",
14923 }, {
14924 old: core.Pod{
14925 Spec: core.PodSpec{
14926 Affinity: &core.Affinity{
14927 NodeAffinity: &core.NodeAffinity{
14928 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14929 NodeSelectorTerms: []core.NodeSelectorTerm{{
14930 MatchExpressions: []core.NodeSelectorRequirement{{
14931 Key: "expr",
14932 Operator: core.NodeSelectorOpIn,
14933 Values: []string{"foo"},
14934 }},
14935 }},
14936 },
14937 },
14938 },
14939 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14940 },
14941 },
14942 new: core.Pod{
14943 Spec: core.PodSpec{
14944 Affinity: &core.Affinity{
14945 NodeAffinity: &core.NodeAffinity{
14946 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14947 NodeSelectorTerms: []core.NodeSelectorTerm{{
14948 MatchFields: []core.NodeSelectorRequirement{{
14949 Key: "metadata.name",
14950 Operator: core.NodeSelectorOpIn,
14951 Values: []string{"foo"},
14952 }},
14953 }},
14954 },
14955 },
14956 },
14957 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14958 },
14959 },
14960 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
14961 test: "nodeAffinity deletion from MatchExpressions not allowed",
14962 }, {
14963 old: core.Pod{
14964 Spec: core.PodSpec{
14965 Affinity: &core.Affinity{
14966 NodeAffinity: &core.NodeAffinity{
14967 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14968 NodeSelectorTerms: []core.NodeSelectorTerm{{
14969 MatchExpressions: []core.NodeSelectorRequirement{{
14970 Key: "expr",
14971 Operator: core.NodeSelectorOpIn,
14972 Values: []string{"foo"},
14973 }},
14974 MatchFields: []core.NodeSelectorRequirement{{
14975 Key: "metadata.name",
14976 Operator: core.NodeSelectorOpIn,
14977 Values: []string{"foo"},
14978 }},
14979 }},
14980 },
14981 },
14982 },
14983 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
14984 },
14985 },
14986 new: core.Pod{
14987 Spec: core.PodSpec{
14988 Affinity: &core.Affinity{
14989 NodeAffinity: &core.NodeAffinity{
14990 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
14991
14992 NodeSelectorTerms: []core.NodeSelectorTerm{{
14993 MatchExpressions: []core.NodeSelectorRequirement{{
14994 Key: "expr",
14995 Operator: core.NodeSelectorOpIn,
14996 Values: []string{"foo"},
14997 }},
14998 }},
14999 },
15000 },
15001 },
15002 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15003 },
15004 },
15005 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
15006 test: "nodeAffinity deletion from MatchFields not allowed",
15007 }, {
15008 old: core.Pod{
15009 Spec: core.PodSpec{
15010 Affinity: &core.Affinity{
15011 NodeAffinity: &core.NodeAffinity{
15012 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15013 NodeSelectorTerms: []core.NodeSelectorTerm{{
15014 MatchExpressions: []core.NodeSelectorRequirement{{
15015 Key: "expr",
15016 Operator: core.NodeSelectorOpIn,
15017 Values: []string{"foo"},
15018 }},
15019 MatchFields: []core.NodeSelectorRequirement{{
15020 Key: "metadata.name",
15021 Operator: core.NodeSelectorOpIn,
15022 Values: []string{"foo"},
15023 }},
15024 }},
15025 },
15026 },
15027 },
15028 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15029 },
15030 },
15031 new: core.Pod{
15032 Spec: core.PodSpec{
15033 Affinity: &core.Affinity{
15034 NodeAffinity: &core.NodeAffinity{
15035 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15036
15037 NodeSelectorTerms: []core.NodeSelectorTerm{{
15038 MatchExpressions: []core.NodeSelectorRequirement{{
15039 Key: "expr",
15040 Operator: core.NodeSelectorOpIn,
15041 Values: []string{"bar"},
15042 }},
15043 MatchFields: []core.NodeSelectorRequirement{{
15044 Key: "metadata.name",
15045 Operator: core.NodeSelectorOpIn,
15046 Values: []string{"foo"},
15047 }},
15048 }},
15049 },
15050 },
15051 },
15052 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15053 },
15054 },
15055 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
15056 test: "nodeAffinity modification of item in MatchExpressions not allowed",
15057 }, {
15058 old: core.Pod{
15059 Spec: core.PodSpec{
15060 Affinity: &core.Affinity{
15061 NodeAffinity: &core.NodeAffinity{
15062 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15063 NodeSelectorTerms: []core.NodeSelectorTerm{{
15064 MatchExpressions: []core.NodeSelectorRequirement{{
15065 Key: "expr",
15066 Operator: core.NodeSelectorOpIn,
15067 Values: []string{"foo"},
15068 }},
15069 MatchFields: []core.NodeSelectorRequirement{{
15070 Key: "metadata.name",
15071 Operator: core.NodeSelectorOpIn,
15072 Values: []string{"foo"},
15073 }},
15074 }},
15075 },
15076 },
15077 },
15078 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15079 },
15080 },
15081 new: core.Pod{
15082 Spec: core.PodSpec{
15083 Affinity: &core.Affinity{
15084 NodeAffinity: &core.NodeAffinity{
15085 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15086 NodeSelectorTerms: []core.NodeSelectorTerm{{
15087 MatchExpressions: []core.NodeSelectorRequirement{{
15088 Key: "expr",
15089 Operator: core.NodeSelectorOpIn,
15090 Values: []string{"foo"},
15091 }},
15092 MatchFields: []core.NodeSelectorRequirement{{
15093 Key: "metadata.name",
15094 Operator: core.NodeSelectorOpIn,
15095 Values: []string{"bar"},
15096 }},
15097 }},
15098 },
15099 },
15100 },
15101 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15102 },
15103 },
15104 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
15105 test: "nodeAffinity modification of item in MatchFields not allowed",
15106 }, {
15107 old: core.Pod{
15108 Spec: core.PodSpec{
15109 Affinity: &core.Affinity{
15110 NodeAffinity: &core.NodeAffinity{
15111 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15112 NodeSelectorTerms: []core.NodeSelectorTerm{{
15113 MatchExpressions: []core.NodeSelectorRequirement{{
15114 Key: "expr",
15115 Operator: core.NodeSelectorOpIn,
15116 Values: []string{"foo"},
15117 }},
15118 MatchFields: []core.NodeSelectorRequirement{{
15119 Key: "metadata.name",
15120 Operator: core.NodeSelectorOpIn,
15121 Values: []string{"foo"},
15122 }},
15123 }},
15124 },
15125 },
15126 },
15127 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15128 },
15129 },
15130 new: core.Pod{
15131 Spec: core.PodSpec{
15132 Affinity: &core.Affinity{
15133 NodeAffinity: &core.NodeAffinity{
15134 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15135 NodeSelectorTerms: []core.NodeSelectorTerm{{
15136 MatchExpressions: []core.NodeSelectorRequirement{{
15137 Key: "expr",
15138 Operator: core.NodeSelectorOpIn,
15139 Values: []string{"foo"},
15140 }},
15141 MatchFields: []core.NodeSelectorRequirement{{
15142 Key: "metadata.name",
15143 Operator: core.NodeSelectorOpIn,
15144 Values: []string{"bar"},
15145 }},
15146 }, {
15147 MatchExpressions: []core.NodeSelectorRequirement{{
15148 Key: "expr",
15149 Operator: core.NodeSelectorOpIn,
15150 Values: []string{"foo2"},
15151 }},
15152 MatchFields: []core.NodeSelectorRequirement{{
15153 Key: "metadata.name",
15154 Operator: core.NodeSelectorOpIn,
15155 Values: []string{"bar2"},
15156 }},
15157 }},
15158 },
15159 },
15160 },
15161 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15162 },
15163 },
15164 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
15165 test: "nodeSelectorTerms addition on gated pod should fail",
15166 }, {
15167 old: core.Pod{
15168 Spec: core.PodSpec{
15169 Affinity: &core.Affinity{
15170 NodeAffinity: &core.NodeAffinity{
15171 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
15172 Weight: 1.0,
15173 Preference: core.NodeSelectorTerm{
15174 MatchExpressions: []core.NodeSelectorRequirement{{
15175 Key: "expr",
15176 Operator: core.NodeSelectorOpIn,
15177 Values: []string{"foo"},
15178 }},
15179 },
15180 }},
15181 },
15182 },
15183 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15184 },
15185 },
15186 new: core.Pod{
15187 Spec: core.PodSpec{
15188 Affinity: &core.Affinity{
15189 NodeAffinity: &core.NodeAffinity{
15190 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
15191 Weight: 1.0,
15192 Preference: core.NodeSelectorTerm{
15193 MatchExpressions: []core.NodeSelectorRequirement{{
15194 Key: "expr",
15195 Operator: core.NodeSelectorOpIn,
15196 Values: []string{"foo2"},
15197 }},
15198 },
15199 }},
15200 },
15201 },
15202 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15203 },
15204 },
15205 test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods",
15206 }, {
15207 old: core.Pod{
15208 Spec: core.PodSpec{
15209 Affinity: &core.Affinity{
15210 NodeAffinity: &core.NodeAffinity{
15211 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
15212 Weight: 1.0,
15213 Preference: core.NodeSelectorTerm{
15214 MatchExpressions: []core.NodeSelectorRequirement{{
15215 Key: "expr",
15216 Operator: core.NodeSelectorOpIn,
15217 Values: []string{"foo"},
15218 }},
15219 },
15220 }},
15221 },
15222 },
15223 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15224 },
15225 },
15226 new: core.Pod{
15227 Spec: core.PodSpec{
15228 Affinity: &core.Affinity{
15229 NodeAffinity: &core.NodeAffinity{
15230 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
15231 Weight: 1.0,
15232 Preference: core.NodeSelectorTerm{
15233 MatchExpressions: []core.NodeSelectorRequirement{{
15234 Key: "expr",
15235 Operator: core.NodeSelectorOpIn,
15236 Values: []string{"foo"},
15237 }, {
15238 Key: "expr2",
15239 Operator: core.NodeSelectorOpIn,
15240 Values: []string{"foo2"},
15241 }},
15242 MatchFields: []core.NodeSelectorRequirement{{
15243 Key: "metadata.name",
15244 Operator: core.NodeSelectorOpIn,
15245 Values: []string{"bar"},
15246 }},
15247 },
15248 }},
15249 },
15250 },
15251 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15252 },
15253 },
15254 test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods",
15255 }, {
15256 old: core.Pod{
15257 Spec: core.PodSpec{
15258 Affinity: &core.Affinity{
15259 NodeAffinity: &core.NodeAffinity{
15260 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
15261 Weight: 1.0,
15262 Preference: core.NodeSelectorTerm{
15263 MatchExpressions: []core.NodeSelectorRequirement{{
15264 Key: "expr",
15265 Operator: core.NodeSelectorOpIn,
15266 Values: []string{"foo"},
15267 }},
15268 },
15269 }},
15270 },
15271 },
15272 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15273 },
15274 },
15275 new: core.Pod{
15276 Spec: core.PodSpec{
15277 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15278 },
15279 },
15280 test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
15281 }, {
15282 old: core.Pod{
15283 Spec: core.PodSpec{
15284 Affinity: &core.Affinity{
15285 NodeAffinity: &core.NodeAffinity{
15286 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15287 NodeSelectorTerms: []core.NodeSelectorTerm{{
15288 MatchExpressions: []core.NodeSelectorRequirement{{
15289 Key: "expr",
15290 Operator: core.NodeSelectorOpIn,
15291 Values: []string{"foo"},
15292 }},
15293 }},
15294 },
15295 },
15296 },
15297 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15298 },
15299 },
15300 new: core.Pod{
15301 Spec: core.PodSpec{
15302 Affinity: &core.Affinity{},
15303 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15304 },
15305 },
15306 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
15307 test: "new node affinity is nil",
15308 }, {
15309 old: core.Pod{
15310 Spec: core.PodSpec{
15311 Affinity: &core.Affinity{
15312 NodeAffinity: &core.NodeAffinity{
15313 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
15314 Weight: 1.0,
15315 Preference: core.NodeSelectorTerm{
15316 MatchExpressions: []core.NodeSelectorRequirement{{
15317 Key: "expr",
15318 Operator: core.NodeSelectorOpIn,
15319 Values: []string{"foo"},
15320 }},
15321 },
15322 }},
15323 },
15324 },
15325 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15326 },
15327 },
15328 new: core.Pod{
15329 Spec: core.PodSpec{
15330 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15331 },
15332 },
15333 test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
15334 }, {
15335 old: core.Pod{
15336 Spec: core.PodSpec{
15337 Affinity: &core.Affinity{
15338 NodeAffinity: &core.NodeAffinity{
15339 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15340 NodeSelectorTerms: []core.NodeSelectorTerm{
15341 {},
15342 },
15343 },
15344 },
15345 },
15346 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15347 },
15348 },
15349 new: core.Pod{
15350 Spec: core.PodSpec{
15351 Affinity: &core.Affinity{
15352 NodeAffinity: &core.NodeAffinity{
15353 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15354 NodeSelectorTerms: []core.NodeSelectorTerm{{
15355 MatchExpressions: []core.NodeSelectorRequirement{{
15356 Key: "expr",
15357 Operator: core.NodeSelectorOpIn,
15358 Values: []string{"foo"},
15359 }},
15360 }},
15361 },
15362 },
15363 },
15364 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15365 },
15366 },
15367 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
15368 test: "empty NodeSelectorTerm (selects nothing) cannot become populated (selects something)",
15369 }, {
15370 old: core.Pod{
15371 Spec: core.PodSpec{
15372 Affinity: nil,
15373 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15374 },
15375 },
15376 new: core.Pod{
15377 Spec: core.PodSpec{
15378 Affinity: &core.Affinity{
15379 NodeAffinity: &core.NodeAffinity{
15380 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15381 NodeSelectorTerms: []core.NodeSelectorTerm{{
15382 MatchExpressions: []core.NodeSelectorRequirement{{
15383 Key: "expr",
15384 Operator: core.NodeSelectorOpIn,
15385 Values: []string{"foo"},
15386 }},
15387 }},
15388 },
15389 },
15390 },
15391 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15392 },
15393 },
15394 test: "nil affinity can be mutated for gated pods",
15395 },
15396 {
15397 old: core.Pod{
15398 Spec: core.PodSpec{
15399 Affinity: nil,
15400 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15401 },
15402 },
15403 new: core.Pod{
15404 Spec: core.PodSpec{
15405 Affinity: &core.Affinity{
15406 NodeAffinity: &core.NodeAffinity{
15407 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15408 NodeSelectorTerms: []core.NodeSelectorTerm{{
15409 MatchExpressions: []core.NodeSelectorRequirement{{
15410 Key: "expr",
15411 Operator: core.NodeSelectorOpIn,
15412 Values: []string{"foo"},
15413 }},
15414 }},
15415 },
15416 },
15417 PodAffinity: &core.PodAffinity{
15418 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
15419 {
15420 TopologyKey: "foo",
15421 LabelSelector: &metav1.LabelSelector{
15422 MatchLabels: map[string]string{"foo": "bar"},
15423 },
15424 },
15425 },
15426 },
15427 },
15428 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15429 },
15430 },
15431 err: "pod updates may not change fields other than",
15432 test: "the podAffinity cannot be updated on gated pods",
15433 },
15434 {
15435 old: core.Pod{
15436 Spec: core.PodSpec{
15437 Affinity: nil,
15438 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15439 },
15440 },
15441 new: core.Pod{
15442 Spec: core.PodSpec{
15443 Affinity: &core.Affinity{
15444 NodeAffinity: &core.NodeAffinity{
15445 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
15446 NodeSelectorTerms: []core.NodeSelectorTerm{{
15447 MatchExpressions: []core.NodeSelectorRequirement{{
15448 Key: "expr",
15449 Operator: core.NodeSelectorOpIn,
15450 Values: []string{"foo"},
15451 }},
15452 }},
15453 },
15454 },
15455 PodAntiAffinity: &core.PodAntiAffinity{
15456 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
15457 {
15458 TopologyKey: "foo",
15459 LabelSelector: &metav1.LabelSelector{
15460 MatchLabels: map[string]string{"foo": "bar"},
15461 },
15462 },
15463 },
15464 },
15465 },
15466 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
15467 },
15468 },
15469 err: "pod updates may not change fields other than",
15470 test: "the podAntiAffinity cannot be updated on gated pods",
15471 },
15472 }
15473 for _, test := range tests {
15474 test.new.ObjectMeta.ResourceVersion = "1"
15475 test.old.ObjectMeta.ResourceVersion = "1"
15476
15477
15478 if test.new.Name == "" && test.old.Name == "" {
15479 test.new.Name = "name"
15480 test.old.Name = "name"
15481 }
15482 if test.new.Namespace == "" && test.old.Namespace == "" {
15483 test.new.Namespace = "namespace"
15484 test.old.Namespace = "namespace"
15485 }
15486 if test.new.Spec.Containers == nil && test.old.Spec.Containers == nil {
15487 test.new.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
15488 test.old.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
15489 }
15490 if len(test.new.Spec.DNSPolicy) == 0 && len(test.old.Spec.DNSPolicy) == 0 {
15491 test.new.Spec.DNSPolicy = core.DNSClusterFirst
15492 test.old.Spec.DNSPolicy = core.DNSClusterFirst
15493 }
15494 if len(test.new.Spec.RestartPolicy) == 0 && len(test.old.Spec.RestartPolicy) == 0 {
15495 test.new.Spec.RestartPolicy = "Always"
15496 test.old.Spec.RestartPolicy = "Always"
15497 }
15498
15499 errs := ValidatePodUpdate(&test.new, &test.old, PodValidationOptions{})
15500 if test.err == "" {
15501 if len(errs) != 0 {
15502 t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
15503 }
15504 } else {
15505 if len(errs) == 0 {
15506 t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
15507 } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
15508 t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
15509 }
15510 }
15511 }
15512 }
15513
15514 func TestValidatePodStatusUpdate(t *testing.T) {
15515 tests := []struct {
15516 new core.Pod
15517 old core.Pod
15518 err string
15519 test string
15520 }{{
15521 core.Pod{
15522 ObjectMeta: metav1.ObjectMeta{
15523 Name: "foo",
15524 },
15525 Spec: core.PodSpec{
15526 NodeName: "node1",
15527 },
15528 Status: core.PodStatus{
15529 NominatedNodeName: "node1",
15530 },
15531 },
15532 core.Pod{
15533 ObjectMeta: metav1.ObjectMeta{
15534 Name: "foo",
15535 },
15536 Spec: core.PodSpec{
15537 NodeName: "node1",
15538 },
15539 Status: core.PodStatus{},
15540 },
15541 "",
15542 "removed nominatedNodeName",
15543 }, {
15544 core.Pod{
15545 ObjectMeta: metav1.ObjectMeta{
15546 Name: "foo",
15547 },
15548 Spec: core.PodSpec{
15549 NodeName: "node1",
15550 },
15551 },
15552 core.Pod{
15553 ObjectMeta: metav1.ObjectMeta{
15554 Name: "foo",
15555 },
15556 Spec: core.PodSpec{
15557 NodeName: "node1",
15558 },
15559 Status: core.PodStatus{
15560 NominatedNodeName: "node1",
15561 },
15562 },
15563 "",
15564 "add valid nominatedNodeName",
15565 }, {
15566 core.Pod{
15567 ObjectMeta: metav1.ObjectMeta{
15568 Name: "foo",
15569 },
15570 Spec: core.PodSpec{
15571 NodeName: "node1",
15572 },
15573 Status: core.PodStatus{
15574 NominatedNodeName: "Node1",
15575 },
15576 },
15577 core.Pod{
15578 ObjectMeta: metav1.ObjectMeta{
15579 Name: "foo",
15580 },
15581 Spec: core.PodSpec{
15582 NodeName: "node1",
15583 },
15584 },
15585 "nominatedNodeName",
15586 "Add invalid nominatedNodeName",
15587 }, {
15588 core.Pod{
15589 ObjectMeta: metav1.ObjectMeta{
15590 Name: "foo",
15591 },
15592 Spec: core.PodSpec{
15593 NodeName: "node1",
15594 },
15595 Status: core.PodStatus{
15596 NominatedNodeName: "node1",
15597 },
15598 },
15599 core.Pod{
15600 ObjectMeta: metav1.ObjectMeta{
15601 Name: "foo",
15602 },
15603 Spec: core.PodSpec{
15604 NodeName: "node1",
15605 },
15606 Status: core.PodStatus{
15607 NominatedNodeName: "node2",
15608 },
15609 },
15610 "",
15611 "Update nominatedNodeName",
15612 }, {
15613 core.Pod{
15614 ObjectMeta: metav1.ObjectMeta{
15615 Name: "foo",
15616 },
15617 Status: core.PodStatus{
15618 InitContainerStatuses: []core.ContainerStatus{{
15619 ContainerID: "docker://numbers",
15620 Image: "alpine",
15621 Name: "init",
15622 Ready: false,
15623 Started: proto.Bool(false),
15624 State: core.ContainerState{
15625 Waiting: &core.ContainerStateWaiting{
15626 Reason: "PodInitializing",
15627 },
15628 },
15629 }},
15630 ContainerStatuses: []core.ContainerStatus{{
15631 ContainerID: "docker://numbers",
15632 Image: "nginx:alpine",
15633 Name: "main",
15634 Ready: false,
15635 Started: proto.Bool(false),
15636 State: core.ContainerState{
15637 Waiting: &core.ContainerStateWaiting{
15638 Reason: "PodInitializing",
15639 },
15640 },
15641 }},
15642 },
15643 },
15644 core.Pod{
15645 ObjectMeta: metav1.ObjectMeta{
15646 Name: "foo",
15647 },
15648 },
15649 "",
15650 "Container statuses pending",
15651 }, {
15652 core.Pod{
15653 ObjectMeta: metav1.ObjectMeta{
15654 Name: "foo",
15655 },
15656 Status: core.PodStatus{
15657 InitContainerStatuses: []core.ContainerStatus{{
15658 ContainerID: "docker://numbers",
15659 Image: "alpine",
15660 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15661 Name: "init",
15662 Ready: true,
15663 State: core.ContainerState{
15664 Terminated: &core.ContainerStateTerminated{
15665 ContainerID: "docker://numbers",
15666 Reason: "Completed",
15667 },
15668 },
15669 }},
15670 ContainerStatuses: []core.ContainerStatus{{
15671 ContainerID: "docker://numbers",
15672 Image: "nginx:alpine",
15673 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15674 Name: "nginx",
15675 Ready: true,
15676 Started: proto.Bool(true),
15677 State: core.ContainerState{
15678 Running: &core.ContainerStateRunning{
15679 StartedAt: metav1.NewTime(time.Now()),
15680 },
15681 },
15682 }},
15683 },
15684 },
15685 core.Pod{
15686 ObjectMeta: metav1.ObjectMeta{
15687 Name: "foo",
15688 },
15689 Status: core.PodStatus{
15690 InitContainerStatuses: []core.ContainerStatus{{
15691 ContainerID: "docker://numbers",
15692 Image: "alpine",
15693 Name: "init",
15694 Ready: false,
15695 State: core.ContainerState{
15696 Waiting: &core.ContainerStateWaiting{
15697 Reason: "PodInitializing",
15698 },
15699 },
15700 }},
15701 ContainerStatuses: []core.ContainerStatus{{
15702 ContainerID: "docker://numbers",
15703 Image: "nginx:alpine",
15704 Name: "main",
15705 Ready: false,
15706 Started: proto.Bool(false),
15707 State: core.ContainerState{
15708 Waiting: &core.ContainerStateWaiting{
15709 Reason: "PodInitializing",
15710 },
15711 },
15712 }},
15713 },
15714 },
15715 "",
15716 "Container statuses running",
15717 }, {
15718 core.Pod{
15719 ObjectMeta: metav1.ObjectMeta{
15720 Name: "foo",
15721 },
15722 Status: core.PodStatus{
15723 ContainerStatuses: []core.ContainerStatus{{
15724 ContainerID: "docker://numbers",
15725 Image: "nginx:alpine",
15726 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15727 Name: "nginx",
15728 Ready: true,
15729 Started: proto.Bool(true),
15730 State: core.ContainerState{
15731 Running: &core.ContainerStateRunning{
15732 StartedAt: metav1.NewTime(time.Now()),
15733 },
15734 },
15735 }},
15736 EphemeralContainerStatuses: []core.ContainerStatus{{
15737 ContainerID: "docker://numbers",
15738 Image: "busybox",
15739 Name: "debug",
15740 Ready: false,
15741 State: core.ContainerState{
15742 Waiting: &core.ContainerStateWaiting{
15743 Reason: "PodInitializing",
15744 },
15745 },
15746 }},
15747 },
15748 },
15749 core.Pod{
15750 ObjectMeta: metav1.ObjectMeta{
15751 Name: "foo",
15752 },
15753 Status: core.PodStatus{
15754 ContainerStatuses: []core.ContainerStatus{{
15755 ContainerID: "docker://numbers",
15756 Image: "nginx:alpine",
15757 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15758 Name: "nginx",
15759 Ready: true,
15760 Started: proto.Bool(true),
15761 State: core.ContainerState{
15762 Running: &core.ContainerStateRunning{
15763 StartedAt: metav1.NewTime(time.Now()),
15764 },
15765 },
15766 }},
15767 },
15768 },
15769 "",
15770 "Container statuses add ephemeral container",
15771 }, {
15772 core.Pod{
15773 ObjectMeta: metav1.ObjectMeta{
15774 Name: "foo",
15775 },
15776 Status: core.PodStatus{
15777 ContainerStatuses: []core.ContainerStatus{{
15778 ContainerID: "docker://numbers",
15779 Image: "nginx:alpine",
15780 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15781 Name: "nginx",
15782 Ready: true,
15783 Started: proto.Bool(true),
15784 State: core.ContainerState{
15785 Running: &core.ContainerStateRunning{
15786 StartedAt: metav1.NewTime(time.Now()),
15787 },
15788 },
15789 }},
15790 EphemeralContainerStatuses: []core.ContainerStatus{{
15791 ContainerID: "docker://numbers",
15792 Image: "busybox",
15793 ImageID: "docker-pullable://busybox@sha256:d0gf00d",
15794 Name: "debug",
15795 Ready: false,
15796 State: core.ContainerState{
15797 Running: &core.ContainerStateRunning{
15798 StartedAt: metav1.NewTime(time.Now()),
15799 },
15800 },
15801 }},
15802 },
15803 },
15804 core.Pod{
15805 ObjectMeta: metav1.ObjectMeta{
15806 Name: "foo",
15807 },
15808 Status: core.PodStatus{
15809 ContainerStatuses: []core.ContainerStatus{{
15810 ContainerID: "docker://numbers",
15811 Image: "nginx:alpine",
15812 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15813 Name: "nginx",
15814 Ready: true,
15815 Started: proto.Bool(true),
15816 State: core.ContainerState{
15817 Running: &core.ContainerStateRunning{
15818 StartedAt: metav1.NewTime(time.Now()),
15819 },
15820 },
15821 }},
15822 EphemeralContainerStatuses: []core.ContainerStatus{{
15823 ContainerID: "docker://numbers",
15824 Image: "busybox",
15825 Name: "debug",
15826 Ready: false,
15827 State: core.ContainerState{
15828 Waiting: &core.ContainerStateWaiting{
15829 Reason: "PodInitializing",
15830 },
15831 },
15832 }},
15833 },
15834 },
15835 "",
15836 "Container statuses ephemeral container running",
15837 }, {
15838 core.Pod{
15839 ObjectMeta: metav1.ObjectMeta{
15840 Name: "foo",
15841 },
15842 Status: core.PodStatus{
15843 ContainerStatuses: []core.ContainerStatus{{
15844 ContainerID: "docker://numbers",
15845 Image: "nginx:alpine",
15846 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15847 Name: "nginx",
15848 Ready: true,
15849 Started: proto.Bool(true),
15850 State: core.ContainerState{
15851 Running: &core.ContainerStateRunning{
15852 StartedAt: metav1.NewTime(time.Now()),
15853 },
15854 },
15855 }},
15856 EphemeralContainerStatuses: []core.ContainerStatus{{
15857 ContainerID: "docker://numbers",
15858 Image: "busybox",
15859 ImageID: "docker-pullable://busybox@sha256:d0gf00d",
15860 Name: "debug",
15861 Ready: false,
15862 State: core.ContainerState{
15863 Terminated: &core.ContainerStateTerminated{
15864 ContainerID: "docker://numbers",
15865 Reason: "Completed",
15866 StartedAt: metav1.NewTime(time.Now()),
15867 FinishedAt: metav1.NewTime(time.Now()),
15868 },
15869 },
15870 }},
15871 },
15872 },
15873 core.Pod{
15874 ObjectMeta: metav1.ObjectMeta{
15875 Name: "foo",
15876 },
15877 Status: core.PodStatus{
15878 ContainerStatuses: []core.ContainerStatus{{
15879 ContainerID: "docker://numbers",
15880 Image: "nginx:alpine",
15881 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15882 Name: "nginx",
15883 Ready: true,
15884 Started: proto.Bool(true),
15885 State: core.ContainerState{
15886 Running: &core.ContainerStateRunning{
15887 StartedAt: metav1.NewTime(time.Now()),
15888 },
15889 },
15890 }},
15891 EphemeralContainerStatuses: []core.ContainerStatus{{
15892 ContainerID: "docker://numbers",
15893 Image: "busybox",
15894 ImageID: "docker-pullable://busybox@sha256:d0gf00d",
15895 Name: "debug",
15896 Ready: false,
15897 State: core.ContainerState{
15898 Running: &core.ContainerStateRunning{
15899 StartedAt: metav1.NewTime(time.Now()),
15900 },
15901 },
15902 }},
15903 },
15904 },
15905 "",
15906 "Container statuses ephemeral container exited",
15907 }, {
15908 core.Pod{
15909 ObjectMeta: metav1.ObjectMeta{
15910 Name: "foo",
15911 },
15912 Status: core.PodStatus{
15913 InitContainerStatuses: []core.ContainerStatus{{
15914 ContainerID: "docker://numbers",
15915 Image: "alpine",
15916 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15917 Name: "init",
15918 Ready: true,
15919 State: core.ContainerState{
15920 Terminated: &core.ContainerStateTerminated{
15921 ContainerID: "docker://numbers",
15922 Reason: "Completed",
15923 },
15924 },
15925 }},
15926 ContainerStatuses: []core.ContainerStatus{{
15927 ContainerID: "docker://numbers",
15928 Image: "nginx:alpine",
15929 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15930 Name: "nginx",
15931 Ready: true,
15932 Started: proto.Bool(true),
15933 State: core.ContainerState{
15934 Terminated: &core.ContainerStateTerminated{
15935 ContainerID: "docker://numbers",
15936 Reason: "Completed",
15937 StartedAt: metav1.NewTime(time.Now()),
15938 FinishedAt: metav1.NewTime(time.Now()),
15939 },
15940 },
15941 }},
15942 EphemeralContainerStatuses: []core.ContainerStatus{{
15943 ContainerID: "docker://numbers",
15944 Image: "busybox",
15945 ImageID: "docker-pullable://busybox@sha256:d0gf00d",
15946 Name: "debug",
15947 Ready: false,
15948 State: core.ContainerState{
15949 Terminated: &core.ContainerStateTerminated{
15950 ContainerID: "docker://numbers",
15951 Reason: "Completed",
15952 StartedAt: metav1.NewTime(time.Now()),
15953 FinishedAt: metav1.NewTime(time.Now()),
15954 },
15955 },
15956 }},
15957 },
15958 },
15959 core.Pod{
15960 ObjectMeta: metav1.ObjectMeta{
15961 Name: "foo",
15962 },
15963 Status: core.PodStatus{
15964 InitContainerStatuses: []core.ContainerStatus{{
15965 ContainerID: "docker://numbers",
15966 Image: "alpine",
15967 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15968 Name: "init",
15969 Ready: true,
15970 State: core.ContainerState{
15971 Terminated: &core.ContainerStateTerminated{
15972 ContainerID: "docker://numbers",
15973 Reason: "Completed",
15974 },
15975 },
15976 }},
15977 ContainerStatuses: []core.ContainerStatus{{
15978 ContainerID: "docker://numbers",
15979 Image: "nginx:alpine",
15980 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
15981 Name: "nginx",
15982 Ready: true,
15983 Started: proto.Bool(true),
15984 State: core.ContainerState{
15985 Running: &core.ContainerStateRunning{
15986 StartedAt: metav1.NewTime(time.Now()),
15987 },
15988 },
15989 }},
15990 EphemeralContainerStatuses: []core.ContainerStatus{{
15991 ContainerID: "docker://numbers",
15992 Image: "busybox",
15993 ImageID: "docker-pullable://busybox@sha256:d0gf00d",
15994 Name: "debug",
15995 Ready: false,
15996 State: core.ContainerState{
15997 Running: &core.ContainerStateRunning{
15998 StartedAt: metav1.NewTime(time.Now()),
15999 },
16000 },
16001 }},
16002 },
16003 },
16004 "",
16005 "Container statuses all containers terminated",
16006 }, {
16007 core.Pod{
16008 ObjectMeta: metav1.ObjectMeta{
16009 Name: "foo",
16010 },
16011 Status: core.PodStatus{
16012 ResourceClaimStatuses: []core.PodResourceClaimStatus{
16013 {Name: "no-such-claim", ResourceClaimName: utilpointer.String("my-claim")},
16014 },
16015 },
16016 },
16017 core.Pod{
16018 ObjectMeta: metav1.ObjectMeta{
16019 Name: "foo",
16020 },
16021 },
16022 "status.resourceClaimStatuses[0].name: Invalid value: \"no-such-claim\": must match the name of an entry in `spec.resourceClaims`",
16023 "Non-existent PodResourceClaim",
16024 }, {
16025 core.Pod{
16026 ObjectMeta: metav1.ObjectMeta{
16027 Name: "foo",
16028 },
16029 Spec: core.PodSpec{
16030 ResourceClaims: []core.PodResourceClaim{
16031 {Name: "my-claim"},
16032 },
16033 },
16034 Status: core.PodStatus{
16035 ResourceClaimStatuses: []core.PodResourceClaimStatus{
16036 {Name: "my-claim", ResourceClaimName: utilpointer.String("%$!#")},
16037 },
16038 },
16039 },
16040 core.Pod{
16041 ObjectMeta: metav1.ObjectMeta{
16042 Name: "foo",
16043 },
16044 Spec: core.PodSpec{
16045 ResourceClaims: []core.PodResourceClaim{
16046 {Name: "my-claim"},
16047 },
16048 },
16049 },
16050 `status.resourceClaimStatuses[0].name: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`,
16051 "Invalid ResourceClaim name",
16052 }, {
16053 core.Pod{
16054 ObjectMeta: metav1.ObjectMeta{
16055 Name: "foo",
16056 },
16057 Spec: core.PodSpec{
16058 ResourceClaims: []core.PodResourceClaim{
16059 {Name: "my-claim"},
16060 {Name: "my-other-claim"},
16061 },
16062 },
16063 Status: core.PodStatus{
16064 ResourceClaimStatuses: []core.PodResourceClaimStatus{
16065 {Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
16066 {Name: "my-other-claim", ResourceClaimName: nil},
16067 {Name: "my-other-claim", ResourceClaimName: nil},
16068 },
16069 },
16070 },
16071 core.Pod{
16072 ObjectMeta: metav1.ObjectMeta{
16073 Name: "foo",
16074 },
16075 Spec: core.PodSpec{
16076 ResourceClaims: []core.PodResourceClaim{
16077 {Name: "my-claim"},
16078 },
16079 },
16080 },
16081 `status.resourceClaimStatuses[2].name: Duplicate value: "my-other-claim"`,
16082 "Duplicate ResourceClaimStatuses.Name",
16083 }, {
16084 core.Pod{
16085 ObjectMeta: metav1.ObjectMeta{
16086 Name: "foo",
16087 },
16088 Spec: core.PodSpec{
16089 ResourceClaims: []core.PodResourceClaim{
16090 {Name: "my-claim"},
16091 {Name: "my-other-claim"},
16092 },
16093 },
16094 Status: core.PodStatus{
16095 ResourceClaimStatuses: []core.PodResourceClaimStatus{
16096 {Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
16097 {Name: "my-other-claim", ResourceClaimName: nil},
16098 },
16099 },
16100 },
16101 core.Pod{
16102 ObjectMeta: metav1.ObjectMeta{
16103 Name: "foo",
16104 },
16105 Spec: core.PodSpec{
16106 ResourceClaims: []core.PodResourceClaim{
16107 {Name: "my-claim"},
16108 },
16109 },
16110 },
16111 "",
16112 "ResourceClaimStatuses okay",
16113 }, {
16114 core.Pod{
16115 ObjectMeta: metav1.ObjectMeta{
16116 Name: "foo",
16117 },
16118 Spec: core.PodSpec{
16119 InitContainers: []core.Container{
16120 {
16121 Name: "init",
16122 },
16123 },
16124 Containers: []core.Container{
16125 {
16126 Name: "nginx",
16127 },
16128 },
16129 },
16130 Status: core.PodStatus{
16131 InitContainerStatuses: []core.ContainerStatus{{
16132 ContainerID: "docker://numbers",
16133 Image: "alpine",
16134 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16135 Name: "init",
16136 Ready: true,
16137 State: core.ContainerState{
16138 Running: &core.ContainerStateRunning{
16139 StartedAt: metav1.NewTime(time.Now()),
16140 },
16141 },
16142 }},
16143 ContainerStatuses: []core.ContainerStatus{{
16144 ContainerID: "docker://numbers",
16145 Image: "nginx:alpine",
16146 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16147 Name: "nginx",
16148 Ready: true,
16149 Started: proto.Bool(true),
16150 State: core.ContainerState{
16151 Running: &core.ContainerStateRunning{
16152 StartedAt: metav1.NewTime(time.Now()),
16153 },
16154 },
16155 }},
16156 },
16157 },
16158 core.Pod{
16159 ObjectMeta: metav1.ObjectMeta{
16160 Name: "foo",
16161 },
16162 Spec: core.PodSpec{
16163 InitContainers: []core.Container{
16164 {
16165 Name: "init",
16166 },
16167 },
16168 Containers: []core.Container{
16169 {
16170 Name: "nginx",
16171 },
16172 },
16173 RestartPolicy: core.RestartPolicyNever,
16174 },
16175 Status: core.PodStatus{
16176 InitContainerStatuses: []core.ContainerStatus{{
16177 ContainerID: "docker://numbers",
16178 Image: "alpine",
16179 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16180 Name: "init",
16181 Ready: false,
16182 State: core.ContainerState{
16183 Terminated: &core.ContainerStateTerminated{
16184 ContainerID: "docker://numbers",
16185 Reason: "Completed",
16186 },
16187 },
16188 }},
16189 ContainerStatuses: []core.ContainerStatus{{
16190 ContainerID: "docker://numbers",
16191 Image: "nginx:alpine",
16192 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16193 Name: "nginx",
16194 Ready: true,
16195 Started: proto.Bool(true),
16196 State: core.ContainerState{
16197 Running: &core.ContainerStateRunning{
16198 StartedAt: metav1.NewTime(time.Now()),
16199 },
16200 },
16201 }},
16202 },
16203 },
16204 `status.initContainerStatuses[0].state: Forbidden: may not be transitioned to non-terminated state`,
16205 "init container cannot restart if RestartPolicyNever",
16206 }, {
16207 core.Pod{
16208 ObjectMeta: metav1.ObjectMeta{
16209 Name: "foo",
16210 },
16211 Spec: core.PodSpec{
16212 InitContainers: []core.Container{
16213 {
16214 Name: "restartable-init",
16215 RestartPolicy: &containerRestartPolicyAlways,
16216 },
16217 },
16218 Containers: []core.Container{
16219 {
16220 Name: "nginx",
16221 },
16222 },
16223 RestartPolicy: core.RestartPolicyNever,
16224 },
16225 Status: core.PodStatus{
16226 InitContainerStatuses: []core.ContainerStatus{{
16227 ContainerID: "docker://numbers",
16228 Image: "alpine",
16229 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16230 Name: "restartable-init",
16231 Ready: true,
16232 State: core.ContainerState{
16233 Running: &core.ContainerStateRunning{
16234 StartedAt: metav1.NewTime(time.Now()),
16235 },
16236 },
16237 }},
16238 ContainerStatuses: []core.ContainerStatus{{
16239 ContainerID: "docker://numbers",
16240 Image: "nginx:alpine",
16241 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16242 Name: "nginx",
16243 Ready: true,
16244 Started: proto.Bool(true),
16245 State: core.ContainerState{
16246 Running: &core.ContainerStateRunning{
16247 StartedAt: metav1.NewTime(time.Now()),
16248 },
16249 },
16250 }},
16251 },
16252 },
16253 core.Pod{
16254 ObjectMeta: metav1.ObjectMeta{
16255 Name: "foo",
16256 },
16257 Spec: core.PodSpec{
16258 InitContainers: []core.Container{
16259 {
16260 Name: "restartable-init",
16261 RestartPolicy: &containerRestartPolicyAlways,
16262 },
16263 },
16264 Containers: []core.Container{
16265 {
16266 Name: "nginx",
16267 },
16268 },
16269 RestartPolicy: core.RestartPolicyNever,
16270 },
16271 Status: core.PodStatus{
16272 InitContainerStatuses: []core.ContainerStatus{{
16273 ContainerID: "docker://numbers",
16274 Image: "alpine",
16275 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16276 Name: "restartable-init",
16277 Ready: false,
16278 State: core.ContainerState{
16279 Terminated: &core.ContainerStateTerminated{
16280 ContainerID: "docker://numbers",
16281 Reason: "Completed",
16282 },
16283 },
16284 }},
16285 ContainerStatuses: []core.ContainerStatus{{
16286 ContainerID: "docker://numbers",
16287 Image: "nginx:alpine",
16288 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16289 Name: "nginx",
16290 Ready: true,
16291 Started: proto.Bool(true),
16292 State: core.ContainerState{
16293 Running: &core.ContainerStateRunning{
16294 StartedAt: metav1.NewTime(time.Now()),
16295 },
16296 },
16297 }},
16298 },
16299 },
16300 "",
16301 "restartable init container can restart if RestartPolicyNever",
16302 }, {
16303 core.Pod{
16304 ObjectMeta: metav1.ObjectMeta{
16305 Name: "foo",
16306 },
16307 Spec: core.PodSpec{
16308 InitContainers: []core.Container{
16309 {
16310 Name: "restartable-init",
16311 RestartPolicy: &containerRestartPolicyAlways,
16312 },
16313 },
16314 Containers: []core.Container{
16315 {
16316 Name: "nginx",
16317 },
16318 },
16319 RestartPolicy: core.RestartPolicyOnFailure,
16320 },
16321 Status: core.PodStatus{
16322 InitContainerStatuses: []core.ContainerStatus{{
16323 ContainerID: "docker://numbers",
16324 Image: "alpine",
16325 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16326 Name: "restartable-init",
16327 Ready: true,
16328 State: core.ContainerState{
16329 Running: &core.ContainerStateRunning{
16330 StartedAt: metav1.NewTime(time.Now()),
16331 },
16332 },
16333 }},
16334 ContainerStatuses: []core.ContainerStatus{{
16335 ContainerID: "docker://numbers",
16336 Image: "nginx:alpine",
16337 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16338 Name: "nginx",
16339 Ready: true,
16340 Started: proto.Bool(true),
16341 State: core.ContainerState{
16342 Running: &core.ContainerStateRunning{
16343 StartedAt: metav1.NewTime(time.Now()),
16344 },
16345 },
16346 }},
16347 },
16348 },
16349 core.Pod{
16350 ObjectMeta: metav1.ObjectMeta{
16351 Name: "foo",
16352 },
16353 Spec: core.PodSpec{
16354 InitContainers: []core.Container{
16355 {
16356 Name: "restartable-init",
16357 RestartPolicy: &containerRestartPolicyAlways,
16358 },
16359 },
16360 Containers: []core.Container{
16361 {
16362 Name: "nginx",
16363 },
16364 },
16365 RestartPolicy: core.RestartPolicyOnFailure,
16366 },
16367 Status: core.PodStatus{
16368 InitContainerStatuses: []core.ContainerStatus{{
16369 ContainerID: "docker://numbers",
16370 Image: "alpine",
16371 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16372 Name: "restartable-init",
16373 Ready: false,
16374 State: core.ContainerState{
16375 Terminated: &core.ContainerStateTerminated{
16376 ContainerID: "docker://numbers",
16377 Reason: "Completed",
16378 },
16379 },
16380 }},
16381 ContainerStatuses: []core.ContainerStatus{{
16382 ContainerID: "docker://numbers",
16383 Image: "nginx:alpine",
16384 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16385 Name: "nginx",
16386 Ready: true,
16387 Started: proto.Bool(true),
16388 State: core.ContainerState{
16389 Running: &core.ContainerStateRunning{
16390 StartedAt: metav1.NewTime(time.Now()),
16391 },
16392 },
16393 }},
16394 },
16395 },
16396 "",
16397 "restartable init container can restart if RestartPolicyOnFailure",
16398 }, {
16399 core.Pod{
16400 ObjectMeta: metav1.ObjectMeta{
16401 Name: "foo",
16402 },
16403 Spec: core.PodSpec{
16404 InitContainers: []core.Container{
16405 {
16406 Name: "restartable-init",
16407 RestartPolicy: &containerRestartPolicyAlways,
16408 },
16409 },
16410 Containers: []core.Container{
16411 {
16412 Name: "nginx",
16413 },
16414 },
16415 RestartPolicy: core.RestartPolicyAlways,
16416 },
16417 Status: core.PodStatus{
16418 InitContainerStatuses: []core.ContainerStatus{{
16419 ContainerID: "docker://numbers",
16420 Image: "alpine",
16421 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16422 Name: "restartable-init",
16423 Ready: true,
16424 State: core.ContainerState{
16425 Running: &core.ContainerStateRunning{
16426 StartedAt: metav1.NewTime(time.Now()),
16427 },
16428 },
16429 }},
16430 ContainerStatuses: []core.ContainerStatus{{
16431 ContainerID: "docker://numbers",
16432 Image: "nginx:alpine",
16433 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16434 Name: "nginx",
16435 Ready: true,
16436 Started: proto.Bool(true),
16437 State: core.ContainerState{
16438 Running: &core.ContainerStateRunning{
16439 StartedAt: metav1.NewTime(time.Now()),
16440 },
16441 },
16442 }},
16443 },
16444 },
16445 core.Pod{
16446 ObjectMeta: metav1.ObjectMeta{
16447 Name: "foo",
16448 },
16449 Spec: core.PodSpec{
16450 InitContainers: []core.Container{
16451 {
16452 Name: "restartable-init",
16453 RestartPolicy: &containerRestartPolicyAlways,
16454 },
16455 },
16456 Containers: []core.Container{
16457 {
16458 Name: "nginx",
16459 },
16460 },
16461 RestartPolicy: core.RestartPolicyAlways,
16462 },
16463 Status: core.PodStatus{
16464 InitContainerStatuses: []core.ContainerStatus{{
16465 ContainerID: "docker://numbers",
16466 Image: "alpine",
16467 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16468 Name: "restartable-init",
16469 Ready: false,
16470 State: core.ContainerState{
16471 Terminated: &core.ContainerStateTerminated{
16472 ContainerID: "docker://numbers",
16473 Reason: "Completed",
16474 },
16475 },
16476 }},
16477 ContainerStatuses: []core.ContainerStatus{{
16478 ContainerID: "docker://numbers",
16479 Image: "nginx:alpine",
16480 ImageID: "docker-pullable://nginx@sha256:d0gf00d",
16481 Name: "nginx",
16482 Ready: true,
16483 Started: proto.Bool(true),
16484 State: core.ContainerState{
16485 Running: &core.ContainerStateRunning{
16486 StartedAt: metav1.NewTime(time.Now()),
16487 },
16488 },
16489 }},
16490 },
16491 },
16492 "",
16493 "restartable init container can restart if RestartPolicyAlways",
16494 },
16495 }
16496
16497 for _, test := range tests {
16498 test.new.ObjectMeta.ResourceVersion = "1"
16499 test.old.ObjectMeta.ResourceVersion = "1"
16500 errs := ValidatePodStatusUpdate(&test.new, &test.old, PodValidationOptions{})
16501 if test.err == "" {
16502 if len(errs) != 0 {
16503 t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
16504 }
16505 } else {
16506 if len(errs) == 0 {
16507 t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
16508 } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
16509 t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
16510 }
16511 }
16512 }
16513 }
16514
16515 func makeValidService() core.Service {
16516 clusterInternalTrafficPolicy := core.ServiceInternalTrafficPolicyCluster
16517 return core.Service{
16518 ObjectMeta: metav1.ObjectMeta{
16519 Name: "valid",
16520 Namespace: "valid",
16521 Labels: map[string]string{},
16522 Annotations: map[string]string{},
16523 ResourceVersion: "1",
16524 },
16525 Spec: core.ServiceSpec{
16526 Selector: map[string]string{"key": "val"},
16527 SessionAffinity: "None",
16528 Type: core.ServiceTypeClusterIP,
16529 Ports: []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt32(8675)}},
16530 InternalTrafficPolicy: &clusterInternalTrafficPolicy,
16531 },
16532 }
16533 }
16534
16535 func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
16536 makePod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
16537 return &core.Pod{
16538 ObjectMeta: metav1.ObjectMeta{
16539 Annotations: map[string]string{},
16540 Labels: map[string]string{},
16541 Name: "pod",
16542 Namespace: "ns",
16543 ResourceVersion: "1",
16544 },
16545 Spec: core.PodSpec{
16546 Containers: []core.Container{{
16547 Name: "cnt",
16548 Image: "image",
16549 ImagePullPolicy: "IfNotPresent",
16550 TerminationMessagePolicy: "File",
16551 }},
16552 DNSPolicy: core.DNSClusterFirst,
16553 EphemeralContainers: ephemeralContainers,
16554 RestartPolicy: core.RestartPolicyOnFailure,
16555 },
16556 }
16557 }
16558
16559
16560
16561 capabilities.SetForTests(capabilities.Capabilities{
16562 AllowPrivileged: true,
16563 })
16564 makeWindowsHostPod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
16565 return &core.Pod{
16566 ObjectMeta: metav1.ObjectMeta{
16567 Annotations: map[string]string{},
16568 Labels: map[string]string{},
16569 Name: "pod",
16570 Namespace: "ns",
16571 ResourceVersion: "1",
16572 },
16573 Spec: core.PodSpec{
16574 Containers: []core.Container{{
16575 Name: "cnt",
16576 Image: "image",
16577 ImagePullPolicy: "IfNotPresent",
16578 SecurityContext: &core.SecurityContext{
16579 WindowsOptions: &core.WindowsSecurityContextOptions{
16580 HostProcess: proto.Bool(true),
16581 },
16582 },
16583 TerminationMessagePolicy: "File",
16584 }},
16585 DNSPolicy: core.DNSClusterFirst,
16586 EphemeralContainers: ephemeralContainers,
16587 RestartPolicy: core.RestartPolicyOnFailure,
16588 SecurityContext: &core.PodSecurityContext{
16589 HostNetwork: true,
16590 WindowsOptions: &core.WindowsSecurityContextOptions{
16591 HostProcess: proto.Bool(true),
16592 },
16593 },
16594 },
16595 }
16596 }
16597
16598 tests := []struct {
16599 name string
16600 new, old *core.Pod
16601 err string
16602 }{{
16603 "no ephemeral containers",
16604 makePod([]core.EphemeralContainer{}),
16605 makePod([]core.EphemeralContainer{}),
16606 "",
16607 }, {
16608 "No change in Ephemeral Containers",
16609 makePod([]core.EphemeralContainer{{
16610 EphemeralContainerCommon: core.EphemeralContainerCommon{
16611 Name: "debugger",
16612 Image: "busybox",
16613 ImagePullPolicy: "IfNotPresent",
16614 TerminationMessagePolicy: "File",
16615 },
16616 }, {
16617 EphemeralContainerCommon: core.EphemeralContainerCommon{
16618 Name: "debugger2",
16619 Image: "busybox",
16620 ImagePullPolicy: "IfNotPresent",
16621 TerminationMessagePolicy: "File",
16622 },
16623 }}),
16624 makePod([]core.EphemeralContainer{{
16625 EphemeralContainerCommon: core.EphemeralContainerCommon{
16626 Name: "debugger",
16627 Image: "busybox",
16628 ImagePullPolicy: "IfNotPresent",
16629 TerminationMessagePolicy: "File",
16630 },
16631 }, {
16632 EphemeralContainerCommon: core.EphemeralContainerCommon{
16633 Name: "debugger2",
16634 Image: "busybox",
16635 ImagePullPolicy: "IfNotPresent",
16636 TerminationMessagePolicy: "File",
16637 },
16638 }}),
16639 "",
16640 }, {
16641 "Ephemeral Container list order changes",
16642 makePod([]core.EphemeralContainer{{
16643 EphemeralContainerCommon: core.EphemeralContainerCommon{
16644 Name: "debugger",
16645 Image: "busybox",
16646 ImagePullPolicy: "IfNotPresent",
16647 TerminationMessagePolicy: "File",
16648 },
16649 }, {
16650 EphemeralContainerCommon: core.EphemeralContainerCommon{
16651 Name: "debugger2",
16652 Image: "busybox",
16653 ImagePullPolicy: "IfNotPresent",
16654 TerminationMessagePolicy: "File",
16655 },
16656 }}),
16657 makePod([]core.EphemeralContainer{{
16658 EphemeralContainerCommon: core.EphemeralContainerCommon{
16659 Name: "debugger2",
16660 Image: "busybox",
16661 ImagePullPolicy: "IfNotPresent",
16662 TerminationMessagePolicy: "File",
16663 },
16664 }, {
16665 EphemeralContainerCommon: core.EphemeralContainerCommon{
16666 Name: "debugger",
16667 Image: "busybox",
16668 ImagePullPolicy: "IfNotPresent",
16669 TerminationMessagePolicy: "File",
16670 },
16671 }}),
16672 "",
16673 }, {
16674 "Add an Ephemeral Container",
16675 makePod([]core.EphemeralContainer{{
16676 EphemeralContainerCommon: core.EphemeralContainerCommon{
16677 Name: "debugger",
16678 Image: "busybox",
16679 ImagePullPolicy: "IfNotPresent",
16680 TerminationMessagePolicy: "File",
16681 },
16682 }}),
16683 makePod([]core.EphemeralContainer{}),
16684 "",
16685 }, {
16686 "Add two Ephemeral Containers",
16687 makePod([]core.EphemeralContainer{{
16688 EphemeralContainerCommon: core.EphemeralContainerCommon{
16689 Name: "debugger1",
16690 Image: "busybox",
16691 ImagePullPolicy: "IfNotPresent",
16692 TerminationMessagePolicy: "File",
16693 },
16694 }, {
16695 EphemeralContainerCommon: core.EphemeralContainerCommon{
16696 Name: "debugger2",
16697 Image: "busybox",
16698 ImagePullPolicy: "IfNotPresent",
16699 TerminationMessagePolicy: "File",
16700 },
16701 }}),
16702 makePod([]core.EphemeralContainer{}),
16703 "",
16704 }, {
16705 "Add to an existing Ephemeral Containers",
16706 makePod([]core.EphemeralContainer{{
16707 EphemeralContainerCommon: core.EphemeralContainerCommon{
16708 Name: "debugger",
16709 Image: "busybox",
16710 ImagePullPolicy: "IfNotPresent",
16711 TerminationMessagePolicy: "File",
16712 },
16713 }, {
16714 EphemeralContainerCommon: core.EphemeralContainerCommon{
16715 Name: "debugger2",
16716 Image: "busybox",
16717 ImagePullPolicy: "IfNotPresent",
16718 TerminationMessagePolicy: "File",
16719 },
16720 }}),
16721 makePod([]core.EphemeralContainer{{
16722 EphemeralContainerCommon: core.EphemeralContainerCommon{
16723 Name: "debugger",
16724 Image: "busybox",
16725 ImagePullPolicy: "IfNotPresent",
16726 TerminationMessagePolicy: "File",
16727 },
16728 }}),
16729 "",
16730 }, {
16731 "Add to an existing Ephemeral Containers, list order changes",
16732 makePod([]core.EphemeralContainer{{
16733 EphemeralContainerCommon: core.EphemeralContainerCommon{
16734 Name: "debugger3",
16735 Image: "busybox",
16736 ImagePullPolicy: "IfNotPresent",
16737 TerminationMessagePolicy: "File",
16738 },
16739 }, {
16740 EphemeralContainerCommon: core.EphemeralContainerCommon{
16741 Name: "debugger2",
16742 Image: "busybox",
16743 ImagePullPolicy: "IfNotPresent",
16744 TerminationMessagePolicy: "File",
16745 },
16746 }, {
16747 EphemeralContainerCommon: core.EphemeralContainerCommon{
16748 Name: "debugger",
16749 Image: "busybox",
16750 ImagePullPolicy: "IfNotPresent",
16751 TerminationMessagePolicy: "File",
16752 },
16753 }}),
16754 makePod([]core.EphemeralContainer{{
16755 EphemeralContainerCommon: core.EphemeralContainerCommon{
16756 Name: "debugger",
16757 Image: "busybox",
16758 ImagePullPolicy: "IfNotPresent",
16759 TerminationMessagePolicy: "File",
16760 },
16761 }, {
16762 EphemeralContainerCommon: core.EphemeralContainerCommon{
16763 Name: "debugger2",
16764 Image: "busybox",
16765 ImagePullPolicy: "IfNotPresent",
16766 TerminationMessagePolicy: "File",
16767 },
16768 }}),
16769 "",
16770 }, {
16771 "Remove an Ephemeral Container",
16772 makePod([]core.EphemeralContainer{}),
16773 makePod([]core.EphemeralContainer{{
16774 EphemeralContainerCommon: core.EphemeralContainerCommon{
16775 Name: "debugger",
16776 Image: "busybox",
16777 ImagePullPolicy: "IfNotPresent",
16778 TerminationMessagePolicy: "File",
16779 },
16780 }}),
16781 "may not be removed",
16782 }, {
16783 "Replace an Ephemeral Container",
16784 makePod([]core.EphemeralContainer{{
16785 EphemeralContainerCommon: core.EphemeralContainerCommon{
16786 Name: "firstone",
16787 Image: "busybox",
16788 ImagePullPolicy: "IfNotPresent",
16789 TerminationMessagePolicy: "File",
16790 },
16791 }}),
16792 makePod([]core.EphemeralContainer{{
16793 EphemeralContainerCommon: core.EphemeralContainerCommon{
16794 Name: "thentheother",
16795 Image: "busybox",
16796 ImagePullPolicy: "IfNotPresent",
16797 TerminationMessagePolicy: "File",
16798 },
16799 }}),
16800 "may not be removed",
16801 }, {
16802 "Change an Ephemeral Containers",
16803 makePod([]core.EphemeralContainer{{
16804 EphemeralContainerCommon: core.EphemeralContainerCommon{
16805 Name: "debugger1",
16806 Image: "busybox",
16807 ImagePullPolicy: "IfNotPresent",
16808 TerminationMessagePolicy: "File",
16809 },
16810 }, {
16811 EphemeralContainerCommon: core.EphemeralContainerCommon{
16812 Name: "debugger2",
16813 Image: "busybox",
16814 ImagePullPolicy: "IfNotPresent",
16815 TerminationMessagePolicy: "File",
16816 },
16817 }}),
16818 makePod([]core.EphemeralContainer{{
16819 EphemeralContainerCommon: core.EphemeralContainerCommon{
16820 Name: "debugger1",
16821 Image: "debian",
16822 ImagePullPolicy: "IfNotPresent",
16823 TerminationMessagePolicy: "File",
16824 },
16825 }, {
16826 EphemeralContainerCommon: core.EphemeralContainerCommon{
16827 Name: "debugger2",
16828 Image: "busybox",
16829 ImagePullPolicy: "IfNotPresent",
16830 TerminationMessagePolicy: "File",
16831 },
16832 }}),
16833 "may not be changed",
16834 }, {
16835 "Ephemeral container with potential conflict with regular containers, but conflict not present",
16836 makeWindowsHostPod([]core.EphemeralContainer{{
16837 EphemeralContainerCommon: core.EphemeralContainerCommon{
16838 Name: "debugger1",
16839 Image: "image",
16840 ImagePullPolicy: "IfNotPresent",
16841 SecurityContext: &core.SecurityContext{
16842 WindowsOptions: &core.WindowsSecurityContextOptions{
16843 HostProcess: proto.Bool(true),
16844 },
16845 },
16846 TerminationMessagePolicy: "File",
16847 },
16848 }}),
16849 makeWindowsHostPod(nil),
16850 "",
16851 }, {
16852 "Ephemeral container with potential conflict with regular containers, and conflict is present",
16853 makeWindowsHostPod([]core.EphemeralContainer{{
16854 EphemeralContainerCommon: core.EphemeralContainerCommon{
16855 Name: "debugger1",
16856 Image: "image",
16857 ImagePullPolicy: "IfNotPresent",
16858 SecurityContext: &core.SecurityContext{
16859 WindowsOptions: &core.WindowsSecurityContextOptions{
16860 HostProcess: proto.Bool(false),
16861 },
16862 },
16863 TerminationMessagePolicy: "File",
16864 },
16865 }}),
16866 makeWindowsHostPod(nil),
16867 "spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical",
16868 }, {
16869 "Add ephemeral container to static pod",
16870 func() *core.Pod {
16871 p := makePod(nil)
16872 p.Spec.NodeName = "some-name"
16873 p.ObjectMeta.Annotations = map[string]string{
16874 core.MirrorPodAnnotationKey: "foo",
16875 }
16876 p.Spec.EphemeralContainers = []core.EphemeralContainer{{
16877 EphemeralContainerCommon: core.EphemeralContainerCommon{
16878 Name: "debugger1",
16879 Image: "debian",
16880 ImagePullPolicy: "IfNotPresent",
16881 TerminationMessagePolicy: "File",
16882 },
16883 }}
16884 return p
16885 }(),
16886 func() *core.Pod {
16887 p := makePod(nil)
16888 p.Spec.NodeName = "some-name"
16889 p.ObjectMeta.Annotations = map[string]string{
16890 core.MirrorPodAnnotationKey: "foo",
16891 }
16892 return p
16893 }(),
16894 "Forbidden: static pods do not support ephemeral containers",
16895 },
16896 }
16897
16898 for _, tc := range tests {
16899 errs := ValidatePodEphemeralContainersUpdate(tc.new, tc.old, PodValidationOptions{})
16900 if tc.err == "" {
16901 if len(errs) != 0 {
16902 t.Errorf("unexpected invalid for test: %s\nErrors returned: %+v\nLocal diff of test objects (-old +new):\n%s", tc.name, errs, cmp.Diff(tc.old, tc.new))
16903 }
16904 } else {
16905 if len(errs) == 0 {
16906 t.Errorf("unexpected valid for test: %s\nLocal diff of test objects (-old +new):\n%s", tc.name, cmp.Diff(tc.old, tc.new))
16907 } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, tc.err) {
16908 t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", tc.name, tc.err, actualErr)
16909 }
16910 }
16911 }
16912 }
16913
16914 func TestValidateServiceCreate(t *testing.T) {
16915 requireDualStack := core.IPFamilyPolicyRequireDualStack
16916 singleStack := core.IPFamilyPolicySingleStack
16917 preferDualStack := core.IPFamilyPolicyPreferDualStack
16918
16919 testCases := []struct {
16920 name string
16921 tweakSvc func(svc *core.Service)
16922 numErrs int
16923 featureGates []featuregate.Feature
16924 }{{
16925 name: "missing namespace",
16926 tweakSvc: func(s *core.Service) {
16927 s.Namespace = ""
16928 },
16929 numErrs: 1,
16930 }, {
16931 name: "invalid namespace",
16932 tweakSvc: func(s *core.Service) {
16933 s.Namespace = "-123"
16934 },
16935 numErrs: 1,
16936 }, {
16937 name: "missing name",
16938 tweakSvc: func(s *core.Service) {
16939 s.Name = ""
16940 },
16941 numErrs: 1,
16942 }, {
16943 name: "invalid name",
16944 tweakSvc: func(s *core.Service) {
16945 s.Name = "-123"
16946 },
16947 numErrs: 1,
16948 }, {
16949 name: "too long name",
16950 tweakSvc: func(s *core.Service) {
16951 s.Name = strings.Repeat("a", 64)
16952 },
16953 numErrs: 1,
16954 }, {
16955 name: "invalid generateName",
16956 tweakSvc: func(s *core.Service) {
16957 s.GenerateName = "-123"
16958 },
16959 numErrs: 1,
16960 }, {
16961 name: "too long generateName",
16962 tweakSvc: func(s *core.Service) {
16963 s.GenerateName = strings.Repeat("a", 64)
16964 },
16965 numErrs: 1,
16966 }, {
16967 name: "invalid label",
16968 tweakSvc: func(s *core.Service) {
16969 s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar"
16970 },
16971 numErrs: 1,
16972 }, {
16973 name: "invalid annotation",
16974 tweakSvc: func(s *core.Service) {
16975 s.Annotations["NoSpecialCharsLike=Equals"] = "bar"
16976 },
16977 numErrs: 1,
16978 }, {
16979 name: "nil selector",
16980 tweakSvc: func(s *core.Service) {
16981 s.Spec.Selector = nil
16982 },
16983 numErrs: 0,
16984 }, {
16985 name: "invalid selector",
16986 tweakSvc: func(s *core.Service) {
16987 s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar"
16988 },
16989 numErrs: 1,
16990 }, {
16991 name: "missing session affinity",
16992 tweakSvc: func(s *core.Service) {
16993 s.Spec.SessionAffinity = ""
16994 },
16995 numErrs: 1,
16996 }, {
16997 name: "missing type",
16998 tweakSvc: func(s *core.Service) {
16999 s.Spec.Type = ""
17000 },
17001 numErrs: 1,
17002 }, {
17003 name: "missing ports",
17004 tweakSvc: func(s *core.Service) {
17005 s.Spec.Ports = nil
17006 },
17007 numErrs: 1,
17008 }, {
17009 name: "missing ports but headless",
17010 tweakSvc: func(s *core.Service) {
17011 s.Spec.Ports = nil
17012 s.Spec.ClusterIP = core.ClusterIPNone
17013 s.Spec.ClusterIPs = []string{core.ClusterIPNone}
17014 },
17015 numErrs: 0,
17016 }, {
17017 name: "empty port[0] name",
17018 tweakSvc: func(s *core.Service) {
17019 s.Spec.Ports[0].Name = ""
17020 },
17021 numErrs: 0,
17022 }, {
17023 name: "empty port[1] name",
17024 tweakSvc: func(s *core.Service) {
17025 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
17026 },
17027 numErrs: 1,
17028 }, {
17029 name: "empty multi-port port[0] name",
17030 tweakSvc: func(s *core.Service) {
17031 s.Spec.Ports[0].Name = ""
17032 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
17033 },
17034 numErrs: 1,
17035 }, {
17036 name: "invalid port name",
17037 tweakSvc: func(s *core.Service) {
17038 s.Spec.Ports[0].Name = "INVALID"
17039 },
17040 numErrs: 1,
17041 }, {
17042 name: "missing protocol",
17043 tweakSvc: func(s *core.Service) {
17044 s.Spec.Ports[0].Protocol = ""
17045 },
17046 numErrs: 1,
17047 }, {
17048 name: "invalid protocol",
17049 tweakSvc: func(s *core.Service) {
17050 s.Spec.Ports[0].Protocol = "INVALID"
17051 },
17052 numErrs: 1,
17053 }, {
17054 name: "invalid cluster ip",
17055 tweakSvc: func(s *core.Service) {
17056 s.Spec.ClusterIP = "invalid"
17057 s.Spec.ClusterIPs = []string{"invalid"}
17058 },
17059 numErrs: 1,
17060 }, {
17061 name: "missing port",
17062 tweakSvc: func(s *core.Service) {
17063 s.Spec.Ports[0].Port = 0
17064 },
17065 numErrs: 1,
17066 }, {
17067 name: "invalid port",
17068 tweakSvc: func(s *core.Service) {
17069 s.Spec.Ports[0].Port = 65536
17070 },
17071 numErrs: 1,
17072 }, {
17073 name: "invalid TargetPort int",
17074 tweakSvc: func(s *core.Service) {
17075 s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536)
17076 },
17077 numErrs: 1,
17078 }, {
17079 name: "valid port headless",
17080 tweakSvc: func(s *core.Service) {
17081 s.Spec.Ports[0].Port = 11722
17082 s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722)
17083 s.Spec.ClusterIP = core.ClusterIPNone
17084 s.Spec.ClusterIPs = []string{core.ClusterIPNone}
17085 },
17086 numErrs: 0,
17087 }, {
17088 name: "invalid port headless 1",
17089 tweakSvc: func(s *core.Service) {
17090 s.Spec.Ports[0].Port = 11722
17091 s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721)
17092 s.Spec.ClusterIP = core.ClusterIPNone
17093 s.Spec.ClusterIPs = []string{core.ClusterIPNone}
17094 },
17095
17096
17097
17098 numErrs: 0,
17099 }, {
17100 name: "invalid port headless 2",
17101 tweakSvc: func(s *core.Service) {
17102 s.Spec.Ports[0].Port = 11722
17103 s.Spec.Ports[0].TargetPort = intstr.FromString("target")
17104 s.Spec.ClusterIP = core.ClusterIPNone
17105 s.Spec.ClusterIPs = []string{core.ClusterIPNone}
17106 },
17107
17108
17109
17110 numErrs: 0,
17111 }, {
17112 name: "invalid publicIPs localhost",
17113 tweakSvc: func(s *core.Service) {
17114 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17115 s.Spec.ExternalIPs = []string{"127.0.0.1"}
17116 },
17117 numErrs: 1,
17118 }, {
17119 name: "invalid publicIPs unspecified",
17120 tweakSvc: func(s *core.Service) {
17121 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17122 s.Spec.ExternalIPs = []string{"0.0.0.0"}
17123 },
17124 numErrs: 1,
17125 }, {
17126 name: "invalid publicIPs loopback",
17127 tweakSvc: func(s *core.Service) {
17128 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17129 s.Spec.ExternalIPs = []string{"127.0.0.1"}
17130 },
17131 numErrs: 1,
17132 }, {
17133 name: "invalid publicIPs host",
17134 tweakSvc: func(s *core.Service) {
17135 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17136 s.Spec.ExternalIPs = []string{"myhost.mydomain"}
17137 },
17138 numErrs: 1,
17139 }, {
17140 name: "valid publicIPs",
17141 tweakSvc: func(s *core.Service) {
17142 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17143 s.Spec.ExternalIPs = []string{"1.2.3.4"}
17144 },
17145 numErrs: 0,
17146 }, {
17147 name: "dup port name",
17148 tweakSvc: func(s *core.Service) {
17149 s.Spec.Ports[0].Name = "p"
17150 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17151 },
17152 numErrs: 1,
17153 }, {
17154 name: "valid load balancer protocol UDP 1",
17155 tweakSvc: func(s *core.Service) {
17156 s.Spec.Type = core.ServiceTypeLoadBalancer
17157 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17158 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17159 s.Spec.Ports[0].Protocol = "UDP"
17160 },
17161 numErrs: 0,
17162 }, {
17163 name: "valid load balancer protocol UDP 2",
17164 tweakSvc: func(s *core.Service) {
17165 s.Spec.Type = core.ServiceTypeLoadBalancer
17166 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17167 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17168 s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}
17169 },
17170 numErrs: 0,
17171 }, {
17172 name: "load balancer with mix protocol",
17173 tweakSvc: func(s *core.Service) {
17174 s.Spec.Type = core.ServiceTypeLoadBalancer
17175 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17176 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17177 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)})
17178 },
17179 numErrs: 0,
17180 }, {
17181 name: "valid 1",
17182 tweakSvc: func(s *core.Service) {
17183
17184 },
17185 numErrs: 0,
17186 }, {
17187 name: "valid 2",
17188 tweakSvc: func(s *core.Service) {
17189 s.Spec.Ports[0].Protocol = "UDP"
17190 s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345)
17191 },
17192 numErrs: 0,
17193 }, {
17194 name: "valid 3",
17195 tweakSvc: func(s *core.Service) {
17196 s.Spec.Ports[0].TargetPort = intstr.FromString("http")
17197 },
17198 numErrs: 0,
17199 }, {
17200 name: "valid cluster ip - none ",
17201 tweakSvc: func(s *core.Service) {
17202 s.Spec.ClusterIP = core.ClusterIPNone
17203 s.Spec.ClusterIPs = []string{core.ClusterIPNone}
17204 },
17205 numErrs: 0,
17206 }, {
17207 name: "valid cluster ip - empty",
17208 tweakSvc: func(s *core.Service) {
17209 s.Spec.ClusterIPs = nil
17210 s.Spec.Ports[0].TargetPort = intstr.FromString("http")
17211 },
17212 numErrs: 0,
17213 }, {
17214 name: "valid type - clusterIP",
17215 tweakSvc: func(s *core.Service) {
17216 s.Spec.Type = core.ServiceTypeClusterIP
17217 },
17218 numErrs: 0,
17219 }, {
17220 name: "valid type - loadbalancer",
17221 tweakSvc: func(s *core.Service) {
17222 s.Spec.Type = core.ServiceTypeLoadBalancer
17223 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17224 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17225 },
17226 numErrs: 0,
17227 }, {
17228 name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false",
17229 tweakSvc: func(s *core.Service) {
17230 s.Spec.Type = core.ServiceTypeLoadBalancer
17231 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17232 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
17233 },
17234 numErrs: 0,
17235 }, {
17236 name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type",
17237 tweakSvc: func(s *core.Service) {
17238 s.Spec.Type = core.ServiceTypeLoadBalancer
17239 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17240 },
17241 numErrs: 1,
17242 }, {
17243 name: "valid type loadbalancer 2 ports",
17244 tweakSvc: func(s *core.Service) {
17245 s.Spec.Type = core.ServiceTypeLoadBalancer
17246 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17247 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17248 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17249 },
17250 numErrs: 0,
17251 }, {
17252 name: "valid external load balancer 2 ports",
17253 tweakSvc: func(s *core.Service) {
17254 s.Spec.Type = core.ServiceTypeLoadBalancer
17255 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17256 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17257 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17258 },
17259 numErrs: 0,
17260 }, {
17261 name: "duplicate nodeports",
17262 tweakSvc: func(s *core.Service) {
17263 s.Spec.Type = core.ServiceTypeNodePort
17264 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17265 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
17266 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
17267 },
17268 numErrs: 1,
17269 }, {
17270 name: "duplicate nodeports (different protocols)",
17271 tweakSvc: func(s *core.Service) {
17272 s.Spec.Type = core.ServiceTypeNodePort
17273 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17274 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
17275 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
17276 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)})
17277 },
17278 numErrs: 0,
17279 }, {
17280 name: "invalid duplicate ports (with same protocol)",
17281 tweakSvc: func(s *core.Service) {
17282 s.Spec.Type = core.ServiceTypeClusterIP
17283 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
17284 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)})
17285 },
17286 numErrs: 1,
17287 }, {
17288 name: "valid duplicate ports (with different protocols)",
17289 tweakSvc: func(s *core.Service) {
17290 s.Spec.Type = core.ServiceTypeClusterIP
17291 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
17292 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)})
17293 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)})
17294 },
17295 numErrs: 0,
17296 }, {
17297 name: "valid type - cluster",
17298 tweakSvc: func(s *core.Service) {
17299 s.Spec.Type = core.ServiceTypeClusterIP
17300 },
17301 numErrs: 0,
17302 }, {
17303 name: "valid type - nodeport",
17304 tweakSvc: func(s *core.Service) {
17305 s.Spec.Type = core.ServiceTypeNodePort
17306 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17307 },
17308 numErrs: 0,
17309 }, {
17310 name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true",
17311 tweakSvc: func(s *core.Service) {
17312 s.Spec.Type = core.ServiceTypeLoadBalancer
17313 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17314 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17315 },
17316 numErrs: 0,
17317 }, {
17318 name: "valid type loadbalancer 2 ports",
17319 tweakSvc: func(s *core.Service) {
17320 s.Spec.Type = core.ServiceTypeLoadBalancer
17321 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17322 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17323 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17324 },
17325 numErrs: 0,
17326 }, {
17327 name: "valid type loadbalancer with NodePort",
17328 tweakSvc: func(s *core.Service) {
17329 s.Spec.Type = core.ServiceTypeLoadBalancer
17330 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17331 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17332 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
17333 },
17334 numErrs: 0,
17335 }, {
17336 name: "valid type=NodePort service with NodePort",
17337 tweakSvc: func(s *core.Service) {
17338 s.Spec.Type = core.ServiceTypeNodePort
17339 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17340 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
17341 },
17342 numErrs: 0,
17343 }, {
17344 name: "valid type=NodePort service without NodePort",
17345 tweakSvc: func(s *core.Service) {
17346 s.Spec.Type = core.ServiceTypeNodePort
17347 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17348 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17349 },
17350 numErrs: 0,
17351 }, {
17352 name: "valid cluster service without NodePort",
17353 tweakSvc: func(s *core.Service) {
17354 s.Spec.Type = core.ServiceTypeClusterIP
17355 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17356 },
17357 numErrs: 0,
17358 }, {
17359 name: "invalid cluster service with NodePort",
17360 tweakSvc: func(s *core.Service) {
17361 s.Spec.Type = core.ServiceTypeClusterIP
17362 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
17363 },
17364 numErrs: 1,
17365 }, {
17366 name: "invalid public service with duplicate NodePort",
17367 tweakSvc: func(s *core.Service) {
17368 s.Spec.Type = core.ServiceTypeNodePort
17369 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17370 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
17371 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
17372 },
17373 numErrs: 1,
17374 }, {
17375 name: "valid type=LoadBalancer",
17376 tweakSvc: func(s *core.Service) {
17377 s.Spec.Type = core.ServiceTypeLoadBalancer
17378 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17379 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17380 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17381 },
17382 numErrs: 0,
17383 }, {
17384
17385
17386 name: "invalid port type=LoadBalancer",
17387 tweakSvc: func(s *core.Service) {
17388 s.Spec.Type = core.ServiceTypeLoadBalancer
17389 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17390 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17391 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
17392 },
17393 numErrs: 1,
17394 }, {
17395 name: "valid LoadBalancer source range annotation",
17396 tweakSvc: func(s *core.Service) {
17397 s.Spec.Type = core.ServiceTypeLoadBalancer
17398 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17399 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17400 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.0/24, 5.6.0.0/16"
17401 },
17402 numErrs: 0,
17403 }, {
17404 name: "valid empty LoadBalancer source range annotation",
17405 tweakSvc: func(s *core.Service) {
17406 s.Spec.Type = core.ServiceTypeLoadBalancer
17407 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17408 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17409 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
17410 },
17411 numErrs: 0,
17412 }, {
17413 name: "valid whitespace-only LoadBalancer source range annotation",
17414 tweakSvc: func(s *core.Service) {
17415 s.Spec.Type = core.ServiceTypeLoadBalancer
17416 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17417 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17418 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = " "
17419 },
17420 numErrs: 0,
17421 }, {
17422 name: "invalid LoadBalancer source range annotation (hostname)",
17423 tweakSvc: func(s *core.Service) {
17424 s.Spec.Type = core.ServiceTypeLoadBalancer
17425 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17426 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17427 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
17428 },
17429 numErrs: 1,
17430 }, {
17431 name: "invalid LoadBalancer source range annotation (invalid CIDR)",
17432 tweakSvc: func(s *core.Service) {
17433 s.Spec.Type = core.ServiceTypeLoadBalancer
17434 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17435 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17436 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33"
17437 },
17438 numErrs: 1,
17439 }, {
17440 name: "invalid LoadBalancer source range annotation for non LoadBalancer type service",
17441 tweakSvc: func(s *core.Service) {
17442 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.0/24"
17443 },
17444 numErrs: 1,
17445 }, {
17446 name: "invalid empty-but-set LoadBalancer source range annotation for non LoadBalancer type service",
17447 tweakSvc: func(s *core.Service) {
17448 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
17449 },
17450 numErrs: 1,
17451 }, {
17452 name: "valid LoadBalancer source range",
17453 tweakSvc: func(s *core.Service) {
17454 s.Spec.Type = core.ServiceTypeLoadBalancer
17455 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17456 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17457 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
17458 },
17459 numErrs: 0,
17460 }, {
17461 name: "valid LoadBalancer source range with whitespace",
17462 tweakSvc: func(s *core.Service) {
17463 s.Spec.Type = core.ServiceTypeLoadBalancer
17464 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17465 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17466 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24 ", " 5.6.0.0/16"}
17467 },
17468 numErrs: 0,
17469 }, {
17470 name: "invalid empty LoadBalancer source range",
17471 tweakSvc: func(s *core.Service) {
17472 s.Spec.Type = core.ServiceTypeLoadBalancer
17473 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17474 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17475 s.Spec.LoadBalancerSourceRanges = []string{" "}
17476 },
17477 numErrs: 1,
17478 }, {
17479 name: "invalid LoadBalancer source range (hostname)",
17480 tweakSvc: func(s *core.Service) {
17481 s.Spec.Type = core.ServiceTypeLoadBalancer
17482 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17483 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17484 s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"}
17485 },
17486 numErrs: 1,
17487 }, {
17488 name: "invalid LoadBalancer source range (invalid CIDR)",
17489 tweakSvc: func(s *core.Service) {
17490 s.Spec.Type = core.ServiceTypeLoadBalancer
17491 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17492 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17493 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/33"}
17494 },
17495 numErrs: 1,
17496 }, {
17497 name: "invalid source range for non LoadBalancer type service",
17498 tweakSvc: func(s *core.Service) {
17499 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
17500 },
17501 numErrs: 1,
17502 }, {
17503 name: "invalid source range annotation ignored with valid source range field",
17504 tweakSvc: func(s *core.Service) {
17505 s.Spec.Type = core.ServiceTypeLoadBalancer
17506 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17507 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17508 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
17509 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
17510 },
17511 numErrs: 0,
17512 }, {
17513 name: "valid ExternalName",
17514 tweakSvc: func(s *core.Service) {
17515 s.Spec.Type = core.ServiceTypeExternalName
17516 s.Spec.ExternalName = "foo.bar.example.com"
17517 },
17518 numErrs: 0,
17519 }, {
17520 name: "valid ExternalName (trailing dot)",
17521 tweakSvc: func(s *core.Service) {
17522 s.Spec.Type = core.ServiceTypeExternalName
17523 s.Spec.ExternalName = "foo.bar.example.com."
17524 },
17525 numErrs: 0,
17526 }, {
17527 name: "invalid ExternalName clusterIP (valid IP)",
17528 tweakSvc: func(s *core.Service) {
17529 s.Spec.Type = core.ServiceTypeExternalName
17530 s.Spec.ClusterIP = "1.2.3.4"
17531 s.Spec.ClusterIPs = []string{"1.2.3.4"}
17532 s.Spec.ExternalName = "foo.bar.example.com"
17533 },
17534 numErrs: 1,
17535 }, {
17536 name: "invalid ExternalName clusterIP (None)",
17537 tweakSvc: func(s *core.Service) {
17538 s.Spec.Type = core.ServiceTypeExternalName
17539 s.Spec.ClusterIP = "None"
17540 s.Spec.ClusterIPs = []string{"None"}
17541 s.Spec.ExternalName = "foo.bar.example.com"
17542 },
17543 numErrs: 1,
17544 }, {
17545 name: "invalid ExternalName (not a DNS name)",
17546 tweakSvc: func(s *core.Service) {
17547 s.Spec.Type = core.ServiceTypeExternalName
17548 s.Spec.ExternalName = "-123"
17549 },
17550 numErrs: 1,
17551 }, {
17552 name: "LoadBalancer type cannot have None ClusterIP",
17553 tweakSvc: func(s *core.Service) {
17554 s.Spec.ClusterIP = "None"
17555 s.Spec.ClusterIPs = []string{"None"}
17556 s.Spec.Type = core.ServiceTypeLoadBalancer
17557 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17558 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17559 },
17560 numErrs: 1,
17561 }, {
17562 name: "invalid node port with clusterIP None",
17563 tweakSvc: func(s *core.Service) {
17564 s.Spec.Type = core.ServiceTypeNodePort
17565 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17566 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
17567 s.Spec.ClusterIP = "None"
17568 s.Spec.ClusterIPs = []string{"None"}
17569 },
17570 numErrs: 1,
17571 },
17572
17573 {
17574 name: "invalid externalTraffic field",
17575 tweakSvc: func(s *core.Service) {
17576 s.Spec.Type = core.ServiceTypeLoadBalancer
17577 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17578 s.Spec.ExternalTrafficPolicy = "invalid"
17579 },
17580 numErrs: 1,
17581 }, {
17582 name: "nil internalTraffic field when feature gate is on",
17583 tweakSvc: func(s *core.Service) {
17584 s.Spec.InternalTrafficPolicy = nil
17585 },
17586 numErrs: 1,
17587 }, {
17588 name: "internalTrafficPolicy field nil when type is ExternalName",
17589 tweakSvc: func(s *core.Service) {
17590 s.Spec.InternalTrafficPolicy = nil
17591 s.Spec.Type = core.ServiceTypeExternalName
17592 s.Spec.ExternalName = "foo.bar.com"
17593 },
17594 numErrs: 0,
17595 }, {
17596
17597
17598
17599
17600 name: "internalTrafficPolicy field is set when type is ExternalName",
17601 tweakSvc: func(s *core.Service) {
17602 cluster := core.ServiceInternalTrafficPolicyCluster
17603 s.Spec.InternalTrafficPolicy = &cluster
17604 s.Spec.Type = core.ServiceTypeExternalName
17605 s.Spec.ExternalName = "foo.bar.com"
17606 },
17607 numErrs: 0,
17608 }, {
17609 name: "invalid internalTraffic field",
17610 tweakSvc: func(s *core.Service) {
17611 invalid := core.ServiceInternalTrafficPolicy("invalid")
17612 s.Spec.InternalTrafficPolicy = &invalid
17613 },
17614 numErrs: 1,
17615 }, {
17616 name: "internalTrafficPolicy field set to Cluster",
17617 tweakSvc: func(s *core.Service) {
17618 cluster := core.ServiceInternalTrafficPolicyCluster
17619 s.Spec.InternalTrafficPolicy = &cluster
17620 },
17621 numErrs: 0,
17622 }, {
17623 name: "internalTrafficPolicy field set to Local",
17624 tweakSvc: func(s *core.Service) {
17625 local := core.ServiceInternalTrafficPolicyLocal
17626 s.Spec.InternalTrafficPolicy = &local
17627 },
17628 numErrs: 0,
17629 }, {
17630 name: "negative healthCheckNodePort field",
17631 tweakSvc: func(s *core.Service) {
17632 s.Spec.Type = core.ServiceTypeLoadBalancer
17633 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17634 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
17635 s.Spec.HealthCheckNodePort = -1
17636 },
17637 numErrs: 1,
17638 }, {
17639 name: "negative healthCheckNodePort field",
17640 tweakSvc: func(s *core.Service) {
17641 s.Spec.Type = core.ServiceTypeLoadBalancer
17642 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17643 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
17644 s.Spec.HealthCheckNodePort = 31100
17645 },
17646 numErrs: 0,
17647 },
17648
17649 {
17650 name: "invalid timeoutSeconds field",
17651 tweakSvc: func(s *core.Service) {
17652 s.Spec.Type = core.ServiceTypeClusterIP
17653 s.Spec.SessionAffinity = core.ServiceAffinityClientIP
17654 s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
17655 ClientIP: &core.ClientIPConfig{
17656 TimeoutSeconds: utilpointer.Int32(-1),
17657 },
17658 }
17659 },
17660 numErrs: 1,
17661 }, {
17662 name: "sessionAffinityConfig can't be set when session affinity is None",
17663 tweakSvc: func(s *core.Service) {
17664 s.Spec.Type = core.ServiceTypeLoadBalancer
17665 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
17666 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
17667 s.Spec.SessionAffinity = core.ServiceAffinityNone
17668 s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
17669 ClientIP: &core.ClientIPConfig{
17670 TimeoutSeconds: utilpointer.Int32(90),
17671 },
17672 }
17673 },
17674 numErrs: 1,
17675 },
17676
17677 {
17678 name: "invalid, service with invalid ipFamilies",
17679 tweakSvc: func(s *core.Service) {
17680 invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
17681 s.Spec.IPFamilies = []core.IPFamily{invalidServiceIPFamily}
17682 },
17683 numErrs: 1,
17684 }, {
17685 name: "invalid, service with invalid ipFamilies (2nd)",
17686 tweakSvc: func(s *core.Service) {
17687 invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
17688 s.Spec.IPFamilyPolicy = &requireDualStack
17689 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, invalidServiceIPFamily}
17690 },
17691 numErrs: 1,
17692 }, {
17693 name: "IPFamilyPolicy(singleStack) is set for two families",
17694 tweakSvc: func(s *core.Service) {
17695 s.Spec.IPFamilyPolicy = &singleStack
17696 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17697 },
17698 numErrs: 0,
17699 }, {
17700 name: "valid, IPFamilyPolicy(preferDualStack) is set for two families (note: alloc sets families)",
17701 tweakSvc: func(s *core.Service) {
17702 s.Spec.IPFamilyPolicy = &preferDualStack
17703 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17704 },
17705 numErrs: 0,
17706 },
17707
17708 {
17709 name: "invalid, service with 2+ ipFamilies",
17710 tweakSvc: func(s *core.Service) {
17711 s.Spec.IPFamilyPolicy = &requireDualStack
17712 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol, core.IPv4Protocol}
17713 },
17714 numErrs: 1,
17715 }, {
17716 name: "invalid, service with same ip families",
17717 tweakSvc: func(s *core.Service) {
17718 s.Spec.IPFamilyPolicy = &requireDualStack
17719 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv6Protocol}
17720 },
17721 numErrs: 1,
17722 }, {
17723 name: "valid, nil service ipFamilies",
17724 tweakSvc: func(s *core.Service) {
17725 s.Spec.IPFamilies = nil
17726 },
17727 numErrs: 0,
17728 }, {
17729 name: "valid, service with valid ipFamilies (v4)",
17730 tweakSvc: func(s *core.Service) {
17731 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
17732 },
17733 numErrs: 0,
17734 }, {
17735 name: "valid, service with valid ipFamilies (v6)",
17736 tweakSvc: func(s *core.Service) {
17737 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
17738 },
17739 numErrs: 0,
17740 }, {
17741 name: "valid, service with valid ipFamilies(v4,v6)",
17742 tweakSvc: func(s *core.Service) {
17743 s.Spec.IPFamilyPolicy = &requireDualStack
17744 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17745 },
17746 numErrs: 0,
17747 }, {
17748 name: "valid, service with valid ipFamilies(v6,v4)",
17749 tweakSvc: func(s *core.Service) {
17750 s.Spec.IPFamilyPolicy = &requireDualStack
17751 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
17752 },
17753 numErrs: 0,
17754 }, {
17755 name: "valid, service preferred dual stack with single family",
17756 tweakSvc: func(s *core.Service) {
17757 s.Spec.IPFamilyPolicy = &preferDualStack
17758 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
17759 },
17760 numErrs: 0,
17761 },
17762
17763 {
17764 name: "invalid, garbage single ip",
17765 tweakSvc: func(s *core.Service) {
17766 s.Spec.ClusterIP = "garbage-ip"
17767 s.Spec.ClusterIPs = []string{"garbage-ip"}
17768 },
17769 numErrs: 1,
17770 }, {
17771 name: "invalid, garbage ips",
17772 tweakSvc: func(s *core.Service) {
17773 s.Spec.IPFamilyPolicy = &requireDualStack
17774 s.Spec.ClusterIP = "garbage-ip"
17775 s.Spec.ClusterIPs = []string{"garbage-ip", "garbage-second-ip"}
17776 },
17777 numErrs: 2,
17778 }, {
17779 name: "invalid, garbage first ip",
17780 tweakSvc: func(s *core.Service) {
17781 s.Spec.IPFamilyPolicy = &requireDualStack
17782 s.Spec.ClusterIP = "garbage-ip"
17783 s.Spec.ClusterIPs = []string{"garbage-ip", "2001::1"}
17784 },
17785 numErrs: 1,
17786 }, {
17787 name: "invalid, garbage second ip",
17788 tweakSvc: func(s *core.Service) {
17789 s.Spec.IPFamilyPolicy = &requireDualStack
17790 s.Spec.ClusterIP = "2001::1"
17791 s.Spec.ClusterIPs = []string{"2001::1", "garbage-ip"}
17792 },
17793 numErrs: 1,
17794 }, {
17795 name: "invalid, NONE + IP",
17796 tweakSvc: func(s *core.Service) {
17797 s.Spec.IPFamilyPolicy = &requireDualStack
17798 s.Spec.ClusterIP = "None"
17799 s.Spec.ClusterIPs = []string{"None", "2001::1"}
17800 },
17801 numErrs: 1,
17802 }, {
17803 name: "invalid, IP + NONE",
17804 tweakSvc: func(s *core.Service) {
17805 s.Spec.IPFamilyPolicy = &requireDualStack
17806 s.Spec.ClusterIP = "2001::1"
17807 s.Spec.ClusterIPs = []string{"2001::1", "None"}
17808 },
17809 numErrs: 1,
17810 }, {
17811 name: "invalid, EMPTY STRING + IP",
17812 tweakSvc: func(s *core.Service) {
17813 s.Spec.IPFamilyPolicy = &requireDualStack
17814 s.Spec.ClusterIP = ""
17815 s.Spec.ClusterIPs = []string{"", "2001::1"}
17816 },
17817 numErrs: 2,
17818 }, {
17819 name: "invalid, IP + EMPTY STRING",
17820 tweakSvc: func(s *core.Service) {
17821 s.Spec.IPFamilyPolicy = &requireDualStack
17822 s.Spec.ClusterIP = "2001::1"
17823 s.Spec.ClusterIPs = []string{"2001::1", ""}
17824 },
17825 numErrs: 1,
17826 }, {
17827 name: "invalid, same ip family (v6)",
17828 tweakSvc: func(s *core.Service) {
17829 s.Spec.IPFamilyPolicy = &requireDualStack
17830 s.Spec.ClusterIP = "2001::1"
17831 s.Spec.ClusterIPs = []string{"2001::1", "2001::4"}
17832 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17833 },
17834 numErrs: 2,
17835 }, {
17836 name: "invalid, same ip family (v4)",
17837 tweakSvc: func(s *core.Service) {
17838 s.Spec.IPFamilyPolicy = &requireDualStack
17839 s.Spec.ClusterIP = "10.0.0.1"
17840 s.Spec.ClusterIPs = []string{"10.0.0.1", "10.0.0.10"}
17841 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17842
17843 },
17844 numErrs: 2,
17845 }, {
17846 name: "invalid, more than two ips",
17847 tweakSvc: func(s *core.Service) {
17848 s.Spec.IPFamilyPolicy = &requireDualStack
17849 s.Spec.ClusterIP = "10.0.0.1"
17850 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1", "10.0.0.10"}
17851 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17852 },
17853 numErrs: 1,
17854 }, {
17855 name: " multi ip, dualstack not set (request for downgrade)",
17856 tweakSvc: func(s *core.Service) {
17857 s.Spec.IPFamilyPolicy = &singleStack
17858 s.Spec.ClusterIP = "10.0.0.1"
17859 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
17860 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17861 },
17862 numErrs: 0,
17863 }, {
17864 name: "valid, headless-no-selector + multi family + gate off",
17865 tweakSvc: func(s *core.Service) {
17866 s.Spec.IPFamilyPolicy = &requireDualStack
17867 s.Spec.ClusterIP = "None"
17868 s.Spec.ClusterIPs = []string{"None"}
17869 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17870 s.Spec.Selector = nil
17871 },
17872 numErrs: 0,
17873 }, {
17874 name: "valid, multi ip, single ipfamilies preferDualStack",
17875 tweakSvc: func(s *core.Service) {
17876 s.Spec.IPFamilyPolicy = &preferDualStack
17877 s.Spec.ClusterIP = "10.0.0.1"
17878 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
17879 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
17880 },
17881 numErrs: 0,
17882 },
17883
17884 {
17885 name: "valid, multi ip, single ipfamilies (must match when provided) + requireDualStack",
17886 tweakSvc: func(s *core.Service) {
17887 s.Spec.IPFamilyPolicy = &requireDualStack
17888 s.Spec.ClusterIP = "10.0.0.1"
17889 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
17890 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
17891 },
17892 numErrs: 0,
17893 }, {
17894 name: "invalid, families don't match (v4=>v6)",
17895 tweakSvc: func(s *core.Service) {
17896 s.Spec.ClusterIP = "10.0.0.1"
17897 s.Spec.ClusterIPs = []string{"10.0.0.1"}
17898 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
17899 },
17900 numErrs: 1,
17901 }, {
17902 name: "invalid, families don't match (v6=>v4)",
17903 tweakSvc: func(s *core.Service) {
17904 s.Spec.ClusterIP = "2001::1"
17905 s.Spec.ClusterIPs = []string{"2001::1"}
17906 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
17907 },
17908 numErrs: 1,
17909 }, {
17910 name: "valid. no field set",
17911 tweakSvc: func(s *core.Service) {
17912 },
17913 numErrs: 0,
17914 },
17915
17916 {
17917 name: "valid, single ip",
17918 tweakSvc: func(s *core.Service) {
17919 s.Spec.IPFamilyPolicy = &singleStack
17920 s.Spec.ClusterIP = "10.0.0.1"
17921 s.Spec.ClusterIPs = []string{"10.0.0.1"}
17922 },
17923 numErrs: 0,
17924 }, {
17925 name: "valid, single family",
17926 tweakSvc: func(s *core.Service) {
17927 s.Spec.IPFamilyPolicy = &singleStack
17928 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
17929
17930 },
17931 numErrs: 0,
17932 }, {
17933 name: "valid, single ip + single family",
17934 tweakSvc: func(s *core.Service) {
17935 s.Spec.IPFamilyPolicy = &singleStack
17936 s.Spec.ClusterIP = "2001::1"
17937 s.Spec.ClusterIPs = []string{"2001::1"}
17938 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
17939
17940 },
17941 numErrs: 0,
17942 }, {
17943 name: "valid, single ip + single family (dual stack requested)",
17944 tweakSvc: func(s *core.Service) {
17945 s.Spec.IPFamilyPolicy = &preferDualStack
17946 s.Spec.ClusterIP = "2001::1"
17947 s.Spec.ClusterIPs = []string{"2001::1"}
17948 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
17949
17950 },
17951 numErrs: 0,
17952 }, {
17953 name: "valid, single ip, multi ipfamilies",
17954 tweakSvc: func(s *core.Service) {
17955 s.Spec.IPFamilyPolicy = &requireDualStack
17956 s.Spec.ClusterIP = "10.0.0.1"
17957 s.Spec.ClusterIPs = []string{"10.0.0.1"}
17958 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17959 },
17960 numErrs: 0,
17961 }, {
17962 name: "valid, multi ips, multi ipfamilies (4,6)",
17963 tweakSvc: func(s *core.Service) {
17964 s.Spec.IPFamilyPolicy = &requireDualStack
17965 s.Spec.ClusterIP = "10.0.0.1"
17966 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
17967 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
17968 },
17969 numErrs: 0,
17970 }, {
17971 name: "valid, ips, multi ipfamilies (6,4)",
17972 tweakSvc: func(s *core.Service) {
17973 s.Spec.IPFamilyPolicy = &requireDualStack
17974 s.Spec.ClusterIP = "2001::1"
17975 s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
17976 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
17977 },
17978 numErrs: 0,
17979 }, {
17980 name: "valid, multi ips (6,4)",
17981 tweakSvc: func(s *core.Service) {
17982 s.Spec.IPFamilyPolicy = &requireDualStack
17983 s.Spec.ClusterIP = "2001::1"
17984 s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
17985 },
17986 numErrs: 0,
17987 }, {
17988 name: "valid, multi ipfamilies (6,4)",
17989 tweakSvc: func(s *core.Service) {
17990 s.Spec.IPFamilyPolicy = &requireDualStack
17991 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
17992 },
17993 numErrs: 0,
17994 }, {
17995 name: "valid, multi ips (4,6)",
17996 tweakSvc: func(s *core.Service) {
17997 s.Spec.IPFamilyPolicy = &requireDualStack
17998 s.Spec.ClusterIP = "10.0.0.1"
17999 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
18000 },
18001 numErrs: 0,
18002 }, {
18003 name: "valid, multi ipfamilies (4,6)",
18004 tweakSvc: func(s *core.Service) {
18005 s.Spec.IPFamilyPolicy = &requireDualStack
18006 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
18007 },
18008 numErrs: 0,
18009 }, {
18010 name: "valid, dual stack",
18011 tweakSvc: func(s *core.Service) {
18012 s.Spec.IPFamilyPolicy = &requireDualStack
18013 },
18014 numErrs: 0,
18015 },
18016
18017 {
18018 name: `valid appProtocol`,
18019 tweakSvc: func(s *core.Service) {
18020 s.Spec.Ports = []core.ServicePort{{
18021 Port: 12345,
18022 TargetPort: intstr.FromInt32(12345),
18023 Protocol: "TCP",
18024 AppProtocol: utilpointer.String("HTTP"),
18025 }}
18026 },
18027 numErrs: 0,
18028 }, {
18029 name: `valid custom appProtocol`,
18030 tweakSvc: func(s *core.Service) {
18031 s.Spec.Ports = []core.ServicePort{{
18032 Port: 12345,
18033 TargetPort: intstr.FromInt32(12345),
18034 Protocol: "TCP",
18035 AppProtocol: utilpointer.String("example.com/protocol"),
18036 }}
18037 },
18038 numErrs: 0,
18039 }, {
18040 name: `invalid appProtocol`,
18041 tweakSvc: func(s *core.Service) {
18042 s.Spec.Ports = []core.ServicePort{{
18043 Port: 12345,
18044 TargetPort: intstr.FromInt32(12345),
18045 Protocol: "TCP",
18046 AppProtocol: utilpointer.String("example.com/protocol_with{invalid}[characters]"),
18047 }}
18048 },
18049 numErrs: 1,
18050 },
18051
18052 {
18053 name: "invalid cluster ip != clusterIP in multi ip service",
18054 tweakSvc: func(s *core.Service) {
18055 s.Spec.IPFamilyPolicy = &requireDualStack
18056 s.Spec.ClusterIP = "10.0.0.10"
18057 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
18058 },
18059 numErrs: 1,
18060 }, {
18061 name: "invalid cluster ip != clusterIP in single ip service",
18062 tweakSvc: func(s *core.Service) {
18063 s.Spec.ClusterIP = "10.0.0.10"
18064 s.Spec.ClusterIPs = []string{"10.0.0.1"}
18065 },
18066 numErrs: 1,
18067 }, {
18068 name: "Use AllocateLoadBalancerNodePorts when type is not LoadBalancer",
18069 tweakSvc: func(s *core.Service) {
18070 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
18071 },
18072 numErrs: 1,
18073 }, {
18074 name: "valid LoadBalancerClass when type is LoadBalancer",
18075 tweakSvc: func(s *core.Service) {
18076 s.Spec.Type = core.ServiceTypeLoadBalancer
18077 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
18078 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
18079 s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
18080 },
18081 numErrs: 0,
18082 }, {
18083 name: "invalid LoadBalancerClass",
18084 tweakSvc: func(s *core.Service) {
18085 s.Spec.Type = core.ServiceTypeLoadBalancer
18086 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
18087 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
18088 s.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerClass")
18089 },
18090 numErrs: 1,
18091 }, {
18092 name: "invalid: set LoadBalancerClass when type is not LoadBalancer",
18093 tweakSvc: func(s *core.Service) {
18094 s.Spec.Type = core.ServiceTypeClusterIP
18095 s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
18096 },
18097 numErrs: 1,
18098 }, {
18099 name: "topology annotations are mismatched",
18100 tweakSvc: func(s *core.Service) {
18101 s.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
18102 s.Annotations[core.AnnotationTopologyMode] = "different"
18103 },
18104 numErrs: 1,
18105 }, {
18106 name: "valid: trafficDistribution field set to PreferClose",
18107 tweakSvc: func(s *core.Service) {
18108 s.Spec.TrafficDistribution = utilpointer.String("PreferClose")
18109 },
18110 numErrs: 0,
18111 }, {
18112 name: "invalid: trafficDistribution field set to Random",
18113 tweakSvc: func(s *core.Service) {
18114 s.Spec.TrafficDistribution = utilpointer.String("Random")
18115 },
18116 numErrs: 1,
18117 },
18118 }
18119
18120 for _, tc := range testCases {
18121 t.Run(tc.name, func(t *testing.T) {
18122 for i := range tc.featureGates {
18123 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, tc.featureGates[i], true)()
18124 }
18125 svc := makeValidService()
18126 tc.tweakSvc(&svc)
18127 errs := ValidateServiceCreate(&svc)
18128 if len(errs) != tc.numErrs {
18129 t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
18130 }
18131 })
18132 }
18133 }
18134
18135 func TestValidateServiceExternalTrafficPolicy(t *testing.T) {
18136 testCases := []struct {
18137 name string
18138 tweakSvc func(svc *core.Service)
18139 numErrs int
18140 }{{
18141 name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set",
18142 tweakSvc: func(s *core.Service) {
18143 s.Spec.Type = core.ServiceTypeLoadBalancer
18144 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
18145 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
18146 s.Spec.HealthCheckNodePort = 34567
18147 },
18148 numErrs: 0,
18149 }, {
18150 name: "valid nodePort service with externalTrafficPolicy set",
18151 tweakSvc: func(s *core.Service) {
18152 s.Spec.Type = core.ServiceTypeNodePort
18153 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
18154 },
18155 numErrs: 0,
18156 }, {
18157 name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set",
18158 tweakSvc: func(s *core.Service) {
18159 s.Spec.Type = core.ServiceTypeClusterIP
18160 },
18161 numErrs: 0,
18162 }, {
18163 name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local",
18164 tweakSvc: func(s *core.Service) {
18165 s.Spec.Type = core.ServiceTypeLoadBalancer
18166 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
18167 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
18168 s.Spec.HealthCheckNodePort = 34567
18169 },
18170 numErrs: 1,
18171 }, {
18172 name: "cannot set healthCheckNodePort field on nodePort service",
18173 tweakSvc: func(s *core.Service) {
18174 s.Spec.Type = core.ServiceTypeNodePort
18175 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
18176 s.Spec.HealthCheckNodePort = 34567
18177 },
18178 numErrs: 1,
18179 }, {
18180 name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service",
18181 tweakSvc: func(s *core.Service) {
18182 s.Spec.Type = core.ServiceTypeClusterIP
18183 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
18184 s.Spec.HealthCheckNodePort = 34567
18185 },
18186 numErrs: 2,
18187 }, {
18188 name: "cannot set externalTrafficPolicy field on ExternalName service",
18189 tweakSvc: func(s *core.Service) {
18190 s.Spec.Type = core.ServiceTypeExternalName
18191 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
18192 },
18193 numErrs: 1,
18194 }, {
18195 name: "externalTrafficPolicy is required on NodePort service",
18196 tweakSvc: func(s *core.Service) {
18197 s.Spec.Type = core.ServiceTypeNodePort
18198 },
18199 numErrs: 1,
18200 }, {
18201 name: "externalTrafficPolicy is required on LoadBalancer service",
18202 tweakSvc: func(s *core.Service) {
18203 s.Spec.Type = core.ServiceTypeLoadBalancer
18204 },
18205 numErrs: 1,
18206 }, {
18207 name: "externalTrafficPolicy is required on ClusterIP service with externalIPs",
18208 tweakSvc: func(s *core.Service) {
18209 s.Spec.Type = core.ServiceTypeClusterIP
18210 s.Spec.ExternalIPs = []string{"1.2.3,4"}
18211 },
18212 numErrs: 1,
18213 },
18214 }
18215
18216 for _, tc := range testCases {
18217 svc := makeValidService()
18218 tc.tweakSvc(&svc)
18219 errs := validateServiceExternalTrafficPolicy(&svc)
18220 if len(errs) != tc.numErrs {
18221 t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
18222 }
18223 }
18224 }
18225
18226 func TestValidateReplicationControllerStatus(t *testing.T) {
18227 tests := []struct {
18228 name string
18229
18230 replicas int32
18231 fullyLabeledReplicas int32
18232 readyReplicas int32
18233 availableReplicas int32
18234 observedGeneration int64
18235
18236 expectedErr bool
18237 }{{
18238 name: "valid status",
18239 replicas: 3,
18240 fullyLabeledReplicas: 3,
18241 readyReplicas: 2,
18242 availableReplicas: 1,
18243 observedGeneration: 2,
18244 expectedErr: false,
18245 }, {
18246 name: "invalid replicas",
18247 replicas: -1,
18248 fullyLabeledReplicas: 3,
18249 readyReplicas: 2,
18250 availableReplicas: 1,
18251 observedGeneration: 2,
18252 expectedErr: true,
18253 }, {
18254 name: "invalid fullyLabeledReplicas",
18255 replicas: 3,
18256 fullyLabeledReplicas: -1,
18257 readyReplicas: 2,
18258 availableReplicas: 1,
18259 observedGeneration: 2,
18260 expectedErr: true,
18261 }, {
18262 name: "invalid readyReplicas",
18263 replicas: 3,
18264 fullyLabeledReplicas: 3,
18265 readyReplicas: -1,
18266 availableReplicas: 1,
18267 observedGeneration: 2,
18268 expectedErr: true,
18269 }, {
18270 name: "invalid availableReplicas",
18271 replicas: 3,
18272 fullyLabeledReplicas: 3,
18273 readyReplicas: 3,
18274 availableReplicas: -1,
18275 observedGeneration: 2,
18276 expectedErr: true,
18277 }, {
18278 name: "invalid observedGeneration",
18279 replicas: 3,
18280 fullyLabeledReplicas: 3,
18281 readyReplicas: 3,
18282 availableReplicas: 3,
18283 observedGeneration: -1,
18284 expectedErr: true,
18285 }, {
18286 name: "fullyLabeledReplicas greater than replicas",
18287 replicas: 3,
18288 fullyLabeledReplicas: 4,
18289 readyReplicas: 3,
18290 availableReplicas: 3,
18291 observedGeneration: 1,
18292 expectedErr: true,
18293 }, {
18294 name: "readyReplicas greater than replicas",
18295 replicas: 3,
18296 fullyLabeledReplicas: 3,
18297 readyReplicas: 4,
18298 availableReplicas: 3,
18299 observedGeneration: 1,
18300 expectedErr: true,
18301 }, {
18302 name: "availableReplicas greater than replicas",
18303 replicas: 3,
18304 fullyLabeledReplicas: 3,
18305 readyReplicas: 3,
18306 availableReplicas: 4,
18307 observedGeneration: 1,
18308 expectedErr: true,
18309 }, {
18310 name: "availableReplicas greater than readyReplicas",
18311 replicas: 3,
18312 fullyLabeledReplicas: 3,
18313 readyReplicas: 2,
18314 availableReplicas: 3,
18315 observedGeneration: 1,
18316 expectedErr: true,
18317 },
18318 }
18319
18320 for _, test := range tests {
18321 status := core.ReplicationControllerStatus{
18322 Replicas: test.replicas,
18323 FullyLabeledReplicas: test.fullyLabeledReplicas,
18324 ReadyReplicas: test.readyReplicas,
18325 AvailableReplicas: test.availableReplicas,
18326 ObservedGeneration: test.observedGeneration,
18327 }
18328
18329 if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
18330 t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
18331 }
18332 }
18333 }
18334
18335 func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
18336 validSelector := map[string]string{"a": "b"}
18337 validPodTemplate := core.PodTemplate{
18338 Template: core.PodTemplateSpec{
18339 ObjectMeta: metav1.ObjectMeta{
18340 Labels: validSelector,
18341 },
18342 Spec: core.PodSpec{
18343 RestartPolicy: core.RestartPolicyAlways,
18344 DNSPolicy: core.DNSClusterFirst,
18345 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18346 },
18347 },
18348 }
18349 type rcUpdateTest struct {
18350 old core.ReplicationController
18351 update core.ReplicationController
18352 }
18353 successCases := []rcUpdateTest{{
18354 old: core.ReplicationController{
18355 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18356 Spec: core.ReplicationControllerSpec{
18357 Selector: validSelector,
18358 Template: &validPodTemplate.Template,
18359 },
18360 Status: core.ReplicationControllerStatus{
18361 Replicas: 2,
18362 },
18363 },
18364 update: core.ReplicationController{
18365 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18366 Spec: core.ReplicationControllerSpec{
18367 Replicas: 3,
18368 Selector: validSelector,
18369 Template: &validPodTemplate.Template,
18370 },
18371 Status: core.ReplicationControllerStatus{
18372 Replicas: 4,
18373 },
18374 },
18375 },
18376 }
18377 for _, successCase := range successCases {
18378 successCase.old.ObjectMeta.ResourceVersion = "1"
18379 successCase.update.ObjectMeta.ResourceVersion = "1"
18380 if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
18381 t.Errorf("expected success: %v", errs)
18382 }
18383 }
18384 errorCases := map[string]rcUpdateTest{
18385 "negative replicas": {
18386 old: core.ReplicationController{
18387 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
18388 Spec: core.ReplicationControllerSpec{
18389 Selector: validSelector,
18390 Template: &validPodTemplate.Template,
18391 },
18392 Status: core.ReplicationControllerStatus{
18393 Replicas: 3,
18394 },
18395 },
18396 update: core.ReplicationController{
18397 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18398 Spec: core.ReplicationControllerSpec{
18399 Replicas: 2,
18400 Selector: validSelector,
18401 Template: &validPodTemplate.Template,
18402 },
18403 Status: core.ReplicationControllerStatus{
18404 Replicas: -3,
18405 },
18406 },
18407 },
18408 }
18409 for testName, errorCase := range errorCases {
18410 if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
18411 t.Errorf("expected failure: %s", testName)
18412 }
18413 }
18414
18415 }
18416
18417 func TestValidateReplicationControllerUpdate(t *testing.T) {
18418 validSelector := map[string]string{"a": "b"}
18419 validPodTemplate := core.PodTemplate{
18420 Template: core.PodTemplateSpec{
18421 ObjectMeta: metav1.ObjectMeta{
18422 Labels: validSelector,
18423 },
18424 Spec: core.PodSpec{
18425 RestartPolicy: core.RestartPolicyAlways,
18426 DNSPolicy: core.DNSClusterFirst,
18427 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18428 },
18429 },
18430 }
18431 readWriteVolumePodTemplate := core.PodTemplate{
18432 Template: core.PodTemplateSpec{
18433 ObjectMeta: metav1.ObjectMeta{
18434 Labels: validSelector,
18435 },
18436 Spec: core.PodSpec{
18437 RestartPolicy: core.RestartPolicyAlways,
18438 DNSPolicy: core.DNSClusterFirst,
18439 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18440 Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
18441 },
18442 },
18443 }
18444 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
18445 invalidPodTemplate := core.PodTemplate{
18446 Template: core.PodTemplateSpec{
18447 Spec: core.PodSpec{
18448 RestartPolicy: core.RestartPolicyAlways,
18449 DNSPolicy: core.DNSClusterFirst,
18450 },
18451 ObjectMeta: metav1.ObjectMeta{
18452 Labels: invalidSelector,
18453 },
18454 },
18455 }
18456 type rcUpdateTest struct {
18457 old core.ReplicationController
18458 update core.ReplicationController
18459 }
18460 successCases := []rcUpdateTest{{
18461 old: core.ReplicationController{
18462 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18463 Spec: core.ReplicationControllerSpec{
18464 Selector: validSelector,
18465 Template: &validPodTemplate.Template,
18466 },
18467 },
18468 update: core.ReplicationController{
18469 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18470 Spec: core.ReplicationControllerSpec{
18471 Replicas: 3,
18472 Selector: validSelector,
18473 Template: &validPodTemplate.Template,
18474 },
18475 },
18476 }, {
18477 old: core.ReplicationController{
18478 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18479 Spec: core.ReplicationControllerSpec{
18480 Selector: validSelector,
18481 Template: &validPodTemplate.Template,
18482 },
18483 },
18484 update: core.ReplicationController{
18485 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18486 Spec: core.ReplicationControllerSpec{
18487 Replicas: 1,
18488 Selector: validSelector,
18489 Template: &readWriteVolumePodTemplate.Template,
18490 },
18491 },
18492 },
18493 }
18494 for _, successCase := range successCases {
18495 successCase.old.ObjectMeta.ResourceVersion = "1"
18496 successCase.update.ObjectMeta.ResourceVersion = "1"
18497 if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old, PodValidationOptions{}); len(errs) != 0 {
18498 t.Errorf("expected success: %v", errs)
18499 }
18500 }
18501 errorCases := map[string]rcUpdateTest{
18502 "more than one read/write": {
18503 old: core.ReplicationController{
18504 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
18505 Spec: core.ReplicationControllerSpec{
18506 Selector: validSelector,
18507 Template: &validPodTemplate.Template,
18508 },
18509 },
18510 update: core.ReplicationController{
18511 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18512 Spec: core.ReplicationControllerSpec{
18513 Replicas: 2,
18514 Selector: validSelector,
18515 Template: &readWriteVolumePodTemplate.Template,
18516 },
18517 },
18518 },
18519 "invalid selector": {
18520 old: core.ReplicationController{
18521 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
18522 Spec: core.ReplicationControllerSpec{
18523 Selector: validSelector,
18524 Template: &validPodTemplate.Template,
18525 },
18526 },
18527 update: core.ReplicationController{
18528 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18529 Spec: core.ReplicationControllerSpec{
18530 Replicas: 2,
18531 Selector: invalidSelector,
18532 Template: &validPodTemplate.Template,
18533 },
18534 },
18535 },
18536 "invalid pod": {
18537 old: core.ReplicationController{
18538 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
18539 Spec: core.ReplicationControllerSpec{
18540 Selector: validSelector,
18541 Template: &validPodTemplate.Template,
18542 },
18543 },
18544 update: core.ReplicationController{
18545 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18546 Spec: core.ReplicationControllerSpec{
18547 Replicas: 2,
18548 Selector: validSelector,
18549 Template: &invalidPodTemplate.Template,
18550 },
18551 },
18552 },
18553 "negative replicas": {
18554 old: core.ReplicationController{
18555 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18556 Spec: core.ReplicationControllerSpec{
18557 Selector: validSelector,
18558 Template: &validPodTemplate.Template,
18559 },
18560 },
18561 update: core.ReplicationController{
18562 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18563 Spec: core.ReplicationControllerSpec{
18564 Replicas: -1,
18565 Selector: validSelector,
18566 Template: &validPodTemplate.Template,
18567 },
18568 },
18569 },
18570 }
18571 for testName, errorCase := range errorCases {
18572 if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old, PodValidationOptions{}); len(errs) == 0 {
18573 t.Errorf("expected failure: %s", testName)
18574 }
18575 }
18576 }
18577
18578 func TestValidateReplicationController(t *testing.T) {
18579 validSelector := map[string]string{"a": "b"}
18580 validPodTemplate := core.PodTemplate{
18581 Template: core.PodTemplateSpec{
18582 ObjectMeta: metav1.ObjectMeta{
18583 Labels: validSelector,
18584 },
18585 Spec: core.PodSpec{
18586 RestartPolicy: core.RestartPolicyAlways,
18587 DNSPolicy: core.DNSClusterFirst,
18588 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18589 },
18590 },
18591 }
18592 readWriteVolumePodTemplate := core.PodTemplate{
18593 Template: core.PodTemplateSpec{
18594 ObjectMeta: metav1.ObjectMeta{
18595 Labels: validSelector,
18596 },
18597 Spec: core.PodSpec{
18598 Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
18599 RestartPolicy: core.RestartPolicyAlways,
18600 DNSPolicy: core.DNSClusterFirst,
18601 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18602 },
18603 },
18604 }
18605 hostnetPodTemplate := core.PodTemplate{
18606 Template: core.PodTemplateSpec{
18607 ObjectMeta: metav1.ObjectMeta{
18608 Labels: validSelector,
18609 },
18610 Spec: core.PodSpec{
18611 SecurityContext: &core.PodSecurityContext{
18612 HostNetwork: true,
18613 },
18614 RestartPolicy: core.RestartPolicyAlways,
18615 DNSPolicy: core.DNSClusterFirst,
18616 Containers: []core.Container{{
18617 Name: "abc",
18618 Image: "image",
18619 ImagePullPolicy: "IfNotPresent",
18620 TerminationMessagePolicy: "File",
18621 Ports: []core.ContainerPort{{
18622 ContainerPort: 12345,
18623 Protocol: core.ProtocolTCP,
18624 }},
18625 }},
18626 },
18627 },
18628 }
18629 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
18630 invalidPodTemplate := core.PodTemplate{
18631 Template: core.PodTemplateSpec{
18632 Spec: core.PodSpec{
18633 RestartPolicy: core.RestartPolicyAlways,
18634 DNSPolicy: core.DNSClusterFirst,
18635 },
18636 ObjectMeta: metav1.ObjectMeta{
18637 Labels: invalidSelector,
18638 },
18639 },
18640 }
18641 successCases := []core.ReplicationController{{
18642 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18643 Spec: core.ReplicationControllerSpec{
18644 Selector: validSelector,
18645 Template: &validPodTemplate.Template,
18646 },
18647 }, {
18648 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
18649 Spec: core.ReplicationControllerSpec{
18650 Selector: validSelector,
18651 Template: &validPodTemplate.Template,
18652 },
18653 }, {
18654 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
18655 Spec: core.ReplicationControllerSpec{
18656 Replicas: 1,
18657 Selector: validSelector,
18658 Template: &readWriteVolumePodTemplate.Template,
18659 },
18660 }, {
18661 ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
18662 Spec: core.ReplicationControllerSpec{
18663 Replicas: 1,
18664 Selector: validSelector,
18665 Template: &hostnetPodTemplate.Template,
18666 },
18667 }}
18668 for _, successCase := range successCases {
18669 if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 {
18670 t.Errorf("expected success: %v", errs)
18671 }
18672 }
18673
18674 errorCases := map[string]core.ReplicationController{
18675 "zero-length ID": {
18676 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
18677 Spec: core.ReplicationControllerSpec{
18678 Selector: validSelector,
18679 Template: &validPodTemplate.Template,
18680 },
18681 },
18682 "missing-namespace": {
18683 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
18684 Spec: core.ReplicationControllerSpec{
18685 Selector: validSelector,
18686 Template: &validPodTemplate.Template,
18687 },
18688 },
18689 "empty selector": {
18690 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18691 Spec: core.ReplicationControllerSpec{
18692 Template: &validPodTemplate.Template,
18693 },
18694 },
18695 "selector_doesnt_match": {
18696 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18697 Spec: core.ReplicationControllerSpec{
18698 Selector: map[string]string{"foo": "bar"},
18699 Template: &validPodTemplate.Template,
18700 },
18701 },
18702 "invalid manifest": {
18703 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18704 Spec: core.ReplicationControllerSpec{
18705 Selector: validSelector,
18706 },
18707 },
18708 "read-write persistent disk with > 1 pod": {
18709 ObjectMeta: metav1.ObjectMeta{Name: "abc"},
18710 Spec: core.ReplicationControllerSpec{
18711 Replicas: 2,
18712 Selector: validSelector,
18713 Template: &readWriteVolumePodTemplate.Template,
18714 },
18715 },
18716 "negative_replicas": {
18717 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
18718 Spec: core.ReplicationControllerSpec{
18719 Replicas: -1,
18720 Selector: validSelector,
18721 },
18722 },
18723 "invalid_label": {
18724 ObjectMeta: metav1.ObjectMeta{
18725 Name: "abc-123",
18726 Namespace: metav1.NamespaceDefault,
18727 Labels: map[string]string{
18728 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
18729 },
18730 },
18731 Spec: core.ReplicationControllerSpec{
18732 Selector: validSelector,
18733 Template: &validPodTemplate.Template,
18734 },
18735 },
18736 "invalid_label 2": {
18737 ObjectMeta: metav1.ObjectMeta{
18738 Name: "abc-123",
18739 Namespace: metav1.NamespaceDefault,
18740 Labels: map[string]string{
18741 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
18742 },
18743 },
18744 Spec: core.ReplicationControllerSpec{
18745 Template: &invalidPodTemplate.Template,
18746 },
18747 },
18748 "invalid_annotation": {
18749 ObjectMeta: metav1.ObjectMeta{
18750 Name: "abc-123",
18751 Namespace: metav1.NamespaceDefault,
18752 Annotations: map[string]string{
18753 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
18754 },
18755 },
18756 Spec: core.ReplicationControllerSpec{
18757 Selector: validSelector,
18758 Template: &validPodTemplate.Template,
18759 },
18760 },
18761 "invalid restart policy 1": {
18762 ObjectMeta: metav1.ObjectMeta{
18763 Name: "abc-123",
18764 Namespace: metav1.NamespaceDefault,
18765 },
18766 Spec: core.ReplicationControllerSpec{
18767 Selector: validSelector,
18768 Template: &core.PodTemplateSpec{
18769 Spec: core.PodSpec{
18770 RestartPolicy: core.RestartPolicyOnFailure,
18771 DNSPolicy: core.DNSClusterFirst,
18772 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18773 },
18774 ObjectMeta: metav1.ObjectMeta{
18775 Labels: validSelector,
18776 },
18777 },
18778 },
18779 },
18780 "invalid restart policy 2": {
18781 ObjectMeta: metav1.ObjectMeta{
18782 Name: "abc-123",
18783 Namespace: metav1.NamespaceDefault,
18784 },
18785 Spec: core.ReplicationControllerSpec{
18786 Selector: validSelector,
18787 Template: &core.PodTemplateSpec{
18788 Spec: core.PodSpec{
18789 RestartPolicy: core.RestartPolicyNever,
18790 DNSPolicy: core.DNSClusterFirst,
18791 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18792 },
18793 ObjectMeta: metav1.ObjectMeta{
18794 Labels: validSelector,
18795 },
18796 },
18797 },
18798 },
18799 "template may not contain ephemeral containers": {
18800 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
18801 Spec: core.ReplicationControllerSpec{
18802 Replicas: 1,
18803 Selector: validSelector,
18804 Template: &core.PodTemplateSpec{
18805 ObjectMeta: metav1.ObjectMeta{
18806 Labels: validSelector,
18807 },
18808 Spec: core.PodSpec{
18809 RestartPolicy: core.RestartPolicyAlways,
18810 DNSPolicy: core.DNSClusterFirst,
18811 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
18812 EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
18813 },
18814 },
18815 },
18816 },
18817 }
18818 for k, v := range errorCases {
18819 errs := ValidateReplicationController(&v, PodValidationOptions{})
18820 if len(errs) == 0 {
18821 t.Errorf("expected failure for %s", k)
18822 }
18823 for i := range errs {
18824 field := errs[i].Field
18825 if !strings.HasPrefix(field, "spec.template.") &&
18826 field != "metadata.name" &&
18827 field != "metadata.namespace" &&
18828 field != "spec.selector" &&
18829 field != "spec.template" &&
18830 field != "GCEPersistentDisk.ReadOnly" &&
18831 field != "spec.replicas" &&
18832 field != "spec.template.labels" &&
18833 field != "metadata.annotations" &&
18834 field != "metadata.labels" &&
18835 field != "status.replicas" {
18836 t.Errorf("%s: missing prefix for: %v", k, errs[i])
18837 }
18838 }
18839 }
18840 }
18841
18842 func TestValidateNode(t *testing.T) {
18843 validSelector := map[string]string{"a": "b"}
18844 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
18845 successCases := []core.Node{{
18846 ObjectMeta: metav1.ObjectMeta{
18847 Name: "abc",
18848 Labels: validSelector,
18849 },
18850 Status: core.NodeStatus{
18851 Addresses: []core.NodeAddress{
18852 {Type: core.NodeExternalIP, Address: "something"},
18853 },
18854 Capacity: core.ResourceList{
18855 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18856 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
18857 core.ResourceName("my.org/gpu"): resource.MustParse("10"),
18858 core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"),
18859 core.ResourceName("hugepages-1Gi"): resource.MustParse("0"),
18860 },
18861 },
18862 }, {
18863 ObjectMeta: metav1.ObjectMeta{
18864 Name: "abc",
18865 },
18866 Status: core.NodeStatus{
18867 Addresses: []core.NodeAddress{
18868 {Type: core.NodeExternalIP, Address: "something"},
18869 },
18870 Capacity: core.ResourceList{
18871 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18872 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
18873 },
18874 },
18875 }, {
18876 ObjectMeta: metav1.ObjectMeta{
18877 Name: "abc",
18878 Labels: validSelector,
18879 },
18880 Status: core.NodeStatus{
18881 Addresses: []core.NodeAddress{
18882 {Type: core.NodeExternalIP, Address: "something"},
18883 },
18884 Capacity: core.ResourceList{
18885 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18886 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
18887 core.ResourceName("my.org/gpu"): resource.MustParse("10"),
18888 core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"),
18889 core.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"),
18890 },
18891 },
18892 }, {
18893 ObjectMeta: metav1.ObjectMeta{
18894 Name: "dedicated-node1",
18895 },
18896 Status: core.NodeStatus{
18897 Addresses: []core.NodeAddress{
18898 {Type: core.NodeExternalIP, Address: "something"},
18899 },
18900 Capacity: core.ResourceList{
18901 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18902 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
18903 },
18904 },
18905 Spec: core.NodeSpec{
18906
18907 Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}},
18908 },
18909 }, {
18910 ObjectMeta: metav1.ObjectMeta{
18911 Name: "abc",
18912 Annotations: map[string]string{
18913 core.PreferAvoidPodsAnnotationKey: `
18914 {
18915 "preferAvoidPods": [
18916 {
18917 "podSignature": {
18918 "podController": {
18919 "apiVersion": "v1",
18920 "kind": "ReplicationController",
18921 "name": "foo",
18922 "uid": "abcdef123456",
18923 "controller": true
18924 }
18925 },
18926 "reason": "some reason",
18927 "message": "some message"
18928 }
18929 ]
18930 }`,
18931 },
18932 },
18933 Status: core.NodeStatus{
18934 Addresses: []core.NodeAddress{
18935 {Type: core.NodeExternalIP, Address: "something"},
18936 },
18937 Capacity: core.ResourceList{
18938 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18939 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
18940 },
18941 },
18942 }, {
18943 ObjectMeta: metav1.ObjectMeta{
18944 Name: "abc",
18945 },
18946 Status: core.NodeStatus{
18947 Addresses: []core.NodeAddress{
18948 {Type: core.NodeExternalIP, Address: "something"},
18949 },
18950 Capacity: core.ResourceList{
18951 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18952 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
18953 },
18954 },
18955 Spec: core.NodeSpec{
18956 PodCIDRs: []string{"192.168.0.0/16"},
18957 },
18958 },
18959 }
18960 for _, successCase := range successCases {
18961 if errs := ValidateNode(&successCase); len(errs) != 0 {
18962 t.Errorf("expected success: %v", errs)
18963 }
18964 }
18965
18966 errorCases := map[string]core.Node{
18967 "zero-length Name": {
18968 ObjectMeta: metav1.ObjectMeta{
18969 Name: "",
18970 Labels: validSelector,
18971 },
18972 Status: core.NodeStatus{
18973 Addresses: []core.NodeAddress{},
18974 Capacity: core.ResourceList{
18975 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18976 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
18977 },
18978 },
18979 },
18980 "invalid-labels": {
18981 ObjectMeta: metav1.ObjectMeta{
18982 Name: "abc-123",
18983 Labels: invalidSelector,
18984 },
18985 Status: core.NodeStatus{
18986 Capacity: core.ResourceList{
18987 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
18988 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
18989 },
18990 },
18991 },
18992 "missing-taint-key": {
18993 ObjectMeta: metav1.ObjectMeta{
18994 Name: "dedicated-node1",
18995 },
18996 Spec: core.NodeSpec{
18997
18998 Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}},
18999 },
19000 },
19001 "bad-taint-key": {
19002 ObjectMeta: metav1.ObjectMeta{
19003 Name: "dedicated-node1",
19004 },
19005 Spec: core.NodeSpec{
19006
19007 Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}},
19008 },
19009 },
19010 "bad-taint-value": {
19011 ObjectMeta: metav1.ObjectMeta{
19012 Name: "dedicated-node2",
19013 },
19014 Status: core.NodeStatus{
19015 Addresses: []core.NodeAddress{
19016 {Type: core.NodeExternalIP, Address: "something"},
19017 },
19018 Capacity: core.ResourceList{
19019 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19020 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19021 },
19022 },
19023 Spec: core.NodeSpec{
19024
19025 Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}},
19026 },
19027 },
19028 "missing-taint-effect": {
19029 ObjectMeta: metav1.ObjectMeta{
19030 Name: "dedicated-node3",
19031 },
19032 Status: core.NodeStatus{
19033 Addresses: []core.NodeAddress{
19034 {Type: core.NodeExternalIP, Address: "something"},
19035 },
19036 Capacity: core.ResourceList{
19037 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19038 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19039 },
19040 },
19041 Spec: core.NodeSpec{
19042
19043 Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}},
19044 },
19045 },
19046 "invalid-taint-effect": {
19047 ObjectMeta: metav1.ObjectMeta{
19048 Name: "dedicated-node3",
19049 },
19050 Status: core.NodeStatus{
19051 Addresses: []core.NodeAddress{
19052 {Type: core.NodeExternalIP, Address: "something"},
19053 },
19054 Capacity: core.ResourceList{
19055 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19056 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19057 },
19058 },
19059 Spec: core.NodeSpec{
19060
19061 Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}},
19062 },
19063 },
19064 "duplicated-taints-with-same-key-effect": {
19065 ObjectMeta: metav1.ObjectMeta{
19066 Name: "dedicated-node1",
19067 },
19068 Spec: core.NodeSpec{
19069
19070 Taints: []core.Taint{
19071 {Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"},
19072 {Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"},
19073 },
19074 },
19075 },
19076 "missing-podSignature": {
19077 ObjectMeta: metav1.ObjectMeta{
19078 Name: "abc-123",
19079 Annotations: map[string]string{
19080 core.PreferAvoidPodsAnnotationKey: `
19081 {
19082 "preferAvoidPods": [
19083 {
19084 "reason": "some reason",
19085 "message": "some message"
19086 }
19087 ]
19088 }`,
19089 },
19090 },
19091 Status: core.NodeStatus{
19092 Addresses: []core.NodeAddress{},
19093 Capacity: core.ResourceList{
19094 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19095 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19096 },
19097 },
19098 },
19099 "invalid-podController": {
19100 ObjectMeta: metav1.ObjectMeta{
19101 Name: "abc-123",
19102 Annotations: map[string]string{
19103 core.PreferAvoidPodsAnnotationKey: `
19104 {
19105 "preferAvoidPods": [
19106 {
19107 "podSignature": {
19108 "podController": {
19109 "apiVersion": "v1",
19110 "kind": "ReplicationController",
19111 "name": "foo",
19112 "uid": "abcdef123456",
19113 "controller": false
19114 }
19115 },
19116 "reason": "some reason",
19117 "message": "some message"
19118 }
19119 ]
19120 }`,
19121 },
19122 },
19123 Status: core.NodeStatus{
19124 Addresses: []core.NodeAddress{},
19125 Capacity: core.ResourceList{
19126 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19127 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19128 },
19129 },
19130 },
19131 "invalid-pod-cidr": {
19132 ObjectMeta: metav1.ObjectMeta{
19133 Name: "abc",
19134 },
19135 Status: core.NodeStatus{
19136 Addresses: []core.NodeAddress{
19137 {Type: core.NodeExternalIP, Address: "something"},
19138 },
19139 Capacity: core.ResourceList{
19140 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19141 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19142 },
19143 },
19144 Spec: core.NodeSpec{
19145 PodCIDRs: []string{"192.168.0.0"},
19146 },
19147 },
19148 "duplicate-pod-cidr": {
19149 ObjectMeta: metav1.ObjectMeta{
19150 Name: "abc",
19151 },
19152 Status: core.NodeStatus{
19153 Addresses: []core.NodeAddress{
19154 {Type: core.NodeExternalIP, Address: "something"},
19155 },
19156 Capacity: core.ResourceList{
19157 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19158 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
19159 },
19160 },
19161 Spec: core.NodeSpec{
19162 PodCIDRs: []string{"10.0.0.1/16", "10.0.0.1/16"},
19163 },
19164 },
19165 }
19166 for k, v := range errorCases {
19167 errs := ValidateNode(&v)
19168 if len(errs) == 0 {
19169 t.Errorf("expected failure for %s", k)
19170 }
19171 for i := range errs {
19172 field := errs[i].Field
19173 expectedFields := map[string]bool{
19174 "metadata.name": true,
19175 "metadata.labels": true,
19176 "metadata.annotations": true,
19177 "metadata.namespace": true,
19178 "spec.externalID": true,
19179 "spec.taints[0].key": true,
19180 "spec.taints[0].value": true,
19181 "spec.taints[0].effect": true,
19182 "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature": true,
19183 "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true,
19184 }
19185 if val, ok := expectedFields[field]; ok {
19186 if !val {
19187 t.Errorf("%s: missing prefix for: %v", k, errs[i])
19188 }
19189 }
19190 }
19191 }
19192 }
19193
19194 func TestValidateNodeUpdate(t *testing.T) {
19195 tests := []struct {
19196 oldNode core.Node
19197 node core.Node
19198 valid bool
19199 }{
19200 {core.Node{}, core.Node{}, true},
19201 {core.Node{
19202 ObjectMeta: metav1.ObjectMeta{
19203 Name: "foo"}},
19204 core.Node{
19205 ObjectMeta: metav1.ObjectMeta{
19206 Name: "bar"},
19207 }, false},
19208 {core.Node{
19209 ObjectMeta: metav1.ObjectMeta{
19210 Name: "foo",
19211 Labels: map[string]string{"foo": "bar"},
19212 },
19213 }, core.Node{
19214 ObjectMeta: metav1.ObjectMeta{
19215 Name: "foo",
19216 Labels: map[string]string{"foo": "baz"},
19217 },
19218 }, true},
19219 {core.Node{
19220 ObjectMeta: metav1.ObjectMeta{
19221 Name: "foo",
19222 },
19223 }, core.Node{
19224 ObjectMeta: metav1.ObjectMeta{
19225 Name: "foo",
19226 Labels: map[string]string{"foo": "baz"},
19227 },
19228 }, true},
19229 {core.Node{
19230 ObjectMeta: metav1.ObjectMeta{
19231 Name: "foo",
19232 Labels: map[string]string{"bar": "foo"},
19233 },
19234 }, core.Node{
19235 ObjectMeta: metav1.ObjectMeta{
19236 Name: "foo",
19237 Labels: map[string]string{"foo": "baz"},
19238 },
19239 }, true},
19240 {core.Node{
19241 ObjectMeta: metav1.ObjectMeta{
19242 Name: "foo",
19243 },
19244 Spec: core.NodeSpec{
19245 PodCIDRs: []string{},
19246 },
19247 }, core.Node{
19248 ObjectMeta: metav1.ObjectMeta{
19249 Name: "foo",
19250 },
19251 Spec: core.NodeSpec{
19252 PodCIDRs: []string{"192.168.0.0/16"},
19253 },
19254 }, true},
19255 {core.Node{
19256 ObjectMeta: metav1.ObjectMeta{
19257 Name: "foo",
19258 },
19259 Spec: core.NodeSpec{
19260 PodCIDRs: []string{"192.123.0.0/16"},
19261 },
19262 }, core.Node{
19263 ObjectMeta: metav1.ObjectMeta{
19264 Name: "foo",
19265 },
19266 Spec: core.NodeSpec{
19267 PodCIDRs: []string{"192.168.0.0/16"},
19268 },
19269 }, false},
19270 {core.Node{
19271 ObjectMeta: metav1.ObjectMeta{
19272 Name: "foo",
19273 },
19274 Status: core.NodeStatus{
19275 Capacity: core.ResourceList{
19276 core.ResourceCPU: resource.MustParse("10000"),
19277 core.ResourceMemory: resource.MustParse("100"),
19278 },
19279 },
19280 }, core.Node{
19281 ObjectMeta: metav1.ObjectMeta{
19282 Name: "foo",
19283 },
19284 Status: core.NodeStatus{
19285 Capacity: core.ResourceList{
19286 core.ResourceCPU: resource.MustParse("100"),
19287 core.ResourceMemory: resource.MustParse("10000"),
19288 },
19289 },
19290 }, true},
19291 {core.Node{
19292 ObjectMeta: metav1.ObjectMeta{
19293 Name: "foo",
19294 Labels: map[string]string{"bar": "foo"},
19295 },
19296 Status: core.NodeStatus{
19297 Capacity: core.ResourceList{
19298 core.ResourceCPU: resource.MustParse("10000"),
19299 core.ResourceMemory: resource.MustParse("100"),
19300 },
19301 },
19302 }, core.Node{
19303 ObjectMeta: metav1.ObjectMeta{
19304 Name: "foo",
19305 Labels: map[string]string{"bar": "fooobaz"},
19306 },
19307 Status: core.NodeStatus{
19308 Capacity: core.ResourceList{
19309 core.ResourceCPU: resource.MustParse("100"),
19310 core.ResourceMemory: resource.MustParse("10000"),
19311 },
19312 },
19313 }, true},
19314 {core.Node{
19315 ObjectMeta: metav1.ObjectMeta{
19316 Name: "foo",
19317 Labels: map[string]string{"bar": "foo"},
19318 },
19319 Status: core.NodeStatus{
19320 Addresses: []core.NodeAddress{
19321 {Type: core.NodeExternalIP, Address: "1.2.3.4"},
19322 },
19323 },
19324 }, core.Node{
19325 ObjectMeta: metav1.ObjectMeta{
19326 Name: "foo",
19327 Labels: map[string]string{"bar": "fooobaz"},
19328 },
19329 }, true},
19330 {core.Node{
19331 ObjectMeta: metav1.ObjectMeta{
19332 Name: "foo",
19333 Labels: map[string]string{"foo": "baz"},
19334 },
19335 }, core.Node{
19336 ObjectMeta: metav1.ObjectMeta{
19337 Name: "foo",
19338 Labels: map[string]string{"Foo": "baz"},
19339 },
19340 }, true},
19341 {core.Node{
19342 ObjectMeta: metav1.ObjectMeta{
19343 Name: "foo",
19344 },
19345 Spec: core.NodeSpec{
19346 Unschedulable: false,
19347 },
19348 }, core.Node{
19349 ObjectMeta: metav1.ObjectMeta{
19350 Name: "foo",
19351 },
19352 Spec: core.NodeSpec{
19353 Unschedulable: true,
19354 },
19355 }, true},
19356 {core.Node{
19357 ObjectMeta: metav1.ObjectMeta{
19358 Name: "foo",
19359 },
19360 Spec: core.NodeSpec{
19361 Unschedulable: false,
19362 },
19363 }, core.Node{
19364 ObjectMeta: metav1.ObjectMeta{
19365 Name: "foo",
19366 },
19367 Status: core.NodeStatus{
19368 Addresses: []core.NodeAddress{
19369 {Type: core.NodeExternalIP, Address: "1.1.1.1"},
19370 {Type: core.NodeExternalIP, Address: "1.1.1.1"},
19371 },
19372 },
19373 }, false},
19374 {core.Node{
19375 ObjectMeta: metav1.ObjectMeta{
19376 Name: "foo",
19377 },
19378 Spec: core.NodeSpec{
19379 Unschedulable: false,
19380 },
19381 }, core.Node{
19382 ObjectMeta: metav1.ObjectMeta{
19383 Name: "foo",
19384 },
19385 Status: core.NodeStatus{
19386 Addresses: []core.NodeAddress{
19387 {Type: core.NodeExternalIP, Address: "1.1.1.1"},
19388 {Type: core.NodeInternalIP, Address: "10.1.1.1"},
19389 },
19390 },
19391 }, true},
19392 {core.Node{
19393 ObjectMeta: metav1.ObjectMeta{
19394 Name: "foo",
19395 },
19396 }, core.Node{
19397 ObjectMeta: metav1.ObjectMeta{
19398 Name: "foo",
19399 Annotations: map[string]string{
19400 core.PreferAvoidPodsAnnotationKey: `
19401 {
19402 "preferAvoidPods": [
19403 {
19404 "podSignature": {
19405 "podController": {
19406 "apiVersion": "v1",
19407 "kind": "ReplicationController",
19408 "name": "foo",
19409 "uid": "abcdef123456",
19410 "controller": true
19411 }
19412 },
19413 "reason": "some reason",
19414 "message": "some message"
19415 }
19416 ]
19417 }`,
19418 },
19419 },
19420 Spec: core.NodeSpec{
19421 Unschedulable: false,
19422 },
19423 }, true},
19424 {core.Node{
19425 ObjectMeta: metav1.ObjectMeta{
19426 Name: "foo",
19427 },
19428 }, core.Node{
19429 ObjectMeta: metav1.ObjectMeta{
19430 Name: "foo",
19431 Annotations: map[string]string{
19432 core.PreferAvoidPodsAnnotationKey: `
19433 {
19434 "preferAvoidPods": [
19435 {
19436 "reason": "some reason",
19437 "message": "some message"
19438 }
19439 ]
19440 }`,
19441 },
19442 },
19443 }, false},
19444 {core.Node{
19445 ObjectMeta: metav1.ObjectMeta{
19446 Name: "foo",
19447 },
19448 }, core.Node{
19449 ObjectMeta: metav1.ObjectMeta{
19450 Name: "foo",
19451 Annotations: map[string]string{
19452 core.PreferAvoidPodsAnnotationKey: `
19453 {
19454 "preferAvoidPods": [
19455 {
19456 "podSignature": {
19457 "podController": {
19458 "apiVersion": "v1",
19459 "kind": "ReplicationController",
19460 "name": "foo",
19461 "uid": "abcdef123456",
19462 "controller": false
19463 }
19464 },
19465 "reason": "some reason",
19466 "message": "some message"
19467 }
19468 ]
19469 }`,
19470 },
19471 },
19472 }, false},
19473 {core.Node{
19474 ObjectMeta: metav1.ObjectMeta{
19475 Name: "valid-extended-resources",
19476 },
19477 }, core.Node{
19478 ObjectMeta: metav1.ObjectMeta{
19479 Name: "valid-extended-resources",
19480 },
19481 Status: core.NodeStatus{
19482 Capacity: core.ResourceList{
19483 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19484 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
19485 core.ResourceName("example.com/a"): resource.MustParse("5"),
19486 core.ResourceName("example.com/b"): resource.MustParse("10"),
19487 },
19488 },
19489 }, true},
19490 {core.Node{
19491 ObjectMeta: metav1.ObjectMeta{
19492 Name: "invalid-fractional-extended-capacity",
19493 },
19494 }, core.Node{
19495 ObjectMeta: metav1.ObjectMeta{
19496 Name: "invalid-fractional-extended-capacity",
19497 },
19498 Status: core.NodeStatus{
19499 Capacity: core.ResourceList{
19500 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19501 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
19502 core.ResourceName("example.com/a"): resource.MustParse("500m"),
19503 },
19504 },
19505 }, false},
19506 {core.Node{
19507 ObjectMeta: metav1.ObjectMeta{
19508 Name: "invalid-fractional-extended-allocatable",
19509 },
19510 }, core.Node{
19511 ObjectMeta: metav1.ObjectMeta{
19512 Name: "invalid-fractional-extended-allocatable",
19513 },
19514 Status: core.NodeStatus{
19515 Capacity: core.ResourceList{
19516 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19517 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
19518 core.ResourceName("example.com/a"): resource.MustParse("5"),
19519 },
19520 Allocatable: core.ResourceList{
19521 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
19522 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
19523 core.ResourceName("example.com/a"): resource.MustParse("4.5"),
19524 },
19525 },
19526 }, false},
19527 {core.Node{
19528 ObjectMeta: metav1.ObjectMeta{
19529 Name: "update-provider-id-when-not-set",
19530 },
19531 }, core.Node{
19532 ObjectMeta: metav1.ObjectMeta{
19533 Name: "update-provider-id-when-not-set",
19534 },
19535 Spec: core.NodeSpec{
19536 ProviderID: "provider:///new",
19537 },
19538 }, true},
19539 {core.Node{
19540 ObjectMeta: metav1.ObjectMeta{
19541 Name: "update-provider-id-when-set",
19542 },
19543 Spec: core.NodeSpec{
19544 ProviderID: "provider:///old",
19545 },
19546 }, core.Node{
19547 ObjectMeta: metav1.ObjectMeta{
19548 Name: "update-provider-id-when-set",
19549 },
19550 Spec: core.NodeSpec{
19551 ProviderID: "provider:///new",
19552 },
19553 }, false},
19554 {core.Node{
19555 ObjectMeta: metav1.ObjectMeta{
19556 Name: "pod-cidrs-as-is",
19557 },
19558 Spec: core.NodeSpec{
19559 PodCIDRs: []string{"192.168.0.0/16"},
19560 },
19561 }, core.Node{
19562 ObjectMeta: metav1.ObjectMeta{
19563 Name: "pod-cidrs-as-is",
19564 },
19565 Spec: core.NodeSpec{
19566 PodCIDRs: []string{"192.168.0.0/16"},
19567 },
19568 }, true},
19569 {core.Node{
19570 ObjectMeta: metav1.ObjectMeta{
19571 Name: "pod-cidrs-as-is-2",
19572 },
19573 Spec: core.NodeSpec{
19574 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
19575 },
19576 }, core.Node{
19577 ObjectMeta: metav1.ObjectMeta{
19578 Name: "pod-cidrs-as-is-2",
19579 },
19580 Spec: core.NodeSpec{
19581 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
19582 },
19583 }, true},
19584 {core.Node{
19585 ObjectMeta: metav1.ObjectMeta{
19586 Name: "pod-cidrs-not-same-length",
19587 },
19588 Spec: core.NodeSpec{
19589 PodCIDRs: []string{"192.168.0.0/16", "192.167.0.0/16", "2000::/10"},
19590 },
19591 }, core.Node{
19592 ObjectMeta: metav1.ObjectMeta{
19593 Name: "pod-cidrs-not-same-length",
19594 },
19595 Spec: core.NodeSpec{
19596 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
19597 },
19598 }, false},
19599 {core.Node{
19600 ObjectMeta: metav1.ObjectMeta{
19601 Name: "pod-cidrs-not-same",
19602 },
19603 Spec: core.NodeSpec{
19604 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
19605 },
19606 }, core.Node{
19607 ObjectMeta: metav1.ObjectMeta{
19608 Name: "pod-cidrs-not-same",
19609 },
19610 Spec: core.NodeSpec{
19611 PodCIDRs: []string{"2000::/10", "192.168.0.0/16"},
19612 },
19613 }, false},
19614 }
19615 for i, test := range tests {
19616 test.oldNode.ObjectMeta.ResourceVersion = "1"
19617 test.node.ObjectMeta.ResourceVersion = "1"
19618 errs := ValidateNodeUpdate(&test.node, &test.oldNode)
19619 if test.valid && len(errs) > 0 {
19620 t.Errorf("%d: Unexpected error: %v", i, errs)
19621 t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
19622 }
19623 if !test.valid && len(errs) == 0 {
19624 t.Errorf("%d: Unexpected non-error", i)
19625 }
19626 }
19627 }
19628
19629 func TestValidateServiceUpdate(t *testing.T) {
19630 requireDualStack := core.IPFamilyPolicyRequireDualStack
19631 preferDualStack := core.IPFamilyPolicyPreferDualStack
19632 singleStack := core.IPFamilyPolicySingleStack
19633 testCases := []struct {
19634 name string
19635 tweakSvc func(oldSvc, newSvc *core.Service)
19636 numErrs int
19637 }{{
19638 name: "no change",
19639 tweakSvc: func(oldSvc, newSvc *core.Service) {
19640
19641 },
19642 numErrs: 0,
19643 }, {
19644 name: "change name",
19645 tweakSvc: func(oldSvc, newSvc *core.Service) {
19646 newSvc.Name += "2"
19647 },
19648 numErrs: 1,
19649 }, {
19650 name: "change namespace",
19651 tweakSvc: func(oldSvc, newSvc *core.Service) {
19652 newSvc.Namespace += "2"
19653 },
19654 numErrs: 1,
19655 }, {
19656 name: "change label valid",
19657 tweakSvc: func(oldSvc, newSvc *core.Service) {
19658 newSvc.Labels["key"] = "other-value"
19659 },
19660 numErrs: 0,
19661 }, {
19662 name: "add label",
19663 tweakSvc: func(oldSvc, newSvc *core.Service) {
19664 newSvc.Labels["key2"] = "value2"
19665 },
19666 numErrs: 0,
19667 }, {
19668 name: "change cluster IP",
19669 tweakSvc: func(oldSvc, newSvc *core.Service) {
19670 oldSvc.Spec.ClusterIP = "1.2.3.4"
19671 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19672
19673 newSvc.Spec.ClusterIP = "8.6.7.5"
19674 newSvc.Spec.ClusterIPs = []string{"8.6.7.5"}
19675 },
19676 numErrs: 1,
19677 }, {
19678 name: "remove cluster IP",
19679 tweakSvc: func(oldSvc, newSvc *core.Service) {
19680 oldSvc.Spec.ClusterIP = "1.2.3.4"
19681 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19682
19683 newSvc.Spec.ClusterIP = ""
19684 newSvc.Spec.ClusterIPs = nil
19685 },
19686 numErrs: 1,
19687 }, {
19688 name: "change affinity",
19689 tweakSvc: func(oldSvc, newSvc *core.Service) {
19690 newSvc.Spec.SessionAffinity = "ClientIP"
19691 newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
19692 ClientIP: &core.ClientIPConfig{
19693 TimeoutSeconds: utilpointer.Int32(90),
19694 },
19695 }
19696 },
19697 numErrs: 0,
19698 }, {
19699 name: "remove affinity",
19700 tweakSvc: func(oldSvc, newSvc *core.Service) {
19701 newSvc.Spec.SessionAffinity = ""
19702 },
19703 numErrs: 1,
19704 }, {
19705 name: "change type",
19706 tweakSvc: func(oldSvc, newSvc *core.Service) {
19707 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19708 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19709 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19710 },
19711 numErrs: 0,
19712 }, {
19713 name: "remove type",
19714 tweakSvc: func(oldSvc, newSvc *core.Service) {
19715 newSvc.Spec.Type = ""
19716 },
19717 numErrs: 1,
19718 }, {
19719 name: "change type -> nodeport",
19720 tweakSvc: func(oldSvc, newSvc *core.Service) {
19721 newSvc.Spec.Type = core.ServiceTypeNodePort
19722 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19723 },
19724 numErrs: 0,
19725 }, {
19726 name: "add loadBalancerSourceRanges",
19727 tweakSvc: func(oldSvc, newSvc *core.Service) {
19728 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
19729 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19730 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19731 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19732 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19733 newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
19734 },
19735 numErrs: 0,
19736 }, {
19737 name: "update loadBalancerSourceRanges",
19738 tweakSvc: func(oldSvc, newSvc *core.Service) {
19739 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
19740 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19741 oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
19742 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19743 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19744 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19745 newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"}
19746 },
19747 numErrs: 0,
19748 }, {
19749 name: "LoadBalancer type cannot have None ClusterIP",
19750 tweakSvc: func(oldSvc, newSvc *core.Service) {
19751 newSvc.Spec.ClusterIP = "None"
19752 newSvc.Spec.ClusterIPs = []string{"None"}
19753 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19754 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19755 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19756 },
19757 numErrs: 1,
19758 }, {
19759 name: "`None` ClusterIP can NOT be changed",
19760 tweakSvc: func(oldSvc, newSvc *core.Service) {
19761 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19762 newSvc.Spec.Type = core.ServiceTypeClusterIP
19763
19764 oldSvc.Spec.ClusterIP = "None"
19765 oldSvc.Spec.ClusterIPs = []string{"None"}
19766
19767 newSvc.Spec.ClusterIP = "1.2.3.4"
19768 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19769 },
19770 numErrs: 1,
19771 }, {
19772 name: "`None` ClusterIP can NOT be removed",
19773 tweakSvc: func(oldSvc, newSvc *core.Service) {
19774 oldSvc.Spec.ClusterIP = "None"
19775 oldSvc.Spec.ClusterIPs = []string{"None"}
19776
19777 newSvc.Spec.ClusterIP = ""
19778 newSvc.Spec.ClusterIPs = nil
19779 },
19780 numErrs: 1,
19781 }, {
19782 name: "ClusterIP can NOT be changed to None",
19783 tweakSvc: func(oldSvc, newSvc *core.Service) {
19784 oldSvc.Spec.ClusterIP = "1.2.3.4"
19785 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19786
19787 newSvc.Spec.ClusterIP = "None"
19788 newSvc.Spec.ClusterIPs = []string{"None"}
19789 },
19790 numErrs: 1,
19791 },
19792
19793 {
19794 name: "Service with ClusterIP type cannot change its set ClusterIP",
19795 tweakSvc: func(oldSvc, newSvc *core.Service) {
19796 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19797 newSvc.Spec.Type = core.ServiceTypeClusterIP
19798
19799 oldSvc.Spec.ClusterIP = "1.2.3.4"
19800 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19801
19802 newSvc.Spec.ClusterIP = "1.2.3.5"
19803 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19804 },
19805 numErrs: 1,
19806 }, {
19807 name: "Service with ClusterIP type can change its empty ClusterIP",
19808 tweakSvc: func(oldSvc, newSvc *core.Service) {
19809 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19810 newSvc.Spec.Type = core.ServiceTypeClusterIP
19811
19812 oldSvc.Spec.ClusterIP = ""
19813 oldSvc.Spec.ClusterIPs = nil
19814 newSvc.Spec.ClusterIP = "1.2.3.5"
19815 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19816 },
19817 numErrs: 0,
19818 }, {
19819 name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort",
19820 tweakSvc: func(oldSvc, newSvc *core.Service) {
19821 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19822 newSvc.Spec.Type = core.ServiceTypeNodePort
19823 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19824
19825 oldSvc.Spec.ClusterIP = "1.2.3.4"
19826 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19827
19828 newSvc.Spec.ClusterIP = "1.2.3.5"
19829 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19830 },
19831 numErrs: 1,
19832 }, {
19833 name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort",
19834 tweakSvc: func(oldSvc, newSvc *core.Service) {
19835 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19836 newSvc.Spec.Type = core.ServiceTypeNodePort
19837 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19838
19839 oldSvc.Spec.ClusterIP = ""
19840 oldSvc.Spec.ClusterIPs = nil
19841
19842 newSvc.Spec.ClusterIP = "1.2.3.5"
19843 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19844 },
19845 numErrs: 0,
19846 }, {
19847 name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer",
19848 tweakSvc: func(oldSvc, newSvc *core.Service) {
19849 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19850 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19851 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19852 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19853
19854 oldSvc.Spec.ClusterIP = "1.2.3.4"
19855 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19856
19857 newSvc.Spec.ClusterIP = "1.2.3.5"
19858 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19859 },
19860 numErrs: 1,
19861 }, {
19862 name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer",
19863 tweakSvc: func(oldSvc, newSvc *core.Service) {
19864 oldSvc.Spec.Type = core.ServiceTypeClusterIP
19865 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19866 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19867 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19868
19869 oldSvc.Spec.ClusterIP = ""
19870 oldSvc.Spec.ClusterIPs = nil
19871
19872 newSvc.Spec.ClusterIP = "1.2.3.5"
19873 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19874 },
19875 numErrs: 0,
19876 }, {
19877 name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from true to false",
19878 tweakSvc: func(oldSvc, newSvc *core.Service) {
19879 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
19880 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19881 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19882 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19883 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
19884 },
19885 numErrs: 0,
19886 }, {
19887 name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from false to true",
19888 tweakSvc: func(oldSvc, newSvc *core.Service) {
19889 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
19890 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
19891 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19892 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19893 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19894 },
19895 numErrs: 0,
19896 }, {
19897 name: "Service with NodePort type cannot change its set ClusterIP",
19898 tweakSvc: func(oldSvc, newSvc *core.Service) {
19899 oldSvc.Spec.Type = core.ServiceTypeNodePort
19900 newSvc.Spec.Type = core.ServiceTypeNodePort
19901 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19902
19903 oldSvc.Spec.ClusterIP = "1.2.3.4"
19904 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19905
19906 newSvc.Spec.ClusterIP = "1.2.3.5"
19907 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19908 },
19909 numErrs: 1,
19910 }, {
19911 name: "Service with NodePort type can change its empty ClusterIP",
19912 tweakSvc: func(oldSvc, newSvc *core.Service) {
19913 oldSvc.Spec.Type = core.ServiceTypeNodePort
19914 newSvc.Spec.Type = core.ServiceTypeNodePort
19915 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19916
19917 oldSvc.Spec.ClusterIP = ""
19918 oldSvc.Spec.ClusterIPs = nil
19919
19920 newSvc.Spec.ClusterIP = "1.2.3.5"
19921 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19922 },
19923 numErrs: 0,
19924 }, {
19925 name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP",
19926 tweakSvc: func(oldSvc, newSvc *core.Service) {
19927 oldSvc.Spec.Type = core.ServiceTypeNodePort
19928 newSvc.Spec.Type = core.ServiceTypeClusterIP
19929
19930 oldSvc.Spec.ClusterIP = "1.2.3.4"
19931 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19932
19933 newSvc.Spec.ClusterIP = "1.2.3.5"
19934 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19935 },
19936 numErrs: 1,
19937 }, {
19938 name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP",
19939 tweakSvc: func(oldSvc, newSvc *core.Service) {
19940 oldSvc.Spec.Type = core.ServiceTypeNodePort
19941 newSvc.Spec.Type = core.ServiceTypeClusterIP
19942
19943 oldSvc.Spec.ClusterIP = ""
19944 oldSvc.Spec.ClusterIPs = nil
19945
19946 newSvc.Spec.ClusterIP = "1.2.3.5"
19947 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19948 },
19949 numErrs: 0,
19950 }, {
19951 name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer",
19952 tweakSvc: func(oldSvc, newSvc *core.Service) {
19953 oldSvc.Spec.Type = core.ServiceTypeNodePort
19954 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19955 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19956 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19957
19958 oldSvc.Spec.ClusterIP = "1.2.3.4"
19959 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19960
19961 newSvc.Spec.ClusterIP = "1.2.3.5"
19962 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19963 },
19964 numErrs: 1,
19965 }, {
19966 name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer",
19967 tweakSvc: func(oldSvc, newSvc *core.Service) {
19968 oldSvc.Spec.Type = core.ServiceTypeNodePort
19969 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19970 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19971 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19972
19973 oldSvc.Spec.ClusterIP = ""
19974 oldSvc.Spec.ClusterIPs = nil
19975
19976 newSvc.Spec.ClusterIP = "1.2.3.5"
19977 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19978 },
19979 numErrs: 0,
19980 }, {
19981 name: "Service with LoadBalancer type cannot change its set ClusterIP",
19982 tweakSvc: func(oldSvc, newSvc *core.Service) {
19983 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
19984 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19985 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
19986 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
19987 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
19988
19989 oldSvc.Spec.ClusterIP = "1.2.3.4"
19990 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
19991
19992 newSvc.Spec.ClusterIP = "1.2.3.5"
19993 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
19994 },
19995 numErrs: 1,
19996 }, {
19997 name: "Service with LoadBalancer type can change its empty ClusterIP",
19998 tweakSvc: func(oldSvc, newSvc *core.Service) {
19999 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20000 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20001 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20002 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20003 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20004
20005 oldSvc.Spec.ClusterIP = ""
20006 oldSvc.Spec.ClusterIPs = nil
20007
20008 newSvc.Spec.ClusterIP = "1.2.3.5"
20009 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20010 },
20011 numErrs: 0,
20012 }, {
20013 name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP",
20014 tweakSvc: func(oldSvc, newSvc *core.Service) {
20015 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20016 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20017 newSvc.Spec.Type = core.ServiceTypeClusterIP
20018
20019 oldSvc.Spec.ClusterIP = "1.2.3.4"
20020 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20021
20022 newSvc.Spec.ClusterIP = "1.2.3.5"
20023 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20024 },
20025 numErrs: 1,
20026 }, {
20027 name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP",
20028 tweakSvc: func(oldSvc, newSvc *core.Service) {
20029 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20030 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20031 newSvc.Spec.Type = core.ServiceTypeClusterIP
20032
20033 oldSvc.Spec.ClusterIP = ""
20034 oldSvc.Spec.ClusterIPs = nil
20035
20036 newSvc.Spec.ClusterIP = "1.2.3.5"
20037 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20038 },
20039 numErrs: 0,
20040 }, {
20041 name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort",
20042 tweakSvc: func(oldSvc, newSvc *core.Service) {
20043 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20044 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20045 newSvc.Spec.Type = core.ServiceTypeNodePort
20046 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20047
20048 oldSvc.Spec.ClusterIP = "1.2.3.4"
20049 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20050
20051 newSvc.Spec.ClusterIP = "1.2.3.5"
20052 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20053 },
20054 numErrs: 1,
20055 }, {
20056 name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort",
20057 tweakSvc: func(oldSvc, newSvc *core.Service) {
20058 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20059 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20060 newSvc.Spec.Type = core.ServiceTypeNodePort
20061 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20062
20063 oldSvc.Spec.ClusterIP = ""
20064 oldSvc.Spec.ClusterIPs = nil
20065
20066 newSvc.Spec.ClusterIP = "1.2.3.5"
20067 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20068 },
20069 numErrs: 0,
20070 }, {
20071 name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP",
20072 tweakSvc: func(oldSvc, newSvc *core.Service) {
20073 oldSvc.Spec.Type = core.ServiceTypeExternalName
20074 newSvc.Spec.Type = core.ServiceTypeClusterIP
20075
20076 oldSvc.Spec.ClusterIP = ""
20077 oldSvc.Spec.ClusterIPs = nil
20078
20079 newSvc.Spec.ClusterIP = "1.2.3.5"
20080 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20081 },
20082 numErrs: 0,
20083 }, {
20084 name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP",
20085 tweakSvc: func(oldSvc, newSvc *core.Service) {
20086 oldSvc.Spec.Type = core.ServiceTypeExternalName
20087 newSvc.Spec.Type = core.ServiceTypeClusterIP
20088
20089 oldSvc.Spec.ClusterIP = "1.2.3.4"
20090 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20091
20092 newSvc.Spec.ClusterIP = "1.2.3.5"
20093 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20094 },
20095 numErrs: 0,
20096 }, {
20097 name: "invalid node port with clusterIP None",
20098 tweakSvc: func(oldSvc, newSvc *core.Service) {
20099 oldSvc.Spec.Type = core.ServiceTypeNodePort
20100 newSvc.Spec.Type = core.ServiceTypeNodePort
20101 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20102
20103 oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
20104 newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
20105
20106 oldSvc.Spec.ClusterIP = ""
20107 oldSvc.Spec.ClusterIPs = nil
20108
20109 newSvc.Spec.ClusterIP = "None"
20110 newSvc.Spec.ClusterIPs = []string{"None"}
20111 },
20112 numErrs: 1,
20113 },
20114
20115 {
20116 name: "convert from ExternalName",
20117 tweakSvc: func(oldSvc, newSvc *core.Service) {
20118 oldSvc.Spec.Type = core.ServiceTypeExternalName
20119 newSvc.Spec.Type = core.ServiceTypeClusterIP
20120 },
20121 numErrs: 0,
20122 }, {
20123 name: "invalid: convert to ExternalName",
20124 tweakSvc: func(oldSvc, newSvc *core.Service) {
20125 singleStack := core.IPFamilyPolicySingleStack
20126
20127 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20128 oldSvc.Spec.ClusterIP = "10.0.0.10"
20129 oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
20130 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20131 oldSvc.Spec.IPFamilyPolicy = &singleStack
20132
20133 newSvc.Spec.Type = core.ServiceTypeExternalName
20134 newSvc.Spec.ExternalName = "foo"
20135
20140 newSvc.Spec.ClusterIP = "10.0.0.10"
20141 newSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
20142 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20143 newSvc.Spec.IPFamilyPolicy = &singleStack
20144
20145 },
20146 numErrs: 3,
20147 }, {
20148 name: "valid: convert to ExternalName",
20149 tweakSvc: func(oldSvc, newSvc *core.Service) {
20150 singleStack := core.IPFamilyPolicySingleStack
20151 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20152 oldSvc.Spec.ClusterIP = "10.0.0.10"
20153 oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
20154 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20155 oldSvc.Spec.IPFamilyPolicy = &singleStack
20156
20157 newSvc.Spec.Type = core.ServiceTypeExternalName
20158 newSvc.Spec.ExternalName = "foo"
20159 },
20160 numErrs: 0,
20161 },
20162
20163 {
20164 name: "same ServiceIPFamily",
20165 tweakSvc: func(oldSvc, newSvc *core.Service) {
20166 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20167 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20168
20169 newSvc.Spec.Type = core.ServiceTypeClusterIP
20170 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20171 },
20172 numErrs: 0,
20173 }, {
20174 name: "same ServiceIPFamily, change IPFamilyPolicy to singleStack",
20175 tweakSvc: func(oldSvc, newSvc *core.Service) {
20176 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20177 oldSvc.Spec.IPFamilyPolicy = nil
20178 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20179
20180 newSvc.Spec.IPFamilyPolicy = &singleStack
20181 newSvc.Spec.Type = core.ServiceTypeClusterIP
20182 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20183 },
20184 numErrs: 0,
20185 }, {
20186 name: "same ServiceIPFamily, change IPFamilyPolicy singleStack => requireDualStack",
20187 tweakSvc: func(oldSvc, newSvc *core.Service) {
20188 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20189 oldSvc.Spec.IPFamilyPolicy = &singleStack
20190 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20191
20192 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20193 newSvc.Spec.Type = core.ServiceTypeClusterIP
20194 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20195 },
20196 numErrs: 0,
20197 },
20198
20199 {
20200 name: "add a new ServiceIPFamily",
20201 tweakSvc: func(oldSvc, newSvc *core.Service) {
20202 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20203 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20204 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20205
20206 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20207 newSvc.Spec.Type = core.ServiceTypeClusterIP
20208 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20209 },
20210 numErrs: 0,
20211 },
20212
20213 {
20214 name: "ExternalName while changing Service IPFamily",
20215 tweakSvc: func(oldSvc, newSvc *core.Service) {
20216 oldSvc.Spec.ExternalName = "somename"
20217 oldSvc.Spec.Type = core.ServiceTypeExternalName
20218
20219 newSvc.Spec.ExternalName = "somename"
20220 newSvc.Spec.Type = core.ServiceTypeExternalName
20221 },
20222 numErrs: 0,
20223 }, {
20224 name: "setting ipfamily from nil to v4",
20225 tweakSvc: func(oldSvc, newSvc *core.Service) {
20226 oldSvc.Spec.IPFamilies = nil
20227
20228 newSvc.Spec.ExternalName = "somename"
20229 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20230 },
20231 numErrs: 0,
20232 }, {
20233 name: "setting ipfamily from nil to v6",
20234 tweakSvc: func(oldSvc, newSvc *core.Service) {
20235 oldSvc.Spec.IPFamilies = nil
20236
20237 newSvc.Spec.ExternalName = "somename"
20238 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
20239 },
20240 numErrs: 0,
20241 }, {
20242 name: "change primary ServiceIPFamily",
20243 tweakSvc: func(oldSvc, newSvc *core.Service) {
20244 oldSvc.Spec.ClusterIP = "1.2.3.4"
20245 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20246 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20247 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20248
20249 newSvc.Spec.Type = core.ServiceTypeClusterIP
20250 newSvc.Spec.ClusterIP = "1.2.3.4"
20251 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20252 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
20253 },
20254 numErrs: 2,
20255 },
20256
20257 {
20258 name: "valid: upgrade to dual stack with requiredDualStack",
20259 tweakSvc: func(oldSvc, newSvc *core.Service) {
20260 oldSvc.Spec.ClusterIP = "1.2.3.4"
20261 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20262 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20263 oldSvc.Spec.IPFamilyPolicy = &singleStack
20264 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20265
20266 newSvc.Spec.Type = core.ServiceTypeClusterIP
20267 newSvc.Spec.ClusterIP = "1.2.3.4"
20268 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20269 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20270 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20271 },
20272 numErrs: 0,
20273 }, {
20274 name: "valid: upgrade to dual stack with preferDualStack",
20275 tweakSvc: func(oldSvc, newSvc *core.Service) {
20276 oldSvc.Spec.ClusterIP = "1.2.3.4"
20277 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20278 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20279 oldSvc.Spec.IPFamilyPolicy = &singleStack
20280 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20281
20282 newSvc.Spec.Type = core.ServiceTypeClusterIP
20283 newSvc.Spec.ClusterIP = "1.2.3.4"
20284 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20285 newSvc.Spec.IPFamilyPolicy = &preferDualStack
20286 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20287 },
20288 numErrs: 0,
20289 },
20290
20291 {
20292 name: "valid: upgrade to dual stack, no specific secondary ip",
20293 tweakSvc: func(oldSvc, newSvc *core.Service) {
20294 oldSvc.Spec.ClusterIP = "1.2.3.4"
20295 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20296 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20297 oldSvc.Spec.IPFamilyPolicy = &singleStack
20298
20299 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20300
20301 newSvc.Spec.Type = core.ServiceTypeClusterIP
20302 newSvc.Spec.ClusterIP = "1.2.3.4"
20303 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20304 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20305 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20306 },
20307 numErrs: 0,
20308 }, {
20309 name: "valid: upgrade to dual stack, with specific secondary ip",
20310 tweakSvc: func(oldSvc, newSvc *core.Service) {
20311 oldSvc.Spec.ClusterIP = "1.2.3.4"
20312 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20313 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20314 oldSvc.Spec.IPFamilyPolicy = &singleStack
20315 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20316
20317 newSvc.Spec.Type = core.ServiceTypeClusterIP
20318 newSvc.Spec.ClusterIP = "1.2.3.4"
20319 newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20320 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20321 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20322 },
20323 numErrs: 0,
20324 }, {
20325 name: "valid: downgrade from dual to single",
20326 tweakSvc: func(oldSvc, newSvc *core.Service) {
20327 oldSvc.Spec.ClusterIP = "1.2.3.4"
20328 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20329 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20330 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20331 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20332
20333 newSvc.Spec.Type = core.ServiceTypeClusterIP
20334 newSvc.Spec.ClusterIP = "1.2.3.4"
20335 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20336 newSvc.Spec.IPFamilyPolicy = &singleStack
20337 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20338 },
20339 numErrs: 0,
20340 }, {
20341 name: "valid: change families for a headless service",
20342 tweakSvc: func(oldSvc, newSvc *core.Service) {
20343 oldSvc.Spec.ClusterIP = "None"
20344 oldSvc.Spec.ClusterIPs = []string{"None"}
20345 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20346 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20347 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20348
20349 newSvc.Spec.Type = core.ServiceTypeClusterIP
20350 newSvc.Spec.ClusterIP = "None"
20351 newSvc.Spec.ClusterIPs = []string{"None"}
20352 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20353 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
20354 },
20355 numErrs: 0,
20356 }, {
20357 name: "valid: upgrade a headless service",
20358 tweakSvc: func(oldSvc, newSvc *core.Service) {
20359 oldSvc.Spec.ClusterIP = "None"
20360 oldSvc.Spec.ClusterIPs = []string{"None"}
20361 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20362 oldSvc.Spec.IPFamilyPolicy = &singleStack
20363 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20364
20365 newSvc.Spec.Type = core.ServiceTypeClusterIP
20366 newSvc.Spec.ClusterIP = "None"
20367 newSvc.Spec.ClusterIPs = []string{"None"}
20368 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20369 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
20370 },
20371 numErrs: 0,
20372 }, {
20373 name: "valid: downgrade a headless service",
20374 tweakSvc: func(oldSvc, newSvc *core.Service) {
20375 oldSvc.Spec.ClusterIP = "None"
20376 oldSvc.Spec.ClusterIPs = []string{"None"}
20377 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20378 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20379 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20380
20381 newSvc.Spec.Type = core.ServiceTypeClusterIP
20382 newSvc.Spec.ClusterIP = "None"
20383 newSvc.Spec.ClusterIPs = []string{"None"}
20384 newSvc.Spec.IPFamilyPolicy = &singleStack
20385 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
20386 },
20387 numErrs: 0,
20388 },
20389
20390 {
20391 name: "invalid flip families",
20392 tweakSvc: func(oldSvc, newSvc *core.Service) {
20393 oldSvc.Spec.ClusterIP = "1.2.3.40"
20394 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20395 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20396 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20397 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20398
20399 newSvc.Spec.Type = core.ServiceTypeClusterIP
20400 newSvc.Spec.ClusterIP = "2001::1"
20401 newSvc.Spec.ClusterIPs = []string{"2001::1", "1.2.3.5"}
20402 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20403 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
20404 },
20405 numErrs: 4,
20406 }, {
20407 name: "invalid change first ip, in dualstack service",
20408 tweakSvc: func(oldSvc, newSvc *core.Service) {
20409 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20410 oldSvc.Spec.ClusterIP = "1.2.3.4"
20411 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20412 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20413 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20414
20415 newSvc.Spec.Type = core.ServiceTypeClusterIP
20416 newSvc.Spec.ClusterIP = "1.2.3.5"
20417 newSvc.Spec.ClusterIPs = []string{"1.2.3.5", "2001::1"}
20418 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20419 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20420 },
20421 numErrs: 1,
20422 }, {
20423 name: "invalid, change second ip in dualstack service",
20424 tweakSvc: func(oldSvc, newSvc *core.Service) {
20425 oldSvc.Spec.ClusterIP = "1.2.3.4"
20426 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20427 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20428 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20429 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20430
20431 newSvc.Spec.Type = core.ServiceTypeClusterIP
20432 newSvc.Spec.ClusterIP = "1.2.3.4"
20433 newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2002::1"}
20434 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20435 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20436 },
20437 numErrs: 1,
20438 }, {
20439 name: "downgrade keeping the families",
20440 tweakSvc: func(oldSvc, newSvc *core.Service) {
20441 oldSvc.Spec.ClusterIP = "1.2.3.4"
20442 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20443 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20444 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20445 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20446
20447 newSvc.Spec.Type = core.ServiceTypeClusterIP
20448 newSvc.Spec.ClusterIP = "1.2.3.4"
20449 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20450 newSvc.Spec.IPFamilyPolicy = &singleStack
20451 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20452 },
20453 numErrs: 0,
20454 }, {
20455 name: "invalid, downgrade without changing to singleStack",
20456 tweakSvc: func(oldSvc, newSvc *core.Service) {
20457 oldSvc.Spec.ClusterIP = "1.2.3.4"
20458 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20459 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20460 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20461 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20462
20463 newSvc.Spec.Type = core.ServiceTypeClusterIP
20464 newSvc.Spec.ClusterIP = "1.2.3.4"
20465 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20466 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20467 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20468 },
20469 numErrs: 2,
20470 }, {
20471 name: "invalid, downgrade and change primary ip",
20472 tweakSvc: func(oldSvc, newSvc *core.Service) {
20473 oldSvc.Spec.ClusterIP = "1.2.3.4"
20474 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
20475 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20476 oldSvc.Spec.IPFamilyPolicy = &requireDualStack
20477 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20478
20479 newSvc.Spec.Type = core.ServiceTypeClusterIP
20480 newSvc.Spec.ClusterIP = "1.2.3.5"
20481 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20482 newSvc.Spec.IPFamilyPolicy = &singleStack
20483 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20484 },
20485 numErrs: 1,
20486 }, {
20487 name: "invalid: upgrade to dual stack and change primary",
20488 tweakSvc: func(oldSvc, newSvc *core.Service) {
20489 oldSvc.Spec.ClusterIP = "1.2.3.4"
20490 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
20491 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20492 oldSvc.Spec.IPFamilyPolicy = &singleStack
20493
20494 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
20495
20496 newSvc.Spec.Type = core.ServiceTypeClusterIP
20497 newSvc.Spec.ClusterIP = "1.2.3.5"
20498 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
20499 newSvc.Spec.IPFamilyPolicy = &requireDualStack
20500 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
20501 },
20502 numErrs: 1,
20503 }, {
20504 name: "update to valid app protocol",
20505 tweakSvc: func(oldSvc, newSvc *core.Service) {
20506 oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
20507 newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
20508 },
20509 numErrs: 0,
20510 }, {
20511 name: "update to invalid app protocol",
20512 tweakSvc: func(oldSvc, newSvc *core.Service) {
20513 oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
20514 newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
20515 },
20516 numErrs: 1,
20517 }, {
20518 name: "Set AllocateLoadBalancerNodePorts when type is not LoadBalancer",
20519 tweakSvc: func(oldSvc, newSvc *core.Service) {
20520 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20521 },
20522 numErrs: 1,
20523 }, {
20524 name: "update LoadBalancer type of service without change LoadBalancerClass",
20525 tweakSvc: func(oldSvc, newSvc *core.Service) {
20526 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20527 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20528 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
20529
20530 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20531 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20532 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20533 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
20534 },
20535 numErrs: 0,
20536 }, {
20537 name: "invalid: change LoadBalancerClass when update service",
20538 tweakSvc: func(oldSvc, newSvc *core.Service) {
20539 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20540 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20541 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
20542
20543 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20544 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20545 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20546 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
20547 },
20548 numErrs: 1,
20549 }, {
20550 name: "invalid: unset LoadBalancerClass when update service",
20551 tweakSvc: func(oldSvc, newSvc *core.Service) {
20552 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20553 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20554 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
20555
20556 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20557 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20558 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20559 newSvc.Spec.LoadBalancerClass = nil
20560 },
20561 numErrs: 1,
20562 }, {
20563 name: "invalid: set LoadBalancerClass when update service",
20564 tweakSvc: func(oldSvc, newSvc *core.Service) {
20565 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20566 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20567 oldSvc.Spec.LoadBalancerClass = nil
20568
20569 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20570 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20571 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20572 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
20573 },
20574 numErrs: 1,
20575 }, {
20576 name: "update to LoadBalancer type of service with valid LoadBalancerClass",
20577 tweakSvc: func(oldSvc, newSvc *core.Service) {
20578 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20579
20580 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20581 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20582 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20583 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20584 },
20585 numErrs: 0,
20586 }, {
20587 name: "update to LoadBalancer type of service without LoadBalancerClass",
20588 tweakSvc: func(oldSvc, newSvc *core.Service) {
20589 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20590
20591 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20592 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20593 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20594 newSvc.Spec.LoadBalancerClass = nil
20595 },
20596 numErrs: 0,
20597 }, {
20598 name: "invalid: set invalid LoadBalancerClass when update service to LoadBalancer",
20599 tweakSvc: func(oldSvc, newSvc *core.Service) {
20600 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20601
20602 newSvc.Spec.Type = core.ServiceTypeLoadBalancer
20603 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20604 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20605 newSvc.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerclass")
20606 },
20607 numErrs: 2,
20608 }, {
20609 name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
20610 tweakSvc: func(oldSvc, newSvc *core.Service) {
20611 oldSvc.Spec.Type = core.ServiceTypeClusterIP
20612
20613 newSvc.Spec.Type = core.ServiceTypeClusterIP
20614 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20615 },
20616 numErrs: 2,
20617 }, {
20618 name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
20619 tweakSvc: func(oldSvc, newSvc *core.Service) {
20620 oldSvc.Spec.Type = core.ServiceTypeExternalName
20621
20622 newSvc.Spec.Type = core.ServiceTypeExternalName
20623 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20624 },
20625 numErrs: 3,
20626 }, {
20627 name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
20628 tweakSvc: func(oldSvc, newSvc *core.Service) {
20629 oldSvc.Spec.Type = core.ServiceTypeNodePort
20630
20631 newSvc.Spec.Type = core.ServiceTypeNodePort
20632 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20633 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20634 },
20635 numErrs: 2,
20636 }, {
20637 name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
20638 tweakSvc: func(oldSvc, newSvc *core.Service) {
20639 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20640 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20641 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20642
20643 newSvc.Spec.Type = core.ServiceTypeClusterIP
20644 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20645 },
20646 numErrs: 2,
20647 }, {
20648 name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
20649 tweakSvc: func(oldSvc, newSvc *core.Service) {
20650 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20651 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20652 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20653
20654 newSvc.Spec.Type = core.ServiceTypeExternalName
20655 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20656 },
20657 numErrs: 3,
20658 }, {
20659 name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
20660 tweakSvc: func(oldSvc, newSvc *core.Service) {
20661 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
20662 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
20663 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20664
20665 newSvc.Spec.Type = core.ServiceTypeNodePort
20666 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
20667 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
20668 },
20669 numErrs: 2,
20670 }, {
20671 name: "update internalTrafficPolicy from Cluster to Local",
20672 tweakSvc: func(oldSvc, newSvc *core.Service) {
20673 cluster := core.ServiceInternalTrafficPolicyCluster
20674 oldSvc.Spec.InternalTrafficPolicy = &cluster
20675
20676 local := core.ServiceInternalTrafficPolicyLocal
20677 newSvc.Spec.InternalTrafficPolicy = &local
20678 },
20679 numErrs: 0,
20680 }, {
20681 name: "update internalTrafficPolicy from Local to Cluster",
20682 tweakSvc: func(oldSvc, newSvc *core.Service) {
20683 local := core.ServiceInternalTrafficPolicyLocal
20684 oldSvc.Spec.InternalTrafficPolicy = &local
20685
20686 cluster := core.ServiceInternalTrafficPolicyCluster
20687 newSvc.Spec.InternalTrafficPolicy = &cluster
20688 },
20689 numErrs: 0,
20690 }, {
20691 name: "topology annotations are mismatched",
20692 tweakSvc: func(oldSvc, newSvc *core.Service) {
20693 newSvc.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
20694 newSvc.Annotations[core.AnnotationTopologyMode] = "different"
20695 },
20696 numErrs: 1,
20697 },
20698 }
20699
20700 for _, tc := range testCases {
20701 t.Run(tc.name, func(t *testing.T) {
20702 oldSvc := makeValidService()
20703 newSvc := makeValidService()
20704 tc.tweakSvc(&oldSvc, &newSvc)
20705 errs := ValidateServiceUpdate(&newSvc, &oldSvc)
20706 if len(errs) != tc.numErrs {
20707 t.Errorf("Expected %d errors, got %d: %v", tc.numErrs, len(errs), errs.ToAggregate())
20708 }
20709 })
20710 }
20711 }
20712
20713 func TestValidateResourceNames(t *testing.T) {
20714 table := []struct {
20715 input core.ResourceName
20716 success bool
20717 expect string
20718 }{
20719 {"memory", true, ""},
20720 {"cpu", true, ""},
20721 {"storage", true, ""},
20722 {"requests.cpu", true, ""},
20723 {"requests.memory", true, ""},
20724 {"requests.storage", true, ""},
20725 {"limits.cpu", true, ""},
20726 {"limits.memory", true, ""},
20727 {"network", false, ""},
20728 {"disk", false, ""},
20729 {"", false, ""},
20730 {".", false, ""},
20731 {"..", false, ""},
20732 {"my.favorite.app.co/12345", true, ""},
20733 {"my.favorite.app.co/_12345", false, ""},
20734 {"my.favorite.app.co/12345_", false, ""},
20735 {"kubernetes.io/..", false, ""},
20736 {core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)), true, ""},
20737 {core.ResourceName("kubernetes.io/" + strings.Repeat("a", 64)), false, ""},
20738 {"kubernetes.io//", false, ""},
20739 {"kubernetes.io", false, ""},
20740 {"kubernetes.io/will/not/work/", false, ""},
20741 }
20742 for k, item := range table {
20743 err := validateResourceName(item.input, field.NewPath("field"))
20744 if len(err) != 0 && item.success {
20745 t.Errorf("expected no failure for input %q", item.input)
20746 } else if len(err) == 0 && !item.success {
20747 t.Errorf("expected failure for input %q", item.input)
20748 for i := range err {
20749 detail := err[i].Detail
20750 if detail != "" && !strings.Contains(detail, item.expect) {
20751 t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail)
20752 }
20753 }
20754 }
20755 }
20756 }
20757
20758 func getResourceList(cpu, memory string) core.ResourceList {
20759 res := core.ResourceList{}
20760 if cpu != "" {
20761 res[core.ResourceCPU] = resource.MustParse(cpu)
20762 }
20763 if memory != "" {
20764 res[core.ResourceMemory] = resource.MustParse(memory)
20765 }
20766 return res
20767 }
20768
20769 func getStorageResourceList(storage string) core.ResourceList {
20770 res := core.ResourceList{}
20771 if storage != "" {
20772 res[core.ResourceStorage] = resource.MustParse(storage)
20773 }
20774 return res
20775 }
20776
20777 func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList {
20778 res := core.ResourceList{}
20779 if ephemeralStorage != "" {
20780 res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
20781 }
20782 return res
20783 }
20784
20785 func TestValidateLimitRangeForLocalStorage(t *testing.T) {
20786 testCases := []struct {
20787 name string
20788 spec core.LimitRangeSpec
20789 }{{
20790 name: "all-fields-valid",
20791 spec: core.LimitRangeSpec{
20792 Limits: []core.LimitRangeItem{{
20793 Type: core.LimitTypePod,
20794 Max: getLocalStorageResourceList("10000Mi"),
20795 Min: getLocalStorageResourceList("100Mi"),
20796 MaxLimitRequestRatio: getLocalStorageResourceList(""),
20797 }, {
20798 Type: core.LimitTypeContainer,
20799 Max: getLocalStorageResourceList("10000Mi"),
20800 Min: getLocalStorageResourceList("100Mi"),
20801 Default: getLocalStorageResourceList("500Mi"),
20802 DefaultRequest: getLocalStorageResourceList("200Mi"),
20803 MaxLimitRequestRatio: getLocalStorageResourceList(""),
20804 }},
20805 },
20806 },
20807 }
20808
20809 for _, testCase := range testCases {
20810 limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec}
20811 if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
20812 t.Errorf("Case %v, unexpected error: %v", testCase.name, errs)
20813 }
20814 }
20815 }
20816
20817 func TestValidateLimitRange(t *testing.T) {
20818 successCases := []struct {
20819 name string
20820 spec core.LimitRangeSpec
20821 }{{
20822 name: "all-fields-valid",
20823 spec: core.LimitRangeSpec{
20824 Limits: []core.LimitRangeItem{{
20825 Type: core.LimitTypePod,
20826 Max: getResourceList("100m", "10000Mi"),
20827 Min: getResourceList("5m", "100Mi"),
20828 MaxLimitRequestRatio: getResourceList("10", ""),
20829 }, {
20830 Type: core.LimitTypeContainer,
20831 Max: getResourceList("100m", "10000Mi"),
20832 Min: getResourceList("5m", "100Mi"),
20833 Default: getResourceList("50m", "500Mi"),
20834 DefaultRequest: getResourceList("10m", "200Mi"),
20835 MaxLimitRequestRatio: getResourceList("10", ""),
20836 }, {
20837 Type: core.LimitTypePersistentVolumeClaim,
20838 Max: getStorageResourceList("10Gi"),
20839 Min: getStorageResourceList("5Gi"),
20840 }},
20841 },
20842 }, {
20843 name: "pvc-min-only",
20844 spec: core.LimitRangeSpec{
20845 Limits: []core.LimitRangeItem{{
20846 Type: core.LimitTypePersistentVolumeClaim,
20847 Min: getStorageResourceList("5Gi"),
20848 }},
20849 },
20850 }, {
20851 name: "pvc-max-only",
20852 spec: core.LimitRangeSpec{
20853 Limits: []core.LimitRangeItem{{
20854 Type: core.LimitTypePersistentVolumeClaim,
20855 Max: getStorageResourceList("10Gi"),
20856 }},
20857 },
20858 }, {
20859 name: "all-fields-valid-big-numbers",
20860 spec: core.LimitRangeSpec{
20861 Limits: []core.LimitRangeItem{{
20862 Type: core.LimitTypeContainer,
20863 Max: getResourceList("100m", "10000T"),
20864 Min: getResourceList("5m", "100Mi"),
20865 Default: getResourceList("50m", "500Mi"),
20866 DefaultRequest: getResourceList("10m", "200Mi"),
20867 MaxLimitRequestRatio: getResourceList("10", ""),
20868 }},
20869 },
20870 }, {
20871 name: "thirdparty-fields-all-valid-standard-container-resources",
20872 spec: core.LimitRangeSpec{
20873 Limits: []core.LimitRangeItem{{
20874 Type: "thirdparty.com/foo",
20875 Max: getResourceList("100m", "10000T"),
20876 Min: getResourceList("5m", "100Mi"),
20877 Default: getResourceList("50m", "500Mi"),
20878 DefaultRequest: getResourceList("10m", "200Mi"),
20879 MaxLimitRequestRatio: getResourceList("10", ""),
20880 }},
20881 },
20882 }, {
20883 name: "thirdparty-fields-all-valid-storage-resources",
20884 spec: core.LimitRangeSpec{
20885 Limits: []core.LimitRangeItem{{
20886 Type: "thirdparty.com/foo",
20887 Max: getStorageResourceList("10000T"),
20888 Min: getStorageResourceList("100Mi"),
20889 Default: getStorageResourceList("500Mi"),
20890 DefaultRequest: getStorageResourceList("200Mi"),
20891 MaxLimitRequestRatio: getStorageResourceList(""),
20892 }},
20893 },
20894 },
20895 }
20896
20897 for _, successCase := range successCases {
20898 limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec}
20899 if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
20900 t.Errorf("Case %v, unexpected error: %v", successCase.name, errs)
20901 }
20902 }
20903
20904 errorCases := map[string]struct {
20905 R core.LimitRange
20906 D string
20907 }{
20908 "zero-length-name": {
20909 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
20910 "name or generateName is required",
20911 },
20912 "zero-length-namespace": {
20913 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}},
20914 "",
20915 },
20916 "invalid-name": {
20917 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
20918 dnsSubdomainLabelErrMsg,
20919 },
20920 "invalid-namespace": {
20921 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}},
20922 dnsLabelErrMsg,
20923 },
20924 "duplicate-limit-type": {
20925 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20926 Limits: []core.LimitRangeItem{{
20927 Type: core.LimitTypePod,
20928 Max: getResourceList("100m", "10000m"),
20929 Min: getResourceList("0m", "100m"),
20930 }, {
20931 Type: core.LimitTypePod,
20932 Min: getResourceList("0m", "100m"),
20933 }},
20934 }},
20935 "",
20936 },
20937 "default-limit-type-pod": {
20938 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20939 Limits: []core.LimitRangeItem{{
20940 Type: core.LimitTypePod,
20941 Max: getResourceList("100m", "10000m"),
20942 Min: getResourceList("0m", "100m"),
20943 Default: getResourceList("10m", "100m"),
20944 }},
20945 }},
20946 "may not be specified when `type` is 'Pod'",
20947 },
20948 "default-request-limit-type-pod": {
20949 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20950 Limits: []core.LimitRangeItem{{
20951 Type: core.LimitTypePod,
20952 Max: getResourceList("100m", "10000m"),
20953 Min: getResourceList("0m", "100m"),
20954 DefaultRequest: getResourceList("10m", "100m"),
20955 }},
20956 }},
20957 "may not be specified when `type` is 'Pod'",
20958 },
20959 "min value 100m is greater than max value 10m": {
20960 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20961 Limits: []core.LimitRangeItem{{
20962 Type: core.LimitTypePod,
20963 Max: getResourceList("10m", ""),
20964 Min: getResourceList("100m", ""),
20965 }},
20966 }},
20967 "min value 100m is greater than max value 10m",
20968 },
20969 "invalid spec default outside range": {
20970 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20971 Limits: []core.LimitRangeItem{{
20972 Type: core.LimitTypeContainer,
20973 Max: getResourceList("1", ""),
20974 Min: getResourceList("100m", ""),
20975 Default: getResourceList("2000m", ""),
20976 }},
20977 }},
20978 "default value 2 is greater than max value 1",
20979 },
20980 "invalid spec default request outside range": {
20981 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20982 Limits: []core.LimitRangeItem{{
20983 Type: core.LimitTypeContainer,
20984 Max: getResourceList("1", ""),
20985 Min: getResourceList("100m", ""),
20986 DefaultRequest: getResourceList("2000m", ""),
20987 }},
20988 }},
20989 "default request value 2 is greater than max value 1",
20990 },
20991 "invalid spec default request more than default": {
20992 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
20993 Limits: []core.LimitRangeItem{{
20994 Type: core.LimitTypeContainer,
20995 Max: getResourceList("2", ""),
20996 Min: getResourceList("100m", ""),
20997 Default: getResourceList("500m", ""),
20998 DefaultRequest: getResourceList("800m", ""),
20999 }},
21000 }},
21001 "default request value 800m is greater than default limit value 500m",
21002 },
21003 "invalid spec maxLimitRequestRatio less than 1": {
21004 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
21005 Limits: []core.LimitRangeItem{{
21006 Type: core.LimitTypePod,
21007 MaxLimitRequestRatio: getResourceList("800m", ""),
21008 }},
21009 }},
21010 "ratio 800m is less than 1",
21011 },
21012 "invalid spec maxLimitRequestRatio greater than max/min": {
21013 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
21014 Limits: []core.LimitRangeItem{{
21015 Type: core.LimitTypeContainer,
21016 Max: getResourceList("", "2Gi"),
21017 Min: getResourceList("", "512Mi"),
21018 MaxLimitRequestRatio: getResourceList("", "10"),
21019 }},
21020 }},
21021 "ratio 10 is greater than max/min = 4.000000",
21022 },
21023 "invalid non standard limit type": {
21024 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
21025 Limits: []core.LimitRangeItem{{
21026 Type: "foo",
21027 Max: getStorageResourceList("10000T"),
21028 Min: getStorageResourceList("100Mi"),
21029 Default: getStorageResourceList("500Mi"),
21030 DefaultRequest: getStorageResourceList("200Mi"),
21031 MaxLimitRequestRatio: getStorageResourceList(""),
21032 }},
21033 }},
21034 "must be a standard limit type or fully qualified",
21035 },
21036 "min and max values missing, one required": {
21037 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
21038 Limits: []core.LimitRangeItem{{
21039 Type: core.LimitTypePersistentVolumeClaim,
21040 }},
21041 }},
21042 "either minimum or maximum storage value is required, but neither was provided",
21043 },
21044 "invalid min greater than max": {
21045 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
21046 Limits: []core.LimitRangeItem{{
21047 Type: core.LimitTypePersistentVolumeClaim,
21048 Min: getStorageResourceList("10Gi"),
21049 Max: getStorageResourceList("1Gi"),
21050 }},
21051 }},
21052 "min value 10Gi is greater than max value 1Gi",
21053 },
21054 }
21055
21056 for k, v := range errorCases {
21057 errs := ValidateLimitRange(&v.R)
21058 if len(errs) == 0 {
21059 t.Errorf("expected failure for %s", k)
21060 }
21061 for i := range errs {
21062 detail := errs[i].Detail
21063 if !strings.Contains(detail, v.D) {
21064 t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail)
21065 }
21066 }
21067 }
21068
21069 }
21070
21071 func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
21072 validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
21073 AccessModes: []core.PersistentVolumeAccessMode{
21074 core.ReadWriteOnce,
21075 core.ReadOnlyMany,
21076 },
21077 Resources: core.VolumeResourceRequirements{
21078 Requests: core.ResourceList{
21079 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21080 },
21081 },
21082 })
21083 validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21084 AccessModes: []core.PersistentVolumeAccessMode{
21085 core.ReadWriteOnce,
21086 core.ReadOnlyMany,
21087 },
21088 Resources: core.VolumeResourceRequirements{
21089 Requests: core.ResourceList{
21090 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21091 },
21092 },
21093 }, core.PersistentVolumeClaimStatus{
21094 Phase: core.ClaimPending,
21095 Conditions: []core.PersistentVolumeClaimCondition{
21096 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
21097 },
21098 })
21099 validAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21100 AccessModes: []core.PersistentVolumeAccessMode{
21101 core.ReadWriteOnce,
21102 core.ReadOnlyMany,
21103 },
21104 Resources: core.VolumeResourceRequirements{
21105 Requests: core.ResourceList{
21106 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21107 },
21108 },
21109 }, core.PersistentVolumeClaimStatus{
21110 Phase: core.ClaimPending,
21111 Conditions: []core.PersistentVolumeClaimCondition{
21112 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
21113 },
21114 AllocatedResources: core.ResourceList{
21115 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21116 },
21117 })
21118
21119 invalidAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21120 AccessModes: []core.PersistentVolumeAccessMode{
21121 core.ReadWriteOnce,
21122 core.ReadOnlyMany,
21123 },
21124 Resources: core.VolumeResourceRequirements{
21125 Requests: core.ResourceList{
21126 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21127 },
21128 },
21129 }, core.PersistentVolumeClaimStatus{
21130 Phase: core.ClaimPending,
21131 Conditions: []core.PersistentVolumeClaimCondition{
21132 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
21133 },
21134 AllocatedResources: core.ResourceList{
21135 core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
21136 },
21137 })
21138
21139 noStoraegeClaimStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21140 AccessModes: []core.PersistentVolumeAccessMode{
21141 core.ReadWriteOnce,
21142 },
21143 Resources: core.VolumeResourceRequirements{
21144 Requests: core.ResourceList{
21145 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21146 },
21147 },
21148 }, core.PersistentVolumeClaimStatus{
21149 Phase: core.ClaimPending,
21150 AllocatedResources: core.ResourceList{
21151 core.ResourceName(core.ResourceCPU): resource.MustParse("10G"),
21152 },
21153 })
21154 progressResizeStatus := core.PersistentVolumeClaimControllerResizeInProgress
21155
21156 invalidResizeStatus := core.ClaimResourceStatus("foo")
21157 validResizeKeyCustom := core.ResourceName("example.com/foo")
21158 invalidNativeResizeKey := core.ResourceName("kubernetes.io/foo")
21159
21160 validResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21161 AccessModes: []core.PersistentVolumeAccessMode{
21162 core.ReadWriteOnce,
21163 },
21164 }, core.PersistentVolumeClaimStatus{
21165 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21166 core.ResourceStorage: progressResizeStatus,
21167 },
21168 })
21169
21170 validResizeStatusControllerResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21171 AccessModes: []core.PersistentVolumeAccessMode{
21172 core.ReadWriteOnce,
21173 },
21174 }, core.PersistentVolumeClaimStatus{
21175 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21176 core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
21177 },
21178 })
21179
21180 validNodeResizePending := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21181 AccessModes: []core.PersistentVolumeAccessMode{
21182 core.ReadWriteOnce,
21183 },
21184 }, core.PersistentVolumeClaimStatus{
21185 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21186 core.ResourceStorage: core.PersistentVolumeClaimNodeResizePending,
21187 },
21188 })
21189
21190 validNodeResizeInProgress := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21191 AccessModes: []core.PersistentVolumeAccessMode{
21192 core.ReadWriteOnce,
21193 },
21194 }, core.PersistentVolumeClaimStatus{
21195 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21196 core.ResourceStorage: core.PersistentVolumeClaimNodeResizeInProgress,
21197 },
21198 })
21199
21200 validNodeResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21201 AccessModes: []core.PersistentVolumeAccessMode{
21202 core.ReadWriteOnce,
21203 },
21204 }, core.PersistentVolumeClaimStatus{
21205 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21206 core.ResourceStorage: core.PersistentVolumeClaimNodeResizeFailed,
21207 },
21208 })
21209
21210 invalidResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21211 AccessModes: []core.PersistentVolumeAccessMode{
21212 core.ReadWriteOnce,
21213 },
21214 }, core.PersistentVolumeClaimStatus{
21215 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21216 core.ResourceStorage: invalidResizeStatus,
21217 },
21218 })
21219
21220 invalidNativeResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21221 AccessModes: []core.PersistentVolumeAccessMode{
21222 core.ReadWriteOnce,
21223 },
21224 }, core.PersistentVolumeClaimStatus{
21225 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21226 invalidNativeResizeKey: core.PersistentVolumeClaimNodeResizePending,
21227 },
21228 })
21229
21230 validExternalResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21231 AccessModes: []core.PersistentVolumeAccessMode{
21232 core.ReadWriteOnce,
21233 },
21234 }, core.PersistentVolumeClaimStatus{
21235 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21236 validResizeKeyCustom: core.PersistentVolumeClaimNodeResizePending,
21237 },
21238 })
21239
21240 multipleResourceStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21241 AccessModes: []core.PersistentVolumeAccessMode{
21242 core.ReadWriteOnce,
21243 },
21244 }, core.PersistentVolumeClaimStatus{
21245 AllocatedResources: core.ResourceList{
21246 core.ResourceStorage: resource.MustParse("5Gi"),
21247 validResizeKeyCustom: resource.MustParse("10Gi"),
21248 },
21249 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
21250 core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
21251 validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress,
21252 },
21253 })
21254
21255 invalidNativeResourceAllocatedKey := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21256 AccessModes: []core.PersistentVolumeAccessMode{
21257 core.ReadWriteOnce,
21258 core.ReadOnlyMany,
21259 },
21260 Resources: core.VolumeResourceRequirements{
21261 Requests: core.ResourceList{
21262 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21263 },
21264 },
21265 }, core.PersistentVolumeClaimStatus{
21266 Phase: core.ClaimPending,
21267 Conditions: []core.PersistentVolumeClaimCondition{
21268 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
21269 },
21270 AllocatedResources: core.ResourceList{
21271 invalidNativeResizeKey: resource.MustParse("14G"),
21272 },
21273 })
21274
21275 validExternalAllocatedResource := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
21276 AccessModes: []core.PersistentVolumeAccessMode{
21277 core.ReadWriteOnce,
21278 core.ReadOnlyMany,
21279 },
21280 Resources: core.VolumeResourceRequirements{
21281 Requests: core.ResourceList{
21282 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
21283 },
21284 },
21285 }, core.PersistentVolumeClaimStatus{
21286 Phase: core.ClaimPending,
21287 Conditions: []core.PersistentVolumeClaimCondition{
21288 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
21289 },
21290 AllocatedResources: core.ResourceList{
21291 validResizeKeyCustom: resource.MustParse("14G"),
21292 },
21293 })
21294
21295 scenarios := map[string]struct {
21296 isExpectedFailure bool
21297 oldClaim *core.PersistentVolumeClaim
21298 newClaim *core.PersistentVolumeClaim
21299 enableRecoverFromExpansion bool
21300 }{
21301 "condition-update-with-enabled-feature-gate": {
21302 isExpectedFailure: false,
21303 oldClaim: validClaim,
21304 newClaim: validConditionUpdate,
21305 },
21306 "status-update-with-valid-allocatedResources-feature-enabled": {
21307 isExpectedFailure: false,
21308 oldClaim: validClaim,
21309 newClaim: validAllocatedResources,
21310 enableRecoverFromExpansion: true,
21311 },
21312 "status-update-with-invalid-allocatedResources-native-key-feature-enabled": {
21313 isExpectedFailure: true,
21314 oldClaim: validClaim,
21315 newClaim: invalidNativeResourceAllocatedKey,
21316 enableRecoverFromExpansion: true,
21317 },
21318 "status-update-with-valid-allocatedResources-external-key-feature-enabled": {
21319 isExpectedFailure: false,
21320 oldClaim: validClaim,
21321 newClaim: validExternalAllocatedResource,
21322 enableRecoverFromExpansion: true,
21323 },
21324
21325 "status-update-with-invalid-allocatedResources-feature-enabled": {
21326 isExpectedFailure: true,
21327 oldClaim: validClaim,
21328 newClaim: invalidAllocatedResources,
21329 enableRecoverFromExpansion: true,
21330 },
21331 "status-update-with-no-storage-update": {
21332 isExpectedFailure: true,
21333 oldClaim: validClaim,
21334 newClaim: noStoraegeClaimStatus,
21335 enableRecoverFromExpansion: true,
21336 },
21337 "staus-update-with-controller-resize-failed": {
21338 isExpectedFailure: false,
21339 oldClaim: validClaim,
21340 newClaim: validResizeStatusControllerResizeFailed,
21341 enableRecoverFromExpansion: true,
21342 },
21343 "staus-update-with-node-resize-pending": {
21344 isExpectedFailure: false,
21345 oldClaim: validClaim,
21346 newClaim: validNodeResizePending,
21347 enableRecoverFromExpansion: true,
21348 },
21349 "staus-update-with-node-resize-inprogress": {
21350 isExpectedFailure: false,
21351 oldClaim: validClaim,
21352 newClaim: validNodeResizeInProgress,
21353 enableRecoverFromExpansion: true,
21354 },
21355 "staus-update-with-node-resize-failed": {
21356 isExpectedFailure: false,
21357 oldClaim: validClaim,
21358 newClaim: validNodeResizeFailed,
21359 enableRecoverFromExpansion: true,
21360 },
21361 "staus-update-with-invalid-native-resource-status-key": {
21362 isExpectedFailure: true,
21363 oldClaim: validClaim,
21364 newClaim: invalidNativeResizeStatusPVC,
21365 enableRecoverFromExpansion: true,
21366 },
21367 "staus-update-with-valid-external-resource-status-key": {
21368 isExpectedFailure: false,
21369 oldClaim: validClaim,
21370 newClaim: validExternalResizeStatusPVC,
21371 enableRecoverFromExpansion: true,
21372 },
21373 "status-update-with-multiple-resources-key": {
21374 isExpectedFailure: false,
21375 oldClaim: validClaim,
21376 newClaim: multipleResourceStatusPVC,
21377 enableRecoverFromExpansion: true,
21378 },
21379 "status-update-with-valid-pvc-resize-status": {
21380 isExpectedFailure: false,
21381 oldClaim: validClaim,
21382 newClaim: validResizeStatusPVC,
21383 enableRecoverFromExpansion: true,
21384 },
21385 "status-update-with-invalid-pvc-resize-status": {
21386 isExpectedFailure: true,
21387 oldClaim: validClaim,
21388 newClaim: invalidResizeStatusPVC,
21389 enableRecoverFromExpansion: true,
21390 },
21391 "status-update-with-old-pvc-valid-resourcestatus-newpvc-invalid-recovery-disabled": {
21392 isExpectedFailure: true,
21393 oldClaim: validResizeStatusPVC,
21394 newClaim: invalidResizeStatusPVC,
21395 enableRecoverFromExpansion: false,
21396 },
21397 "status-update-with-old-pvc-valid-allocatedResource-newpvc-invalid-recovery-disabled": {
21398 isExpectedFailure: true,
21399 oldClaim: validExternalAllocatedResource,
21400 newClaim: invalidNativeResourceAllocatedKey,
21401 enableRecoverFromExpansion: false,
21402 },
21403 }
21404 for name, scenario := range scenarios {
21405 t.Run(name, func(t *testing.T) {
21406 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
21407
21408 validateOpts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
21409
21410
21411 scenario.oldClaim.ResourceVersion = "1"
21412 scenario.newClaim.ResourceVersion = "1"
21413 errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim, validateOpts)
21414 if len(errs) == 0 && scenario.isExpectedFailure {
21415 t.Errorf("Unexpected success for scenario: %s", name)
21416 }
21417 if len(errs) > 0 && !scenario.isExpectedFailure {
21418 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
21419 }
21420 })
21421 }
21422 }
21423
21424 func TestValidateResourceQuota(t *testing.T) {
21425 spec := core.ResourceQuotaSpec{
21426 Hard: core.ResourceList{
21427 core.ResourceCPU: resource.MustParse("100"),
21428 core.ResourceMemory: resource.MustParse("10000"),
21429 core.ResourceRequestsCPU: resource.MustParse("100"),
21430 core.ResourceRequestsMemory: resource.MustParse("10000"),
21431 core.ResourceLimitsCPU: resource.MustParse("100"),
21432 core.ResourceLimitsMemory: resource.MustParse("10000"),
21433 core.ResourcePods: resource.MustParse("10"),
21434 core.ResourceServices: resource.MustParse("0"),
21435 core.ResourceReplicationControllers: resource.MustParse("10"),
21436 core.ResourceQuotas: resource.MustParse("10"),
21437 core.ResourceConfigMaps: resource.MustParse("10"),
21438 core.ResourceSecrets: resource.MustParse("10"),
21439 },
21440 }
21441
21442 terminatingSpec := core.ResourceQuotaSpec{
21443 Hard: core.ResourceList{
21444 core.ResourceCPU: resource.MustParse("100"),
21445 core.ResourceLimitsCPU: resource.MustParse("200"),
21446 },
21447 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating},
21448 }
21449
21450 nonTerminatingSpec := core.ResourceQuotaSpec{
21451 Hard: core.ResourceList{
21452 core.ResourceCPU: resource.MustParse("100"),
21453 },
21454 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating},
21455 }
21456
21457 bestEffortSpec := core.ResourceQuotaSpec{
21458 Hard: core.ResourceList{
21459 core.ResourcePods: resource.MustParse("100"),
21460 },
21461 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort},
21462 }
21463
21464 nonBestEffortSpec := core.ResourceQuotaSpec{
21465 Hard: core.ResourceList{
21466 core.ResourceCPU: resource.MustParse("100"),
21467 },
21468 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort},
21469 }
21470
21471 crossNamespaceAffinitySpec := core.ResourceQuotaSpec{
21472 Hard: core.ResourceList{
21473 core.ResourceCPU: resource.MustParse("100"),
21474 core.ResourceLimitsCPU: resource.MustParse("200"),
21475 },
21476 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeCrossNamespacePodAffinity},
21477 }
21478
21479 scopeSelectorSpec := core.ResourceQuotaSpec{
21480 ScopeSelector: &core.ScopeSelector{
21481 MatchExpressions: []core.ScopedResourceSelectorRequirement{{
21482 ScopeName: core.ResourceQuotaScopePriorityClass,
21483 Operator: core.ScopeSelectorOpIn,
21484 Values: []string{"cluster-services"},
21485 }},
21486 },
21487 }
21488
21489
21490 invalidQuotaResourceSpec := core.ResourceQuotaSpec{
21491 Hard: core.ResourceList{
21492 core.ResourceStorage: resource.MustParse("10"),
21493 },
21494 }
21495
21496 negativeSpec := core.ResourceQuotaSpec{
21497 Hard: core.ResourceList{
21498 core.ResourceCPU: resource.MustParse("-100"),
21499 core.ResourceMemory: resource.MustParse("-10000"),
21500 core.ResourcePods: resource.MustParse("-10"),
21501 core.ResourceServices: resource.MustParse("-10"),
21502 core.ResourceReplicationControllers: resource.MustParse("-10"),
21503 core.ResourceQuotas: resource.MustParse("-10"),
21504 core.ResourceConfigMaps: resource.MustParse("-10"),
21505 core.ResourceSecrets: resource.MustParse("-10"),
21506 },
21507 }
21508
21509 fractionalComputeSpec := core.ResourceQuotaSpec{
21510 Hard: core.ResourceList{
21511 core.ResourceCPU: resource.MustParse("100m"),
21512 },
21513 }
21514
21515 fractionalPodSpec := core.ResourceQuotaSpec{
21516 Hard: core.ResourceList{
21517 core.ResourcePods: resource.MustParse(".1"),
21518 core.ResourceServices: resource.MustParse(".5"),
21519 core.ResourceReplicationControllers: resource.MustParse("1.25"),
21520 core.ResourceQuotas: resource.MustParse("2.5"),
21521 },
21522 }
21523
21524 invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{
21525 Hard: core.ResourceList{
21526 core.ResourceCPU: resource.MustParse("100"),
21527 },
21528 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating},
21529 }
21530
21531 invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{
21532 Hard: core.ResourceList{
21533 core.ResourcePods: resource.MustParse("100"),
21534 },
21535 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort},
21536 }
21537
21538 invalidCrossNamespaceAffinitySpec := core.ResourceQuotaSpec{
21539 ScopeSelector: &core.ScopeSelector{
21540 MatchExpressions: []core.ScopedResourceSelectorRequirement{{
21541 ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity,
21542 Operator: core.ScopeSelectorOpIn,
21543 Values: []string{"cluster-services"},
21544 }},
21545 },
21546 }
21547
21548 invalidScopeNameSpec := core.ResourceQuotaSpec{
21549 Hard: core.ResourceList{
21550 core.ResourceCPU: resource.MustParse("100"),
21551 },
21552 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")},
21553 }
21554
21555 testCases := map[string]struct {
21556 rq core.ResourceQuota
21557 errDetail string
21558 errField string
21559 }{
21560 "no-scope": {
21561 rq: core.ResourceQuota{
21562 ObjectMeta: metav1.ObjectMeta{
21563 Name: "abc",
21564 Namespace: "foo",
21565 },
21566 Spec: spec,
21567 },
21568 },
21569 "fractional-compute-spec": {
21570 rq: core.ResourceQuota{
21571 ObjectMeta: metav1.ObjectMeta{
21572 Name: "abc",
21573 Namespace: "foo",
21574 },
21575 Spec: fractionalComputeSpec,
21576 },
21577 },
21578 "terminating-spec": {
21579 rq: core.ResourceQuota{
21580 ObjectMeta: metav1.ObjectMeta{
21581 Name: "abc",
21582 Namespace: "foo",
21583 },
21584 Spec: terminatingSpec,
21585 },
21586 },
21587 "non-terminating-spec": {
21588 rq: core.ResourceQuota{
21589 ObjectMeta: metav1.ObjectMeta{
21590 Name: "abc",
21591 Namespace: "foo",
21592 },
21593 Spec: nonTerminatingSpec,
21594 },
21595 },
21596 "best-effort-spec": {
21597 rq: core.ResourceQuota{
21598 ObjectMeta: metav1.ObjectMeta{
21599 Name: "abc",
21600 Namespace: "foo",
21601 },
21602 Spec: bestEffortSpec,
21603 },
21604 },
21605 "cross-namespace-affinity-spec": {
21606 rq: core.ResourceQuota{
21607 ObjectMeta: metav1.ObjectMeta{
21608 Name: "abc",
21609 Namespace: "foo",
21610 },
21611 Spec: crossNamespaceAffinitySpec,
21612 },
21613 },
21614 "scope-selector-spec": {
21615 rq: core.ResourceQuota{
21616 ObjectMeta: metav1.ObjectMeta{
21617 Name: "abc",
21618 Namespace: "foo",
21619 },
21620 Spec: scopeSelectorSpec,
21621 },
21622 },
21623 "non-best-effort-spec": {
21624 rq: core.ResourceQuota{
21625 ObjectMeta: metav1.ObjectMeta{
21626 Name: "abc",
21627 Namespace: "foo",
21628 },
21629 Spec: nonBestEffortSpec,
21630 },
21631 },
21632 "zero-length Name": {
21633 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
21634 errDetail: "name or generateName is required",
21635 },
21636 "zero-length Namespace": {
21637 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
21638 errField: "metadata.namespace",
21639 },
21640 "invalid Name": {
21641 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
21642 errDetail: dnsSubdomainLabelErrMsg,
21643 },
21644 "invalid Namespace": {
21645 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
21646 errDetail: dnsLabelErrMsg,
21647 },
21648 "negative-limits": {
21649 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec},
21650 errDetail: isNegativeErrorMsg,
21651 },
21652 "fractional-api-resource": {
21653 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec},
21654 errDetail: isNotIntegerErrorMsg,
21655 },
21656 "invalid-quota-resource": {
21657 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec},
21658 errDetail: isInvalidQuotaResource,
21659 },
21660 "invalid-quota-terminating-pair": {
21661 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec},
21662 errDetail: "conflicting scopes",
21663 },
21664 "invalid-quota-besteffort-pair": {
21665 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec},
21666 errDetail: "conflicting scopes",
21667 },
21668 "invalid-quota-scope-name": {
21669 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
21670 errDetail: "unsupported scope",
21671 },
21672 "invalid-cross-namespace-affinity": {
21673 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec},
21674 errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity",
21675 },
21676 }
21677 for name, tc := range testCases {
21678 t.Run(name, func(t *testing.T) {
21679 errs := ValidateResourceQuota(&tc.rq)
21680 if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 {
21681 t.Errorf("expected success: %v", errs)
21682 } else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 {
21683 t.Errorf("expected failure")
21684 } else {
21685 for i := range errs {
21686 if !strings.Contains(errs[i].Detail, tc.errDetail) {
21687 t.Errorf("expected error detail either empty or %s, got %s", tc.errDetail, errs[i].Detail)
21688 }
21689 }
21690 }
21691 })
21692 }
21693 }
21694
21695 func TestValidateNamespace(t *testing.T) {
21696 validLabels := map[string]string{"a": "b"}
21697 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
21698 successCases := []core.Namespace{{
21699 ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels},
21700 }, {
21701 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
21702 Spec: core.NamespaceSpec{
21703 Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"},
21704 },
21705 },
21706 }
21707 for _, successCase := range successCases {
21708 if errs := ValidateNamespace(&successCase); len(errs) != 0 {
21709 t.Errorf("expected success: %v", errs)
21710 }
21711 }
21712 errorCases := map[string]struct {
21713 R core.Namespace
21714 D string
21715 }{
21716 "zero-length name": {
21717 core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}},
21718 "",
21719 },
21720 "defined-namespace": {
21721 core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
21722 "",
21723 },
21724 "invalid-labels": {
21725 core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}},
21726 "",
21727 },
21728 }
21729 for k, v := range errorCases {
21730 errs := ValidateNamespace(&v.R)
21731 if len(errs) == 0 {
21732 t.Errorf("expected failure for %s", k)
21733 }
21734 }
21735 }
21736
21737 func TestValidateNamespaceFinalizeUpdate(t *testing.T) {
21738 tests := []struct {
21739 oldNamespace core.Namespace
21740 namespace core.Namespace
21741 valid bool
21742 }{
21743 {core.Namespace{}, core.Namespace{}, true},
21744 {core.Namespace{
21745 ObjectMeta: metav1.ObjectMeta{
21746 Name: "foo"}},
21747 core.Namespace{
21748 ObjectMeta: metav1.ObjectMeta{
21749 Name: "foo"},
21750 Spec: core.NamespaceSpec{
21751 Finalizers: []core.FinalizerName{"Foo"},
21752 },
21753 }, false},
21754 {core.Namespace{
21755 ObjectMeta: metav1.ObjectMeta{
21756 Name: "foo"},
21757 Spec: core.NamespaceSpec{
21758 Finalizers: []core.FinalizerName{"foo.com/bar"},
21759 },
21760 },
21761 core.Namespace{
21762 ObjectMeta: metav1.ObjectMeta{
21763 Name: "foo"},
21764 Spec: core.NamespaceSpec{
21765 Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"},
21766 },
21767 }, true},
21768 {core.Namespace{
21769 ObjectMeta: metav1.ObjectMeta{
21770 Name: "fooemptyfinalizer"},
21771 Spec: core.NamespaceSpec{
21772 Finalizers: []core.FinalizerName{"foo.com/bar"},
21773 },
21774 },
21775 core.Namespace{
21776 ObjectMeta: metav1.ObjectMeta{
21777 Name: "fooemptyfinalizer"},
21778 Spec: core.NamespaceSpec{
21779 Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"},
21780 },
21781 }, false},
21782 }
21783 for i, test := range tests {
21784 test.namespace.ObjectMeta.ResourceVersion = "1"
21785 test.oldNamespace.ObjectMeta.ResourceVersion = "1"
21786 errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace)
21787 if test.valid && len(errs) > 0 {
21788 t.Errorf("%d: Unexpected error: %v", i, errs)
21789 t.Logf("%#v vs %#v", test.oldNamespace, test.namespace)
21790 }
21791 if !test.valid && len(errs) == 0 {
21792 t.Errorf("%d: Unexpected non-error", i)
21793 }
21794 }
21795 }
21796
21797 func TestValidateNamespaceStatusUpdate(t *testing.T) {
21798 now := metav1.Now()
21799
21800 tests := []struct {
21801 oldNamespace core.Namespace
21802 namespace core.Namespace
21803 valid bool
21804 }{
21805 {core.Namespace{}, core.Namespace{
21806 Status: core.NamespaceStatus{
21807 Phase: core.NamespaceActive,
21808 },
21809 }, true},
21810
21811 {core.Namespace{
21812 ObjectMeta: metav1.ObjectMeta{
21813 Name: "foo"}},
21814 core.Namespace{
21815 ObjectMeta: metav1.ObjectMeta{
21816 Name: "foo",
21817 DeletionTimestamp: &now},
21818 Status: core.NamespaceStatus{
21819 Phase: core.NamespaceTerminating,
21820 },
21821 }, false},
21822
21823 {core.Namespace{
21824 ObjectMeta: metav1.ObjectMeta{
21825 Name: "foo",
21826 DeletionTimestamp: &now}},
21827 core.Namespace{
21828 ObjectMeta: metav1.ObjectMeta{
21829 Name: "foo",
21830 DeletionTimestamp: &now},
21831 Status: core.NamespaceStatus{
21832 Phase: core.NamespaceTerminating,
21833 },
21834 }, true},
21835 {core.Namespace{
21836 ObjectMeta: metav1.ObjectMeta{
21837 Name: "foo"}},
21838 core.Namespace{
21839 ObjectMeta: metav1.ObjectMeta{
21840 Name: "foo"},
21841 Status: core.NamespaceStatus{
21842 Phase: core.NamespaceTerminating,
21843 },
21844 }, false},
21845 {core.Namespace{
21846 ObjectMeta: metav1.ObjectMeta{
21847 Name: "foo"}},
21848 core.Namespace{
21849 ObjectMeta: metav1.ObjectMeta{
21850 Name: "bar"},
21851 Status: core.NamespaceStatus{
21852 Phase: core.NamespaceTerminating,
21853 },
21854 }, false},
21855 }
21856 for i, test := range tests {
21857 test.namespace.ObjectMeta.ResourceVersion = "1"
21858 test.oldNamespace.ObjectMeta.ResourceVersion = "1"
21859 errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace)
21860 if test.valid && len(errs) > 0 {
21861 t.Errorf("%d: Unexpected error: %v", i, errs)
21862 t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
21863 }
21864 if !test.valid && len(errs) == 0 {
21865 t.Errorf("%d: Unexpected non-error", i)
21866 }
21867 }
21868 }
21869
21870 func TestValidateNamespaceUpdate(t *testing.T) {
21871 tests := []struct {
21872 oldNamespace core.Namespace
21873 namespace core.Namespace
21874 valid bool
21875 }{
21876 {core.Namespace{}, core.Namespace{}, true},
21877 {core.Namespace{
21878 ObjectMeta: metav1.ObjectMeta{
21879 Name: "foo1"}},
21880 core.Namespace{
21881 ObjectMeta: metav1.ObjectMeta{
21882 Name: "bar1"},
21883 }, false},
21884 {core.Namespace{
21885 ObjectMeta: metav1.ObjectMeta{
21886 Name: "foo2",
21887 Labels: map[string]string{"foo": "bar"},
21888 },
21889 }, core.Namespace{
21890 ObjectMeta: metav1.ObjectMeta{
21891 Name: "foo2",
21892 Labels: map[string]string{"foo": "baz"},
21893 },
21894 }, true},
21895 {core.Namespace{
21896 ObjectMeta: metav1.ObjectMeta{
21897 Name: "foo3",
21898 },
21899 }, core.Namespace{
21900 ObjectMeta: metav1.ObjectMeta{
21901 Name: "foo3",
21902 Labels: map[string]string{"foo": "baz"},
21903 },
21904 }, true},
21905 {core.Namespace{
21906 ObjectMeta: metav1.ObjectMeta{
21907 Name: "foo4",
21908 Labels: map[string]string{"bar": "foo"},
21909 },
21910 }, core.Namespace{
21911 ObjectMeta: metav1.ObjectMeta{
21912 Name: "foo4",
21913 Labels: map[string]string{"foo": "baz"},
21914 },
21915 }, true},
21916 {core.Namespace{
21917 ObjectMeta: metav1.ObjectMeta{
21918 Name: "foo5",
21919 Labels: map[string]string{"foo": "baz"},
21920 },
21921 }, core.Namespace{
21922 ObjectMeta: metav1.ObjectMeta{
21923 Name: "foo5",
21924 Labels: map[string]string{"Foo": "baz"},
21925 },
21926 }, true},
21927 {core.Namespace{
21928 ObjectMeta: metav1.ObjectMeta{
21929 Name: "foo6",
21930 Labels: map[string]string{"foo": "baz"},
21931 },
21932 }, core.Namespace{
21933 ObjectMeta: metav1.ObjectMeta{
21934 Name: "foo6",
21935 Labels: map[string]string{"Foo": "baz"},
21936 },
21937 Spec: core.NamespaceSpec{
21938 Finalizers: []core.FinalizerName{"kubernetes"},
21939 },
21940 Status: core.NamespaceStatus{
21941 Phase: core.NamespaceTerminating,
21942 },
21943 }, true},
21944 }
21945 for i, test := range tests {
21946 test.namespace.ObjectMeta.ResourceVersion = "1"
21947 test.oldNamespace.ObjectMeta.ResourceVersion = "1"
21948 errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace)
21949 if test.valid && len(errs) > 0 {
21950 t.Errorf("%d: Unexpected error: %v", i, errs)
21951 t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
21952 }
21953 if !test.valid && len(errs) == 0 {
21954 t.Errorf("%d: Unexpected non-error", i)
21955 }
21956 }
21957 }
21958
21959 func TestValidateSecret(t *testing.T) {
21960
21961 validSecret := func() core.Secret {
21962 return core.Secret{
21963 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
21964 Data: map[string][]byte{
21965 "data-1": []byte("bar"),
21966 },
21967 }
21968 }
21969
21970 var (
21971 emptyName = validSecret()
21972 invalidName = validSecret()
21973 emptyNs = validSecret()
21974 invalidNs = validSecret()
21975 overMaxSize = validSecret()
21976 invalidKey = validSecret()
21977 leadingDotKey = validSecret()
21978 dotKey = validSecret()
21979 doubleDotKey = validSecret()
21980 )
21981
21982 emptyName.Name = ""
21983 invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals"
21984 emptyNs.Namespace = ""
21985 invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals"
21986 overMaxSize.Data = map[string][]byte{
21987 "over": make([]byte, core.MaxSecretSize+1),
21988 }
21989 invalidKey.Data["a*b"] = []byte("whoops")
21990 leadingDotKey.Data[".key"] = []byte("bar")
21991 dotKey.Data["."] = []byte("bar")
21992 doubleDotKey.Data[".."] = []byte("bar")
21993
21994
21995 validServiceAccountTokenSecret := func() core.Secret {
21996 return core.Secret{
21997 ObjectMeta: metav1.ObjectMeta{
21998 Name: "foo",
21999 Namespace: "bar",
22000 Annotations: map[string]string{
22001 core.ServiceAccountNameKey: "foo",
22002 },
22003 },
22004 Type: core.SecretTypeServiceAccountToken,
22005 Data: map[string][]byte{
22006 "data-1": []byte("bar"),
22007 },
22008 }
22009 }
22010
22011 var (
22012 emptyTokenAnnotation = validServiceAccountTokenSecret()
22013 missingTokenAnnotation = validServiceAccountTokenSecret()
22014 missingTokenAnnotations = validServiceAccountTokenSecret()
22015 )
22016 emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = ""
22017 delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey)
22018 missingTokenAnnotations.Annotations = nil
22019
22020 tests := map[string]struct {
22021 secret core.Secret
22022 valid bool
22023 }{
22024 "valid": {validSecret(), true},
22025 "empty name": {emptyName, false},
22026 "invalid name": {invalidName, false},
22027 "empty namespace": {emptyNs, false},
22028 "invalid namespace": {invalidNs, false},
22029 "over max size": {overMaxSize, false},
22030 "invalid key": {invalidKey, false},
22031 "valid service-account-token secret": {validServiceAccountTokenSecret(), true},
22032 "empty service-account-token annotation": {emptyTokenAnnotation, false},
22033 "missing service-account-token annotation": {missingTokenAnnotation, false},
22034 "missing service-account-token annotations": {missingTokenAnnotations, false},
22035 "leading dot key": {leadingDotKey, true},
22036 "dot key": {dotKey, false},
22037 "double dot key": {doubleDotKey, false},
22038 }
22039
22040 for name, tc := range tests {
22041 errs := ValidateSecret(&tc.secret)
22042 if tc.valid && len(errs) > 0 {
22043 t.Errorf("%v: Unexpected error: %v", name, errs)
22044 }
22045 if !tc.valid && len(errs) == 0 {
22046 t.Errorf("%v: Unexpected non-error", name)
22047 }
22048 }
22049 }
22050
22051 func TestValidateSecretUpdate(t *testing.T) {
22052 validSecret := func() core.Secret {
22053 return core.Secret{
22054 ObjectMeta: metav1.ObjectMeta{
22055 Name: "foo",
22056 Namespace: "bar",
22057 ResourceVersion: "20",
22058 },
22059 Data: map[string][]byte{
22060 "data-1": []byte("bar"),
22061 },
22062 }
22063 }
22064
22065 falseVal := false
22066 trueVal := true
22067
22068 secret := validSecret()
22069 immutableSecret := validSecret()
22070 immutableSecret.Immutable = &trueVal
22071 mutableSecret := validSecret()
22072 mutableSecret.Immutable = &falseVal
22073
22074 secretWithData := validSecret()
22075 secretWithData.Data["data-2"] = []byte("baz")
22076 immutableSecretWithData := validSecret()
22077 immutableSecretWithData.Immutable = &trueVal
22078 immutableSecretWithData.Data["data-2"] = []byte("baz")
22079
22080 secretWithChangedData := validSecret()
22081 secretWithChangedData.Data["data-1"] = []byte("foo")
22082 immutableSecretWithChangedData := validSecret()
22083 immutableSecretWithChangedData.Immutable = &trueVal
22084 immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
22085
22086 tests := []struct {
22087 name string
22088 oldSecret core.Secret
22089 newSecret core.Secret
22090 valid bool
22091 }{{
22092 name: "mark secret immutable",
22093 oldSecret: secret,
22094 newSecret: immutableSecret,
22095 valid: true,
22096 }, {
22097 name: "revert immutable secret",
22098 oldSecret: immutableSecret,
22099 newSecret: secret,
22100 valid: false,
22101 }, {
22102 name: "makr immutable secret mutable",
22103 oldSecret: immutableSecret,
22104 newSecret: mutableSecret,
22105 valid: false,
22106 }, {
22107 name: "add data in secret",
22108 oldSecret: secret,
22109 newSecret: secretWithData,
22110 valid: true,
22111 }, {
22112 name: "add data in immutable secret",
22113 oldSecret: immutableSecret,
22114 newSecret: immutableSecretWithData,
22115 valid: false,
22116 }, {
22117 name: "change data in secret",
22118 oldSecret: secret,
22119 newSecret: secretWithChangedData,
22120 valid: true,
22121 }, {
22122 name: "change data in immutable secret",
22123 oldSecret: immutableSecret,
22124 newSecret: immutableSecretWithChangedData,
22125 valid: false,
22126 },
22127 }
22128
22129 for _, tc := range tests {
22130 t.Run(tc.name, func(t *testing.T) {
22131 errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
22132 if tc.valid && len(errs) > 0 {
22133 t.Errorf("Unexpected error: %v", errs)
22134 }
22135 if !tc.valid && len(errs) == 0 {
22136 t.Errorf("Unexpected lack of error")
22137 }
22138 })
22139 }
22140 }
22141
22142 func TestValidateDockerConfigSecret(t *testing.T) {
22143 validDockerSecret := func() core.Secret {
22144 return core.Secret{
22145 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
22146 Type: core.SecretTypeDockercfg,
22147 Data: map[string][]byte{
22148 core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`),
22149 },
22150 }
22151 }
22152 validDockerSecret2 := func() core.Secret {
22153 return core.Secret{
22154 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
22155 Type: core.SecretTypeDockerConfigJSON,
22156 Data: map[string][]byte{
22157 core.DockerConfigJSONKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`),
22158 },
22159 }
22160 }
22161
22162 var (
22163 missingDockerConfigKey = validDockerSecret()
22164 emptyDockerConfigKey = validDockerSecret()
22165 invalidDockerConfigKey = validDockerSecret()
22166 missingDockerConfigKey2 = validDockerSecret2()
22167 emptyDockerConfigKey2 = validDockerSecret2()
22168 invalidDockerConfigKey2 = validDockerSecret2()
22169 )
22170
22171 delete(missingDockerConfigKey.Data, core.DockerConfigKey)
22172 emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("")
22173 invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad")
22174 delete(missingDockerConfigKey2.Data, core.DockerConfigJSONKey)
22175 emptyDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("")
22176 invalidDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("bad")
22177
22178 tests := map[string]struct {
22179 secret core.Secret
22180 valid bool
22181 }{
22182 "valid dockercfg": {validDockerSecret(), true},
22183 "missing dockercfg": {missingDockerConfigKey, false},
22184 "empty dockercfg": {emptyDockerConfigKey, false},
22185 "invalid dockercfg": {invalidDockerConfigKey, false},
22186 "valid config.json": {validDockerSecret2(), true},
22187 "missing config.json": {missingDockerConfigKey2, false},
22188 "empty config.json": {emptyDockerConfigKey2, false},
22189 "invalid config.json": {invalidDockerConfigKey2, false},
22190 }
22191
22192 for name, tc := range tests {
22193 errs := ValidateSecret(&tc.secret)
22194 if tc.valid && len(errs) > 0 {
22195 t.Errorf("%v: Unexpected error: %v", name, errs)
22196 }
22197 if !tc.valid && len(errs) == 0 {
22198 t.Errorf("%v: Unexpected non-error", name)
22199 }
22200 }
22201 }
22202
22203 func TestValidateBasicAuthSecret(t *testing.T) {
22204 validBasicAuthSecret := func() core.Secret {
22205 return core.Secret{
22206 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
22207 Type: core.SecretTypeBasicAuth,
22208 Data: map[string][]byte{
22209 core.BasicAuthUsernameKey: []byte("username"),
22210 core.BasicAuthPasswordKey: []byte("password"),
22211 },
22212 }
22213 }
22214
22215 var (
22216 missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret()
22217 )
22218
22219 delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey)
22220 delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey)
22221
22222 tests := map[string]struct {
22223 secret core.Secret
22224 valid bool
22225 }{
22226 "valid": {validBasicAuthSecret(), true},
22227 "missing username and password": {missingBasicAuthUsernamePasswordKeys, false},
22228 }
22229
22230 for name, tc := range tests {
22231 errs := ValidateSecret(&tc.secret)
22232 if tc.valid && len(errs) > 0 {
22233 t.Errorf("%v: Unexpected error: %v", name, errs)
22234 }
22235 if !tc.valid && len(errs) == 0 {
22236 t.Errorf("%v: Unexpected non-error", name)
22237 }
22238 }
22239 }
22240
22241 func TestValidateSSHAuthSecret(t *testing.T) {
22242 validSSHAuthSecret := func() core.Secret {
22243 return core.Secret{
22244 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
22245 Type: core.SecretTypeSSHAuth,
22246 Data: map[string][]byte{
22247 core.SSHAuthPrivateKey: []byte("foo-bar-baz"),
22248 },
22249 }
22250 }
22251
22252 missingSSHAuthPrivateKey := validSSHAuthSecret()
22253
22254 delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey)
22255
22256 tests := map[string]struct {
22257 secret core.Secret
22258 valid bool
22259 }{
22260 "valid": {validSSHAuthSecret(), true},
22261 "missing private key": {missingSSHAuthPrivateKey, false},
22262 }
22263
22264 for name, tc := range tests {
22265 errs := ValidateSecret(&tc.secret)
22266 if tc.valid && len(errs) > 0 {
22267 t.Errorf("%v: Unexpected error: %v", name, errs)
22268 }
22269 if !tc.valid && len(errs) == 0 {
22270 t.Errorf("%v: Unexpected non-error", name)
22271 }
22272 }
22273 }
22274
22275 func TestValidateEndpointsCreate(t *testing.T) {
22276 successCases := map[string]struct {
22277 endpoints core.Endpoints
22278 }{
22279 "simple endpoint": {
22280 endpoints: core.Endpoints{
22281 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22282 Subsets: []core.EndpointSubset{{
22283 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
22284 Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
22285 }, {
22286 Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
22287 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
22288 }},
22289 },
22290 },
22291 "empty subsets": {
22292 endpoints: core.Endpoints{
22293 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22294 },
22295 },
22296 "no name required for singleton port": {
22297 endpoints: core.Endpoints{
22298 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22299 Subsets: []core.EndpointSubset{{
22300 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22301 Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
22302 }},
22303 },
22304 },
22305 "valid appProtocol": {
22306 endpoints: core.Endpoints{
22307 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22308 Subsets: []core.EndpointSubset{{
22309 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22310 Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}},
22311 }},
22312 },
22313 },
22314 "empty ports": {
22315 endpoints: core.Endpoints{
22316 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22317 Subsets: []core.EndpointSubset{{
22318 Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
22319 }},
22320 },
22321 },
22322 }
22323
22324 for name, tc := range successCases {
22325 t.Run(name, func(t *testing.T) {
22326 errs := ValidateEndpointsCreate(&tc.endpoints)
22327 if len(errs) != 0 {
22328 t.Errorf("Expected no validation errors, got %v", errs)
22329 }
22330
22331 })
22332 }
22333
22334 errorCases := map[string]struct {
22335 endpoints core.Endpoints
22336 errorType field.ErrorType
22337 errorDetail string
22338 }{
22339 "missing namespace": {
22340 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}},
22341 errorType: "FieldValueRequired",
22342 },
22343 "missing name": {
22344 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}},
22345 errorType: "FieldValueRequired",
22346 },
22347 "invalid namespace": {
22348 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}},
22349 errorType: "FieldValueInvalid",
22350 errorDetail: dnsLabelErrMsg,
22351 },
22352 "invalid name": {
22353 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}},
22354 errorType: "FieldValueInvalid",
22355 errorDetail: dnsSubdomainLabelErrMsg,
22356 },
22357 "empty addresses": {
22358 endpoints: core.Endpoints{
22359 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22360 Subsets: []core.EndpointSubset{{
22361 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
22362 }},
22363 },
22364 errorType: "FieldValueRequired",
22365 },
22366 "invalid IP": {
22367 endpoints: core.Endpoints{
22368 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22369 Subsets: []core.EndpointSubset{{
22370 Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}},
22371 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
22372 }},
22373 },
22374 errorType: "FieldValueInvalid",
22375 errorDetail: "must be a valid IP address",
22376 },
22377 "Multiple ports, one without name": {
22378 endpoints: core.Endpoints{
22379 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22380 Subsets: []core.EndpointSubset{{
22381 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22382 Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
22383 }},
22384 },
22385 errorType: "FieldValueRequired",
22386 },
22387 "Invalid port number": {
22388 endpoints: core.Endpoints{
22389 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22390 Subsets: []core.EndpointSubset{{
22391 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22392 Ports: []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}},
22393 }},
22394 },
22395 errorType: "FieldValueInvalid",
22396 errorDetail: "between",
22397 },
22398 "Invalid protocol": {
22399 endpoints: core.Endpoints{
22400 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22401 Subsets: []core.EndpointSubset{{
22402 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22403 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}},
22404 }},
22405 },
22406 errorType: "FieldValueNotSupported",
22407 },
22408 "Address missing IP": {
22409 endpoints: core.Endpoints{
22410 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22411 Subsets: []core.EndpointSubset{{
22412 Addresses: []core.EndpointAddress{{}},
22413 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
22414 }},
22415 },
22416 errorType: "FieldValueInvalid",
22417 errorDetail: "must be a valid IP address",
22418 },
22419 "Port missing number": {
22420 endpoints: core.Endpoints{
22421 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22422 Subsets: []core.EndpointSubset{{
22423 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22424 Ports: []core.EndpointPort{{Name: "a", Protocol: "TCP"}},
22425 }},
22426 },
22427 errorType: "FieldValueInvalid",
22428 errorDetail: "between",
22429 },
22430 "Port missing protocol": {
22431 endpoints: core.Endpoints{
22432 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22433 Subsets: []core.EndpointSubset{{
22434 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22435 Ports: []core.EndpointPort{{Name: "a", Port: 93}},
22436 }},
22437 },
22438 errorType: "FieldValueRequired",
22439 },
22440 "Address is loopback": {
22441 endpoints: core.Endpoints{
22442 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22443 Subsets: []core.EndpointSubset{{
22444 Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}},
22445 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
22446 }},
22447 },
22448 errorType: "FieldValueInvalid",
22449 errorDetail: "loopback",
22450 },
22451 "Address is link-local": {
22452 endpoints: core.Endpoints{
22453 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22454 Subsets: []core.EndpointSubset{{
22455 Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}},
22456 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
22457 }},
22458 },
22459 errorType: "FieldValueInvalid",
22460 errorDetail: "link-local",
22461 },
22462 "Address is link-local multicast": {
22463 endpoints: core.Endpoints{
22464 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22465 Subsets: []core.EndpointSubset{{
22466 Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}},
22467 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
22468 }},
22469 },
22470 errorType: "FieldValueInvalid",
22471 errorDetail: "link-local multicast",
22472 },
22473 "Invalid AppProtocol": {
22474 endpoints: core.Endpoints{
22475 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
22476 Subsets: []core.EndpointSubset{{
22477 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
22478 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}},
22479 }},
22480 },
22481 errorType: "FieldValueInvalid",
22482 errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character",
22483 },
22484 }
22485
22486 for k, v := range errorCases {
22487 t.Run(k, func(t *testing.T) {
22488 if errs := ValidateEndpointsCreate(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
22489 t.Errorf("Expected error type %s with detail %q, got %v", v.errorType, v.errorDetail, errs)
22490 }
22491 })
22492 }
22493 }
22494
22495 func TestValidateEndpointsUpdate(t *testing.T) {
22496 baseEndpoints := core.Endpoints{
22497 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace", ResourceVersion: "1234"},
22498 Subsets: []core.EndpointSubset{{
22499 Addresses: []core.EndpointAddress{{IP: "10.1.2.3"}},
22500 }},
22501 }
22502
22503 testCases := map[string]struct {
22504 tweakOldEndpoints func(ep *core.Endpoints)
22505 tweakNewEndpoints func(ep *core.Endpoints)
22506 numExpectedErrors int
22507 }{
22508 "update to valid app protocol": {
22509 tweakOldEndpoints: func(ep *core.Endpoints) {
22510 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
22511 },
22512 tweakNewEndpoints: func(ep *core.Endpoints) {
22513 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
22514 },
22515 numExpectedErrors: 0,
22516 },
22517 "update to invalid app protocol": {
22518 tweakOldEndpoints: func(ep *core.Endpoints) {
22519 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
22520 },
22521 tweakNewEndpoints: func(ep *core.Endpoints) {
22522 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
22523 },
22524 numExpectedErrors: 1,
22525 },
22526 }
22527
22528 for name, tc := range testCases {
22529 t.Run(name, func(t *testing.T) {
22530 oldEndpoints := baseEndpoints.DeepCopy()
22531 tc.tweakOldEndpoints(oldEndpoints)
22532 newEndpoints := baseEndpoints.DeepCopy()
22533 tc.tweakNewEndpoints(newEndpoints)
22534
22535 errs := ValidateEndpointsUpdate(newEndpoints, oldEndpoints)
22536 if len(errs) != tc.numExpectedErrors {
22537 t.Errorf("Expected %d validation errors, got %d: %v", tc.numExpectedErrors, len(errs), errs)
22538 }
22539
22540 })
22541 }
22542 }
22543
22544 func TestValidateWindowsSecurityContext(t *testing.T) {
22545 tests := []struct {
22546 name string
22547 sc *core.PodSpec
22548 expectError bool
22549 errorMsg string
22550 errorType field.ErrorType
22551 }{{
22552 name: "pod with SELinux Options",
22553 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}},
22554 expectError: true,
22555 errorMsg: "cannot be set for a windows pod",
22556 errorType: "FieldValueForbidden",
22557 }, {
22558 name: "pod with SeccompProfile",
22559 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}},
22560 expectError: true,
22561 errorMsg: "cannot be set for a windows pod",
22562 errorType: "FieldValueForbidden",
22563 }, {
22564 name: "pod with AppArmorProfile",
22565 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{AppArmorProfile: &core.AppArmorProfile{Type: core.AppArmorProfileTypeRuntimeDefault}}}}},
22566 expectError: true,
22567 errorMsg: "cannot be set for a windows pod",
22568 errorType: "FieldValueForbidden",
22569 }, {
22570 name: "pod with WindowsOptions, no error",
22571 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}},
22572 expectError: false,
22573 },
22574 }
22575 for _, test := range tests {
22576 t.Run(test.name, func(t *testing.T) {
22577 errs := validateWindows(test.sc, field.NewPath("field"))
22578 if test.expectError && len(errs) > 0 {
22579 if errs[0].Type != test.errorType {
22580 t.Errorf("expected error type %q got %q", test.errorType, errs[0].Type)
22581 }
22582 if errs[0].Detail != test.errorMsg {
22583 t.Errorf("expected error detail %q, got %q", test.errorMsg, errs[0].Detail)
22584 }
22585 } else if test.expectError && len(errs) == 0 {
22586 t.Error("Unexpected success")
22587 }
22588 if !test.expectError && len(errs) != 0 {
22589 t.Errorf("Unexpected error(s): %v", errs)
22590 }
22591 })
22592 }
22593 }
22594
22595 func TestValidateOSFields(t *testing.T) {
22596
22597
22598
22599
22600
22601
22602 osSpecificFields := sets.NewString(
22603 "Containers[*].SecurityContext.AppArmorProfile",
22604 "Containers[*].SecurityContext.AllowPrivilegeEscalation",
22605 "Containers[*].SecurityContext.Capabilities",
22606 "Containers[*].SecurityContext.Privileged",
22607 "Containers[*].SecurityContext.ProcMount",
22608 "Containers[*].SecurityContext.ReadOnlyRootFilesystem",
22609 "Containers[*].SecurityContext.RunAsGroup",
22610 "Containers[*].SecurityContext.RunAsUser",
22611 "Containers[*].SecurityContext.SELinuxOptions",
22612 "Containers[*].SecurityContext.SeccompProfile",
22613 "Containers[*].SecurityContext.WindowsOptions",
22614 "InitContainers[*].SecurityContext.AppArmorProfile",
22615 "InitContainers[*].SecurityContext.AllowPrivilegeEscalation",
22616 "InitContainers[*].SecurityContext.Capabilities",
22617 "InitContainers[*].SecurityContext.Privileged",
22618 "InitContainers[*].SecurityContext.ProcMount",
22619 "InitContainers[*].SecurityContext.ReadOnlyRootFilesystem",
22620 "InitContainers[*].SecurityContext.RunAsGroup",
22621 "InitContainers[*].SecurityContext.RunAsUser",
22622 "InitContainers[*].SecurityContext.SELinuxOptions",
22623 "InitContainers[*].SecurityContext.SeccompProfile",
22624 "InitContainers[*].SecurityContext.WindowsOptions",
22625 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AppArmorProfile",
22626 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AllowPrivilegeEscalation",
22627 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Capabilities",
22628 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Privileged",
22629 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ProcMount",
22630 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ReadOnlyRootFilesystem",
22631 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsGroup",
22632 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsUser",
22633 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SELinuxOptions",
22634 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile",
22635 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions",
22636 "OS",
22637 "SecurityContext.AppArmorProfile",
22638 "SecurityContext.FSGroup",
22639 "SecurityContext.FSGroupChangePolicy",
22640 "SecurityContext.HostIPC",
22641 "SecurityContext.HostNetwork",
22642 "SecurityContext.HostPID",
22643 "SecurityContext.HostUsers",
22644 "SecurityContext.RunAsGroup",
22645 "SecurityContext.RunAsUser",
22646 "SecurityContext.SELinuxOptions",
22647 "SecurityContext.SeccompProfile",
22648 "SecurityContext.ShareProcessNamespace",
22649 "SecurityContext.SupplementalGroups",
22650 "SecurityContext.Sysctls",
22651 "SecurityContext.WindowsOptions",
22652 )
22653 osNeutralFields := sets.NewString(
22654 "ActiveDeadlineSeconds",
22655 "Affinity",
22656 "AutomountServiceAccountToken",
22657 "Containers[*].Args",
22658 "Containers[*].Command",
22659 "Containers[*].Env",
22660 "Containers[*].EnvFrom",
22661 "Containers[*].Image",
22662 "Containers[*].ImagePullPolicy",
22663 "Containers[*].Lifecycle",
22664 "Containers[*].LivenessProbe",
22665 "Containers[*].Name",
22666 "Containers[*].Ports",
22667 "Containers[*].ReadinessProbe",
22668 "Containers[*].Resources",
22669 "Containers[*].ResizePolicy[*].RestartPolicy",
22670 "Containers[*].ResizePolicy[*].ResourceName",
22671 "Containers[*].RestartPolicy",
22672 "Containers[*].SecurityContext.RunAsNonRoot",
22673 "Containers[*].Stdin",
22674 "Containers[*].StdinOnce",
22675 "Containers[*].StartupProbe",
22676 "Containers[*].VolumeDevices[*]",
22677 "Containers[*].VolumeMounts[*]",
22678 "Containers[*].TTY",
22679 "Containers[*].TerminationMessagePath",
22680 "Containers[*].TerminationMessagePolicy",
22681 "Containers[*].WorkingDir",
22682 "DNSPolicy",
22683 "EnableServiceLinks",
22684 "EphemeralContainers[*].EphemeralContainerCommon.Args",
22685 "EphemeralContainers[*].EphemeralContainerCommon.Command",
22686 "EphemeralContainers[*].EphemeralContainerCommon.Env",
22687 "EphemeralContainers[*].EphemeralContainerCommon.EnvFrom",
22688 "EphemeralContainers[*].EphemeralContainerCommon.Image",
22689 "EphemeralContainers[*].EphemeralContainerCommon.ImagePullPolicy",
22690 "EphemeralContainers[*].EphemeralContainerCommon.Lifecycle",
22691 "EphemeralContainers[*].EphemeralContainerCommon.LivenessProbe",
22692 "EphemeralContainers[*].EphemeralContainerCommon.Name",
22693 "EphemeralContainers[*].EphemeralContainerCommon.Ports",
22694 "EphemeralContainers[*].EphemeralContainerCommon.ReadinessProbe",
22695 "EphemeralContainers[*].EphemeralContainerCommon.Resources",
22696 "EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].RestartPolicy",
22697 "EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].ResourceName",
22698 "EphemeralContainers[*].EphemeralContainerCommon.RestartPolicy",
22699 "EphemeralContainers[*].EphemeralContainerCommon.Stdin",
22700 "EphemeralContainers[*].EphemeralContainerCommon.StdinOnce",
22701 "EphemeralContainers[*].EphemeralContainerCommon.TTY",
22702 "EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePath",
22703 "EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePolicy",
22704 "EphemeralContainers[*].EphemeralContainerCommon.WorkingDir",
22705 "EphemeralContainers[*].TargetContainerName",
22706 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsNonRoot",
22707 "EphemeralContainers[*].EphemeralContainerCommon.StartupProbe",
22708 "EphemeralContainers[*].EphemeralContainerCommon.VolumeDevices[*]",
22709 "EphemeralContainers[*].EphemeralContainerCommon.VolumeMounts[*]",
22710 "HostAliases",
22711 "Hostname",
22712 "ImagePullSecrets",
22713 "InitContainers[*].Args",
22714 "InitContainers[*].Command",
22715 "InitContainers[*].Env",
22716 "InitContainers[*].EnvFrom",
22717 "InitContainers[*].Image",
22718 "InitContainers[*].ImagePullPolicy",
22719 "InitContainers[*].Lifecycle",
22720 "InitContainers[*].LivenessProbe",
22721 "InitContainers[*].Name",
22722 "InitContainers[*].Ports",
22723 "InitContainers[*].ReadinessProbe",
22724 "InitContainers[*].Resources",
22725 "InitContainers[*].ResizePolicy[*].RestartPolicy",
22726 "InitContainers[*].ResizePolicy[*].ResourceName",
22727 "InitContainers[*].RestartPolicy",
22728 "InitContainers[*].Stdin",
22729 "InitContainers[*].StdinOnce",
22730 "InitContainers[*].TTY",
22731 "InitContainers[*].TerminationMessagePath",
22732 "InitContainers[*].TerminationMessagePolicy",
22733 "InitContainers[*].WorkingDir",
22734 "InitContainers[*].SecurityContext.RunAsNonRoot",
22735 "InitContainers[*].StartupProbe",
22736 "InitContainers[*].VolumeDevices[*]",
22737 "InitContainers[*].VolumeMounts[*]",
22738 "NodeName",
22739 "NodeSelector",
22740 "PreemptionPolicy",
22741 "Priority",
22742 "PriorityClassName",
22743 "ReadinessGates",
22744 "ResourceClaims[*].Name",
22745 "ResourceClaims[*].Source.ResourceClaimName",
22746 "ResourceClaims[*].Source.ResourceClaimTemplateName",
22747 "RestartPolicy",
22748 "RuntimeClassName",
22749 "SchedulerName",
22750 "SchedulingGates[*].Name",
22751 "SecurityContext.RunAsNonRoot",
22752 "ServiceAccountName",
22753 "SetHostnameAsFQDN",
22754 "Subdomain",
22755 "TerminationGracePeriodSeconds",
22756 "Volumes",
22757 "DNSConfig",
22758 "Overhead",
22759 "Tolerations",
22760 "TopologySpreadConstraints",
22761 )
22762
22763 expect := sets.NewString().Union(osSpecificFields).Union(osNeutralFields)
22764
22765 result := collectResourcePaths(t, expect, reflect.TypeOf(&core.PodSpec{}), nil)
22766
22767 if !expect.Equal(result) {
22768
22769 missing := expect.Difference(result)
22770
22771 unexpected := result.Difference(expect)
22772 if len(missing) > 0 {
22773 t.Errorf("the following fields were expected, but missing from the result. "+
22774 "If the field has been removed, please remove it from the osNeutralFields set "+
22775 "or remove it from the osSpecificFields set, as appropriate:\n%s",
22776 strings.Join(missing.List(), "\n"))
22777 }
22778 if len(unexpected) > 0 {
22779 t.Errorf("the following fields were in the result, but unexpected. "+
22780 "If the field is new, please add it to the osNeutralFields set "+
22781 "or add it to the osSpecificFields set, as appropriate:\n%s",
22782 strings.Join(unexpected.List(), "\n"))
22783 }
22784 }
22785 }
22786
22787 func TestValidateSchedulingGates(t *testing.T) {
22788 fieldPath := field.NewPath("field")
22789
22790 tests := []struct {
22791 name string
22792 schedulingGates []core.PodSchedulingGate
22793 wantFieldErrors field.ErrorList
22794 }{{
22795 name: "nil gates",
22796 schedulingGates: nil,
22797 wantFieldErrors: field.ErrorList{},
22798 }, {
22799 name: "empty string in gates",
22800 schedulingGates: []core.PodSchedulingGate{
22801 {Name: "foo"},
22802 {Name: ""},
22803 },
22804 wantFieldErrors: field.ErrorList{
22805 field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"),
22806 field.Invalid(fieldPath.Index(1), "", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"),
22807 },
22808 }, {
22809 name: "legal gates",
22810 schedulingGates: []core.PodSchedulingGate{
22811 {Name: "foo"},
22812 {Name: "bar"},
22813 },
22814 wantFieldErrors: field.ErrorList{},
22815 }, {
22816 name: "illegal gates",
22817 schedulingGates: []core.PodSchedulingGate{
22818 {Name: "foo"},
22819 {Name: "\nbar"},
22820 },
22821 wantFieldErrors: []*field.Error{field.Invalid(fieldPath.Index(1), "\nbar", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")},
22822 }, {
22823 name: "duplicated gates (single duplication)",
22824 schedulingGates: []core.PodSchedulingGate{
22825 {Name: "foo"},
22826 {Name: "bar"},
22827 {Name: "bar"},
22828 },
22829 wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
22830 }, {
22831 name: "duplicated gates (multiple duplications)",
22832 schedulingGates: []core.PodSchedulingGate{
22833 {Name: "foo"},
22834 {Name: "bar"},
22835 {Name: "foo"},
22836 {Name: "baz"},
22837 {Name: "foo"},
22838 {Name: "bar"},
22839 },
22840 wantFieldErrors: field.ErrorList{
22841 field.Duplicate(fieldPath.Index(2), "foo"),
22842 field.Duplicate(fieldPath.Index(4), "foo"),
22843 field.Duplicate(fieldPath.Index(5), "bar"),
22844 },
22845 },
22846 }
22847 for _, tt := range tests {
22848 t.Run(tt.name, func(t *testing.T) {
22849 errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
22850 if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
22851 t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
22852 }
22853 })
22854 }
22855 }
22856
22857
22858 func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
22859 if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
22860 return sets.NewString(pathStr)
22861 }
22862
22863 paths := sets.NewString()
22864 switch tp.Kind() {
22865 case reflect.Pointer:
22866 paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path).List()...)
22867 case reflect.Struct:
22868 for i := 0; i < tp.NumField(); i++ {
22869 field := tp.Field(i)
22870 paths.Insert(collectResourcePaths(t, skipRecurseList, field.Type, path.Child(field.Name)).List()...)
22871 }
22872 case reflect.Map, reflect.Slice:
22873 paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path.Key("*")).List()...)
22874 case reflect.Interface:
22875 t.Fatalf("unexpected interface{} field %s", path.String())
22876 default:
22877
22878 paths.Insert(path.String())
22879 }
22880 return paths
22881 }
22882
22883 func TestValidateTLSSecret(t *testing.T) {
22884 successCases := map[string]core.Secret{
22885 "empty certificate chain": {
22886 ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"},
22887 Data: map[string][]byte{
22888 core.TLSCertKey: []byte("public key"),
22889 core.TLSPrivateKeyKey: []byte("private key"),
22890 },
22891 },
22892 }
22893 for k, v := range successCases {
22894 if errs := ValidateSecret(&v); len(errs) != 0 {
22895 t.Errorf("Expected success for %s, got %v", k, errs)
22896 }
22897 }
22898 errorCases := map[string]struct {
22899 secrets core.Secret
22900 errorType field.ErrorType
22901 errorDetail string
22902 }{
22903 "missing public key": {
22904 secrets: core.Secret{
22905 ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
22906 Data: map[string][]byte{
22907 core.TLSCertKey: []byte("public key"),
22908 },
22909 },
22910 errorType: "FieldValueRequired",
22911 },
22912 "missing private key": {
22913 secrets: core.Secret{
22914 ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
22915 Data: map[string][]byte{
22916 core.TLSCertKey: []byte("public key"),
22917 },
22918 },
22919 errorType: "FieldValueRequired",
22920 },
22921 }
22922 for k, v := range errorCases {
22923 if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
22924 t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
22925 }
22926 }
22927 }
22928
22929 func TestValidateLinuxSecurityContext(t *testing.T) {
22930 runAsUser := int64(1)
22931 validLinuxSC := &core.SecurityContext{
22932 Privileged: utilpointer.Bool(false),
22933 Capabilities: &core.Capabilities{
22934 Add: []core.Capability{"foo"},
22935 Drop: []core.Capability{"bar"},
22936 },
22937 SELinuxOptions: &core.SELinuxOptions{
22938 User: "user",
22939 Role: "role",
22940 Type: "type",
22941 Level: "level",
22942 },
22943 RunAsUser: &runAsUser,
22944 }
22945 invalidLinuxSC := &core.SecurityContext{
22946 WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
22947 }
22948 cases := map[string]struct {
22949 sc *core.PodSpec
22950 expectErr bool
22951 errorType field.ErrorType
22952 errorDetail string
22953 }{
22954 "valid SC, linux, no error": {
22955 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: validLinuxSC}}},
22956 expectErr: false,
22957 },
22958 "invalid SC, linux, error": {
22959 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: invalidLinuxSC}}},
22960 errorType: "FieldValueForbidden",
22961 errorDetail: "windows options cannot be set for a linux pod",
22962 expectErr: true,
22963 },
22964 }
22965 for k, v := range cases {
22966 t.Run(k, func(t *testing.T) {
22967 errs := validateLinux(v.sc, field.NewPath("field"))
22968 if v.expectErr && len(errs) > 0 {
22969 if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
22970 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
22971 }
22972 } else if v.expectErr && len(errs) == 0 {
22973 t.Errorf("Unexpected success")
22974 }
22975 if !v.expectErr && len(errs) != 0 {
22976 t.Errorf("Unexpected error(s): %v", errs)
22977 }
22978 })
22979 }
22980 }
22981
22982 func TestValidateSecurityContext(t *testing.T) {
22983 runAsUser := int64(1)
22984 fullValidSC := func() *core.SecurityContext {
22985 return &core.SecurityContext{
22986 Privileged: utilpointer.Bool(false),
22987 Capabilities: &core.Capabilities{
22988 Add: []core.Capability{"foo"},
22989 Drop: []core.Capability{"bar"},
22990 },
22991 SELinuxOptions: &core.SELinuxOptions{
22992 User: "user",
22993 Role: "role",
22994 Type: "type",
22995 Level: "level",
22996 },
22997 RunAsUser: &runAsUser,
22998 }
22999 }
23000
23001
23002 allSettings := fullValidSC()
23003 noCaps := fullValidSC()
23004 noCaps.Capabilities = nil
23005
23006 noSELinux := fullValidSC()
23007 noSELinux.SELinuxOptions = nil
23008
23009 noPrivRequest := fullValidSC()
23010 noPrivRequest.Privileged = nil
23011
23012 noRunAsUser := fullValidSC()
23013 noRunAsUser.RunAsUser = nil
23014
23015 procMountSet := fullValidSC()
23016 defPmt := core.DefaultProcMount
23017 procMountSet.ProcMount = &defPmt
23018
23019 umPmt := core.UnmaskedProcMount
23020 procMountUnmasked := fullValidSC()
23021 procMountUnmasked.ProcMount = &umPmt
23022
23023 successCases := map[string]struct {
23024 sc *core.SecurityContext
23025 hostUsers bool
23026 }{
23027 "all settings": {allSettings, false},
23028 "no capabilities": {noCaps, false},
23029 "no selinux": {noSELinux, false},
23030 "no priv request": {noPrivRequest, false},
23031 "no run as user": {noRunAsUser, false},
23032 "proc mount set": {procMountSet, true},
23033 "proc mount unmasked": {procMountUnmasked, false},
23034 }
23035 for k, v := range successCases {
23036 if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), v.hostUsers); len(errs) != 0 {
23037 t.Errorf("[%s] Expected success, got %v", k, errs)
23038 }
23039 }
23040
23041 privRequestWithGlobalDeny := fullValidSC()
23042 privRequestWithGlobalDeny.Privileged = utilpointer.Bool(true)
23043
23044 negativeRunAsUser := fullValidSC()
23045 negativeUser := int64(-1)
23046 negativeRunAsUser.RunAsUser = &negativeUser
23047
23048 privWithoutEscalation := fullValidSC()
23049 privWithoutEscalation.Privileged = utilpointer.Bool(true)
23050 privWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
23051
23052 capSysAdminWithoutEscalation := fullValidSC()
23053 capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"}
23054 capSysAdminWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
23055
23056 errorCases := map[string]struct {
23057 sc *core.SecurityContext
23058 errorType field.ErrorType
23059 errorDetail string
23060 capAllowPriv bool
23061 }{
23062 "request privileged when capabilities forbids": {
23063 sc: privRequestWithGlobalDeny,
23064 errorType: "FieldValueForbidden",
23065 errorDetail: "disallowed by cluster policy",
23066 },
23067 "negative RunAsUser": {
23068 sc: negativeRunAsUser,
23069 errorType: "FieldValueInvalid",
23070 errorDetail: "must be between",
23071 },
23072 "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": {
23073 sc: capSysAdminWithoutEscalation,
23074 errorType: "FieldValueInvalid",
23075 errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN",
23076 },
23077 "with privileged and allowPrivilegeEscalation false": {
23078 sc: privWithoutEscalation,
23079 errorType: "FieldValueInvalid",
23080 errorDetail: "cannot set `allowPrivilegeEscalation` to false and `privileged` to true",
23081 capAllowPriv: true,
23082 },
23083 "with unmasked proc mount type and no user namespace": {
23084 sc: procMountUnmasked,
23085 errorType: "FieldValueInvalid",
23086 errorDetail: "`hostUsers` must be false to use `Unmasked`",
23087 },
23088 }
23089 for k, v := range errorCases {
23090 capabilities.SetForTests(capabilities.Capabilities{
23091 AllowPrivileged: v.capAllowPriv,
23092 })
23093
23094
23095 if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), true); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
23096 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
23097 }
23098 }
23099 }
23100
23101 func fakeValidSecurityContext(priv bool) *core.SecurityContext {
23102 return &core.SecurityContext{
23103 Privileged: &priv,
23104 }
23105 }
23106
23107 func TestValidPodLogOptions(t *testing.T) {
23108 now := metav1.Now()
23109 negative := int64(-1)
23110 zero := int64(0)
23111 positive := int64(1)
23112 tests := []struct {
23113 opt core.PodLogOptions
23114 errs int
23115 }{
23116 {core.PodLogOptions{}, 0},
23117 {core.PodLogOptions{Previous: true}, 0},
23118 {core.PodLogOptions{Follow: true}, 0},
23119 {core.PodLogOptions{TailLines: &zero}, 0},
23120 {core.PodLogOptions{TailLines: &negative}, 1},
23121 {core.PodLogOptions{TailLines: &positive}, 0},
23122 {core.PodLogOptions{LimitBytes: &zero}, 1},
23123 {core.PodLogOptions{LimitBytes: &negative}, 1},
23124 {core.PodLogOptions{LimitBytes: &positive}, 0},
23125 {core.PodLogOptions{SinceSeconds: &negative}, 1},
23126 {core.PodLogOptions{SinceSeconds: &positive}, 0},
23127 {core.PodLogOptions{SinceSeconds: &zero}, 1},
23128 {core.PodLogOptions{SinceTime: &now}, 0},
23129 }
23130 for i, test := range tests {
23131 errs := ValidatePodLogOptions(&test.opt)
23132 if test.errs != len(errs) {
23133 t.Errorf("%d: Unexpected errors: %v", i, errs)
23134 }
23135 }
23136 }
23137
23138 func TestValidateConfigMap(t *testing.T) {
23139 newConfigMap := func(name, namespace string, data map[string]string, binaryData map[string][]byte) core.ConfigMap {
23140 return core.ConfigMap{
23141 ObjectMeta: metav1.ObjectMeta{
23142 Name: name,
23143 Namespace: namespace,
23144 },
23145 Data: data,
23146 BinaryData: binaryData,
23147 }
23148 }
23149
23150 var (
23151 validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}, map[string][]byte{"bin": []byte("value")})
23152 maxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}, nil)
23153
23154 emptyName = newConfigMap("", "validns", nil, nil)
23155 invalidName = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil, nil)
23156 emptyNs = newConfigMap("validname", "", nil, nil)
23157 invalidNs = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, nil)
23158 invalidKey = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}, nil)
23159 leadingDotKey = newConfigMap("validname", "validns", map[string]string{".ab": "value"}, nil)
23160 dotKey = newConfigMap("validname", "validns", map[string]string{".": "value"}, nil)
23161 doubleDotKey = newConfigMap("validname", "validns", map[string]string{"..": "value"}, nil)
23162 overMaxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}, nil)
23163 overMaxSize = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", v1.MaxSecretSize+1)}, nil)
23164 duplicatedKey = newConfigMap("validname", "validns", map[string]string{"key": "value1"}, map[string][]byte{"key": []byte("value2")})
23165 binDataInvalidKey = newConfigMap("validname", "validns", nil, map[string][]byte{"a*b": []byte("value")})
23166 binDataLeadingDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{".ab": []byte("value")})
23167 binDataDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{".": []byte("value")})
23168 binDataDoubleDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{"..": []byte("value")})
23169 binDataOverMaxKeyLength = newConfigMap("validname", "validns", nil, map[string][]byte{strings.Repeat("a", 254): []byte("value")})
23170 binDataOverMaxSize = newConfigMap("validname", "validns", nil, map[string][]byte{"bin": bytes.Repeat([]byte("a"), v1.MaxSecretSize+1)})
23171 binNonUtf8Value = newConfigMap("validname", "validns", nil, map[string][]byte{"key": {0, 0xFE, 0, 0xFF}})
23172 )
23173
23174 tests := map[string]struct {
23175 cfg core.ConfigMap
23176 isValid bool
23177 }{
23178 "valid": {validConfigMap, true},
23179 "max key length": {maxKeyLength, true},
23180 "leading dot key": {leadingDotKey, true},
23181 "empty name": {emptyName, false},
23182 "invalid name": {invalidName, false},
23183 "invalid key": {invalidKey, false},
23184 "empty namespace": {emptyNs, false},
23185 "invalid namespace": {invalidNs, false},
23186 "dot key": {dotKey, false},
23187 "double dot key": {doubleDotKey, false},
23188 "over max key length": {overMaxKeyLength, false},
23189 "over max size": {overMaxSize, false},
23190 "duplicated key": {duplicatedKey, false},
23191 "binary data invalid key": {binDataInvalidKey, false},
23192 "binary data leading dot key": {binDataLeadingDotKey, true},
23193 "binary data dot key": {binDataDotKey, false},
23194 "binary data double dot key": {binDataDoubleDotKey, false},
23195 "binary data over max key length": {binDataOverMaxKeyLength, false},
23196 "binary data max size": {binDataOverMaxSize, false},
23197 "binary data non utf-8 bytes": {binNonUtf8Value, true},
23198 }
23199
23200 for name, tc := range tests {
23201 errs := ValidateConfigMap(&tc.cfg)
23202 if tc.isValid && len(errs) > 0 {
23203 t.Errorf("%v: unexpected error: %v", name, errs)
23204 }
23205 if !tc.isValid && len(errs) == 0 {
23206 t.Errorf("%v: unexpected non-error", name)
23207 }
23208 }
23209 }
23210
23211 func TestValidateConfigMapUpdate(t *testing.T) {
23212 newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap {
23213 return core.ConfigMap{
23214 ObjectMeta: metav1.ObjectMeta{
23215 Name: name,
23216 Namespace: namespace,
23217 ResourceVersion: version,
23218 },
23219 Data: data,
23220 }
23221 }
23222 validConfigMap := func() core.ConfigMap {
23223 return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
23224 }
23225
23226 falseVal := false
23227 trueVal := true
23228
23229 configMap := validConfigMap()
23230 immutableConfigMap := validConfigMap()
23231 immutableConfigMap.Immutable = &trueVal
23232 mutableConfigMap := validConfigMap()
23233 mutableConfigMap.Immutable = &falseVal
23234
23235 configMapWithData := validConfigMap()
23236 configMapWithData.Data["key-2"] = "value-2"
23237 immutableConfigMapWithData := validConfigMap()
23238 immutableConfigMapWithData.Immutable = &trueVal
23239 immutableConfigMapWithData.Data["key-2"] = "value-2"
23240
23241 configMapWithChangedData := validConfigMap()
23242 configMapWithChangedData.Data["key"] = "foo"
23243 immutableConfigMapWithChangedData := validConfigMap()
23244 immutableConfigMapWithChangedData.Immutable = &trueVal
23245 immutableConfigMapWithChangedData.Data["key"] = "foo"
23246
23247 noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
23248
23249 cases := []struct {
23250 name string
23251 newCfg core.ConfigMap
23252 oldCfg core.ConfigMap
23253 valid bool
23254 }{{
23255 name: "valid",
23256 newCfg: configMap,
23257 oldCfg: configMap,
23258 valid: true,
23259 }, {
23260 name: "invalid",
23261 newCfg: noVersion,
23262 oldCfg: configMap,
23263 valid: false,
23264 }, {
23265 name: "mark configmap immutable",
23266 oldCfg: configMap,
23267 newCfg: immutableConfigMap,
23268 valid: true,
23269 }, {
23270 name: "revert immutable configmap",
23271 oldCfg: immutableConfigMap,
23272 newCfg: configMap,
23273 valid: false,
23274 }, {
23275 name: "mark immutable configmap mutable",
23276 oldCfg: immutableConfigMap,
23277 newCfg: mutableConfigMap,
23278 valid: false,
23279 }, {
23280 name: "add data in configmap",
23281 oldCfg: configMap,
23282 newCfg: configMapWithData,
23283 valid: true,
23284 }, {
23285 name: "add data in immutable configmap",
23286 oldCfg: immutableConfigMap,
23287 newCfg: immutableConfigMapWithData,
23288 valid: false,
23289 }, {
23290 name: "change data in configmap",
23291 oldCfg: configMap,
23292 newCfg: configMapWithChangedData,
23293 valid: true,
23294 }, {
23295 name: "change data in immutable configmap",
23296 oldCfg: immutableConfigMap,
23297 newCfg: immutableConfigMapWithChangedData,
23298 valid: false,
23299 },
23300 }
23301
23302 for _, tc := range cases {
23303 t.Run(tc.name, func(t *testing.T) {
23304 errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
23305 if tc.valid && len(errs) > 0 {
23306 t.Errorf("Unexpected error: %v", errs)
23307 }
23308 if !tc.valid && len(errs) == 0 {
23309 t.Errorf("Unexpected lack of error")
23310 }
23311 })
23312 }
23313 }
23314
23315 func TestValidateHasLabel(t *testing.T) {
23316 successCase := metav1.ObjectMeta{
23317 Name: "123",
23318 Namespace: "ns",
23319 Labels: map[string]string{
23320 "other": "blah",
23321 "foo": "bar",
23322 },
23323 }
23324 if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 {
23325 t.Errorf("expected success: %v", errs)
23326 }
23327
23328 missingCase := metav1.ObjectMeta{
23329 Name: "123",
23330 Namespace: "ns",
23331 Labels: map[string]string{
23332 "other": "blah",
23333 },
23334 }
23335 if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
23336 t.Errorf("expected failure")
23337 }
23338
23339 wrongValueCase := metav1.ObjectMeta{
23340 Name: "123",
23341 Namespace: "ns",
23342 Labels: map[string]string{
23343 "other": "blah",
23344 "foo": "notbar",
23345 },
23346 }
23347 if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
23348 t.Errorf("expected failure")
23349 }
23350 }
23351
23352 func TestIsValidSysctlName(t *testing.T) {
23353 valid := []string{
23354 "a.b.c.d",
23355 "a",
23356 "a_b",
23357 "a-b",
23358 "abc",
23359 "abc.def",
23360 "a/b/c/d",
23361 "a/b.c",
23362 }
23363 invalid := []string{
23364 "",
23365 "*",
23366 "ä",
23367 "a_",
23368 "_",
23369 "__",
23370 "_a",
23371 "_a._b",
23372 "-",
23373 ".",
23374 "a.",
23375 ".a",
23376 "a.b.",
23377 "a*.b",
23378 "a*b",
23379 "*a",
23380 "a.*",
23381 "*",
23382 "abc*",
23383 "a.abc*",
23384 "a.b.*",
23385 "Abc",
23386 "/",
23387 "/a",
23388 "a/abc*",
23389 "a/b/*",
23390 func(n int) string {
23391 x := make([]byte, n)
23392 for i := range x {
23393 x[i] = byte('a')
23394 }
23395 return string(x)
23396 }(256),
23397 }
23398
23399 for _, s := range valid {
23400 if !IsValidSysctlName(s) {
23401 t.Errorf("%q expected to be a valid sysctl name", s)
23402 }
23403 }
23404 for _, s := range invalid {
23405 if IsValidSysctlName(s) {
23406 t.Errorf("%q expected to be an invalid sysctl name", s)
23407 }
23408 }
23409 }
23410
23411 func TestValidateSysctls(t *testing.T) {
23412 valid := []string{
23413 "net.foo.bar",
23414 "kernel.shmmax",
23415 "net.ipv4.conf.enp3s0/200.forwarding",
23416 "net/ipv4/conf/enp3s0.200/forwarding",
23417 }
23418 invalid := []string{
23419 "i..nvalid",
23420 "_invalid",
23421 }
23422
23423 invalidWithHostNet := []string{
23424 "net.ipv4.conf.enp3s0/200.forwarding",
23425 "net/ipv4/conf/enp3s0.200/forwarding",
23426 }
23427
23428 invalidWithHostIPC := []string{
23429 "kernel.shmmax",
23430 "kernel.msgmax",
23431 }
23432
23433 duplicates := []string{
23434 "kernel.shmmax",
23435 "kernel.shmmax",
23436 }
23437 opts := PodValidationOptions{
23438 AllowNamespacedSysctlsForHostNetAndHostIPC: false,
23439 }
23440
23441 sysctls := make([]core.Sysctl, len(valid))
23442 validSecurityContext := &core.PodSecurityContext{
23443 Sysctls: sysctls,
23444 }
23445 for i, sysctl := range valid {
23446 sysctls[i].Name = sysctl
23447 }
23448 errs := validateSysctls(validSecurityContext, field.NewPath("foo"), opts)
23449 if len(errs) != 0 {
23450 t.Errorf("unexpected validation errors: %v", errs)
23451 }
23452
23453 sysctls = make([]core.Sysctl, len(invalid))
23454 for i, sysctl := range invalid {
23455 sysctls[i].Name = sysctl
23456 }
23457 inValidSecurityContext := &core.PodSecurityContext{
23458 Sysctls: sysctls,
23459 }
23460 errs = validateSysctls(inValidSecurityContext, field.NewPath("foo"), opts)
23461 if len(errs) != 2 {
23462 t.Errorf("expected 2 validation errors. Got: %v", errs)
23463 } else {
23464 if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) {
23465 t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
23466 }
23467 if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) {
23468 t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
23469 }
23470 }
23471
23472 sysctls = make([]core.Sysctl, len(duplicates))
23473 for i, sysctl := range duplicates {
23474 sysctls[i].Name = sysctl
23475 }
23476 securityContextWithDup := &core.PodSecurityContext{
23477 Sysctls: sysctls,
23478 }
23479 errs = validateSysctls(securityContextWithDup, field.NewPath("foo"), opts)
23480 if len(errs) != 1 {
23481 t.Errorf("unexpected validation errors: %v", errs)
23482 } else if errs[0].Type != field.ErrorTypeDuplicate {
23483 t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
23484 }
23485
23486 sysctls = make([]core.Sysctl, len(invalidWithHostNet))
23487 for i, sysctl := range invalidWithHostNet {
23488 sysctls[i].Name = sysctl
23489 }
23490 invalidSecurityContextWithHostNet := &core.PodSecurityContext{
23491 Sysctls: sysctls,
23492 HostIPC: false,
23493 HostNetwork: true,
23494 }
23495 errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
23496 if len(errs) != 2 {
23497 t.Errorf("unexpected validation errors: %v", errs)
23498 }
23499 opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
23500 errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
23501 if len(errs) != 0 {
23502 t.Errorf("unexpected validation errors: %v", errs)
23503 }
23504
23505 sysctls = make([]core.Sysctl, len(invalidWithHostIPC))
23506 for i, sysctl := range invalidWithHostIPC {
23507 sysctls[i].Name = sysctl
23508 }
23509 invalidSecurityContextWithHostIPC := &core.PodSecurityContext{
23510 Sysctls: sysctls,
23511 HostIPC: true,
23512 HostNetwork: false,
23513 }
23514 opts.AllowNamespacedSysctlsForHostNetAndHostIPC = false
23515 errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
23516 if len(errs) != 2 {
23517 t.Errorf("unexpected validation errors: %v", errs)
23518 }
23519 opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
23520 errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
23521 if len(errs) != 0 {
23522 t.Errorf("unexpected validation errors: %v", errs)
23523 }
23524 }
23525
23526 func newNodeNameEndpoint(nodeName string) *core.Endpoints {
23527 ep := &core.Endpoints{
23528 ObjectMeta: metav1.ObjectMeta{
23529 Name: "foo",
23530 Namespace: metav1.NamespaceDefault,
23531 ResourceVersion: "1",
23532 },
23533 Subsets: []core.EndpointSubset{{
23534 NotReadyAddresses: []core.EndpointAddress{},
23535 Ports: []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}},
23536 Addresses: []core.EndpointAddress{{
23537 IP: "8.8.8.8",
23538 Hostname: "zookeeper1",
23539 NodeName: &nodeName}}}}}
23540 return ep
23541 }
23542
23543 func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
23544 oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend")
23545 updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename")
23546
23547
23548 errList := ValidateEndpoints(updatedEndpoint)
23549 errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...)
23550 if len(errList) != 0 {
23551 t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update")
23552 }
23553 }
23554
23555 func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
23556
23557 endpoint := newNodeNameEndpoint("illegal*.nodename")
23558 errList := ValidateEndpoints(endpoint)
23559 if len(errList) == 0 {
23560 t.Error("Endpoint should reject invalid NodeName")
23561 }
23562 }
23563
23564 func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
23565 endpoint := newNodeNameEndpoint("10.10.1.1")
23566 errList := ValidateEndpoints(endpoint)
23567 if len(errList) != 0 {
23568 t.Error("Endpoint should accept a NodeName that is an IP address")
23569 }
23570 }
23571
23572 func TestValidateFlexVolumeSource(t *testing.T) {
23573 testcases := map[string]struct {
23574 source *core.FlexVolumeSource
23575 expectedErrs map[string]string
23576 }{
23577 "valid": {
23578 source: &core.FlexVolumeSource{Driver: "foo"},
23579 expectedErrs: map[string]string{},
23580 },
23581 "valid with options": {
23582 source: &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}},
23583 expectedErrs: map[string]string{},
23584 },
23585 "no driver": {
23586 source: &core.FlexVolumeSource{Driver: ""},
23587 expectedErrs: map[string]string{"driver": "Required value"},
23588 },
23589 "reserved option keys": {
23590 source: &core.FlexVolumeSource{
23591 Driver: "foo",
23592 Options: map[string]string{
23593
23594 "myns.io": "A",
23595 "myns.io/bar": "A",
23596 "myns.io/kubernetes.io": "A",
23597
23598
23599 "KUBERNETES.IO": "A",
23600 "kubernetes.io": "A",
23601 "kubernetes.io/": "A",
23602 "kubernetes.io/foo": "A",
23603
23604 "alpha.kubernetes.io": "A",
23605 "alpha.kubernetes.io/": "A",
23606 "alpha.kubernetes.io/foo": "A",
23607
23608 "k8s.io": "A",
23609 "k8s.io/": "A",
23610 "k8s.io/foo": "A",
23611
23612 "alpha.k8s.io": "A",
23613 "alpha.k8s.io/": "A",
23614 "alpha.k8s.io/foo": "A",
23615 },
23616 },
23617 expectedErrs: map[string]string{
23618 "options[KUBERNETES.IO]": "reserved",
23619 "options[kubernetes.io]": "reserved",
23620 "options[kubernetes.io/]": "reserved",
23621 "options[kubernetes.io/foo]": "reserved",
23622 "options[alpha.kubernetes.io]": "reserved",
23623 "options[alpha.kubernetes.io/]": "reserved",
23624 "options[alpha.kubernetes.io/foo]": "reserved",
23625 "options[k8s.io]": "reserved",
23626 "options[k8s.io/]": "reserved",
23627 "options[k8s.io/foo]": "reserved",
23628 "options[alpha.k8s.io]": "reserved",
23629 "options[alpha.k8s.io/]": "reserved",
23630 "options[alpha.k8s.io/foo]": "reserved",
23631 },
23632 },
23633 }
23634
23635 for k, tc := range testcases {
23636 errs := validateFlexVolumeSource(tc.source, nil)
23637 for _, err := range errs {
23638 expectedErr, ok := tc.expectedErrs[err.Field]
23639 if !ok {
23640 t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err)
23641 continue
23642 }
23643 if !strings.Contains(err.Error(), expectedErr) {
23644 t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error())
23645 continue
23646 }
23647 }
23648 if len(errs) != len(tc.expectedErrs) {
23649 t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs)
23650 continue
23651 }
23652 }
23653 }
23654
23655 func TestValidateOrSetClientIPAffinityConfig(t *testing.T) {
23656 successCases := map[string]*core.SessionAffinityConfig{
23657 "non-empty config, valid timeout: 1": {
23658 ClientIP: &core.ClientIPConfig{
23659 TimeoutSeconds: utilpointer.Int32(1),
23660 },
23661 },
23662 "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": {
23663 ClientIP: &core.ClientIPConfig{
23664 TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds - 1),
23665 },
23666 },
23667 "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": {
23668 ClientIP: &core.ClientIPConfig{
23669 TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds),
23670 },
23671 },
23672 }
23673
23674 for name, test := range successCases {
23675 if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 {
23676 t.Errorf("case: %s, expected success: %v", name, errs)
23677 }
23678 }
23679
23680 errorCases := map[string]*core.SessionAffinityConfig{
23681 "empty session affinity config": nil,
23682 "empty client IP config": {
23683 ClientIP: nil,
23684 },
23685 "empty timeoutSeconds": {
23686 ClientIP: &core.ClientIPConfig{
23687 TimeoutSeconds: nil,
23688 },
23689 },
23690 "non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": {
23691 ClientIP: &core.ClientIPConfig{
23692 TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds + 1),
23693 },
23694 },
23695 "non-empty config, invalid timeout: -1": {
23696 ClientIP: &core.ClientIPConfig{
23697 TimeoutSeconds: utilpointer.Int32(-1),
23698 },
23699 },
23700 "non-empty config, invalid timeout: 0": {
23701 ClientIP: &core.ClientIPConfig{
23702 TimeoutSeconds: utilpointer.Int32(0),
23703 },
23704 },
23705 }
23706
23707 for name, test := range errorCases {
23708 if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 {
23709 t.Errorf("case: %v, expected failures: %v", name, errs)
23710 }
23711 }
23712 }
23713
23714 func TestValidateWindowsSecurityContextOptions(t *testing.T) {
23715 toPtr := func(s string) *string {
23716 return &s
23717 }
23718
23719 testCases := []struct {
23720 testName string
23721
23722 windowsOptions *core.WindowsSecurityContextOptions
23723 expectedErrorSubstring string
23724 }{{
23725 testName: "a nil pointer",
23726 }, {
23727 testName: "an empty struct",
23728 windowsOptions: &core.WindowsSecurityContextOptions{},
23729 }, {
23730 testName: "a valid input",
23731 windowsOptions: &core.WindowsSecurityContextOptions{
23732 GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"),
23733 GMSACredentialSpec: toPtr("dummy-gmsa-crep-spec-contents"),
23734 },
23735 }, {
23736 testName: "a GMSA cred spec name that is not a valid resource name",
23737 windowsOptions: &core.WindowsSecurityContextOptions{
23738
23739 GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"),
23740 },
23741 expectedErrorSubstring: dnsSubdomainLabelErrMsg,
23742 }, {
23743 testName: "empty GMSA cred spec contents",
23744 windowsOptions: &core.WindowsSecurityContextOptions{
23745 GMSACredentialSpec: toPtr(""),
23746 },
23747 expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string",
23748 }, {
23749 testName: "GMSA cred spec contents that are too long",
23750 windowsOptions: &core.WindowsSecurityContextOptions{
23751 GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)),
23752 },
23753 expectedErrorSubstring: "gmsaCredentialSpec size must be under",
23754 }, {
23755 testName: "RunAsUserName is nil",
23756 windowsOptions: &core.WindowsSecurityContextOptions{
23757 RunAsUserName: nil,
23758 },
23759 }, {
23760 testName: "a valid RunAsUserName",
23761 windowsOptions: &core.WindowsSecurityContextOptions{
23762 RunAsUserName: toPtr("Container. User"),
23763 },
23764 }, {
23765 testName: "a valid RunAsUserName with NetBios Domain",
23766 windowsOptions: &core.WindowsSecurityContextOptions{
23767 RunAsUserName: toPtr("Network Service\\Container. User"),
23768 },
23769 }, {
23770 testName: "a valid RunAsUserName with DNS Domain",
23771 windowsOptions: &core.WindowsSecurityContextOptions{
23772 RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"),
23773 },
23774 }, {
23775 testName: "a valid RunAsUserName with DNS Domain with a single character segment",
23776 windowsOptions: &core.WindowsSecurityContextOptions{
23777 RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"),
23778 },
23779 }, {
23780 testName: "a valid RunAsUserName with a long single segment DNS Domain",
23781 windowsOptions: &core.WindowsSecurityContextOptions{
23782 RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"),
23783 },
23784 }, {
23785 testName: "an empty RunAsUserName",
23786 windowsOptions: &core.WindowsSecurityContextOptions{
23787 RunAsUserName: toPtr(""),
23788 },
23789 expectedErrorSubstring: "runAsUserName cannot be an empty string",
23790 }, {
23791 testName: "RunAsUserName containing a control character",
23792 windowsOptions: &core.WindowsSecurityContextOptions{
23793 RunAsUserName: toPtr("Container\tUser"),
23794 },
23795 expectedErrorSubstring: "runAsUserName cannot contain control characters",
23796 }, {
23797 testName: "RunAsUserName containing too many backslashes",
23798 windowsOptions: &core.WindowsSecurityContextOptions{
23799 RunAsUserName: toPtr("Container\\Foo\\Lish"),
23800 },
23801 expectedErrorSubstring: "runAsUserName cannot contain more than one backslash",
23802 }, {
23803 testName: "RunAsUserName containing backslash but empty Domain",
23804 windowsOptions: &core.WindowsSecurityContextOptions{
23805 RunAsUserName: toPtr("\\User"),
23806 },
23807 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
23808 }, {
23809 testName: "RunAsUserName containing backslash but empty User",
23810 windowsOptions: &core.WindowsSecurityContextOptions{
23811 RunAsUserName: toPtr("Container\\"),
23812 },
23813 expectedErrorSubstring: "runAsUserName's User cannot be empty",
23814 }, {
23815 testName: "RunAsUserName's NetBios Domain is too long",
23816 windowsOptions: &core.WindowsSecurityContextOptions{
23817 RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"),
23818 },
23819 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
23820 }, {
23821 testName: "RunAsUserName's DNS Domain is too long",
23822 windowsOptions: &core.WindowsSecurityContextOptions{
23823
23824 RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"),
23825 },
23826 expectedErrorSubstring: "runAsUserName's Domain length must be under",
23827 }, {
23828 testName: "RunAsUserName's User is too long",
23829 windowsOptions: &core.WindowsSecurityContextOptions{
23830 RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)),
23831 },
23832 expectedErrorSubstring: "runAsUserName's User length must not be longer than",
23833 }, {
23834 testName: "RunAsUserName's User cannot contain only spaces or periods",
23835 windowsOptions: &core.WindowsSecurityContextOptions{
23836 RunAsUserName: toPtr("... ..."),
23837 },
23838 expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces",
23839 }, {
23840 testName: "RunAsUserName's NetBios Domain cannot start with a dot",
23841 windowsOptions: &core.WindowsSecurityContextOptions{
23842 RunAsUserName: toPtr(".FooLish\\User"),
23843 },
23844 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
23845 }, {
23846 testName: "RunAsUserName's NetBios Domain cannot contain invalid characters",
23847 windowsOptions: &core.WindowsSecurityContextOptions{
23848 RunAsUserName: toPtr("Foo? Lish?\\User"),
23849 },
23850 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
23851 }, {
23852 testName: "RunAsUserName's DNS Domain cannot contain invalid characters",
23853 windowsOptions: &core.WindowsSecurityContextOptions{
23854 RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"),
23855 },
23856 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
23857 }, {
23858 testName: "RunAsUserName's User cannot contain invalid characters",
23859 windowsOptions: &core.WindowsSecurityContextOptions{
23860 RunAsUserName: toPtr("Container/User"),
23861 },
23862 expectedErrorSubstring: "runAsUserName's User cannot contain the following characters",
23863 },
23864 }
23865
23866 for _, testCase := range testCases {
23867 t.Run("validateWindowsSecurityContextOptions with"+testCase.testName, func(t *testing.T) {
23868 errs := validateWindowsSecurityContextOptions(testCase.windowsOptions, field.NewPath("field"))
23869
23870 switch len(errs) {
23871 case 0:
23872 if testCase.expectedErrorSubstring != "" {
23873 t.Errorf("expected a failure containing the substring: %q", testCase.expectedErrorSubstring)
23874 }
23875 case 1:
23876 if testCase.expectedErrorSubstring == "" {
23877 t.Errorf("didn't expect a failure, got: %q", errs[0].Error())
23878 } else if !strings.Contains(errs[0].Error(), testCase.expectedErrorSubstring) {
23879 t.Errorf("expected a failure with the substring %q, got %q instead", testCase.expectedErrorSubstring, errs[0].Error())
23880 }
23881 default:
23882 t.Errorf("got %d failures", len(errs))
23883 for i, err := range errs {
23884 t.Errorf("error %d: %q", i, err.Error())
23885 }
23886 }
23887 })
23888 }
23889 }
23890
23891 func testDataSourceInSpec(name, kind, apiGroup string) *core.PersistentVolumeClaimSpec {
23892 scName := "csi-plugin"
23893 dataSourceInSpec := core.PersistentVolumeClaimSpec{
23894 AccessModes: []core.PersistentVolumeAccessMode{
23895 core.ReadOnlyMany,
23896 },
23897 Resources: core.VolumeResourceRequirements{
23898 Requests: core.ResourceList{
23899 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
23900 },
23901 },
23902 StorageClassName: &scName,
23903 DataSource: &core.TypedLocalObjectReference{
23904 APIGroup: &apiGroup,
23905 Kind: kind,
23906 Name: name,
23907 },
23908 }
23909
23910 return &dataSourceInSpec
23911 }
23912
23913 func TestAlphaVolumePVCDataSource(t *testing.T) {
23914 testCases := []struct {
23915 testName string
23916 claimSpec core.PersistentVolumeClaimSpec
23917 expectedFail bool
23918 }{{
23919 testName: "test create from valid snapshot source",
23920 claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
23921 }, {
23922 testName: "test create from valid pvc source",
23923 claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
23924 }, {
23925 testName: "test missing name in snapshot datasource should fail",
23926 claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
23927 expectedFail: true,
23928 }, {
23929 testName: "test missing kind in snapshot datasource should fail",
23930 claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
23931 expectedFail: true,
23932 }, {
23933 testName: "test create from valid generic custom resource source",
23934 claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
23935 }, {
23936 testName: "test invalid datasource should fail",
23937 claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""),
23938 expectedFail: true,
23939 },
23940 }
23941
23942 for _, tc := range testCases {
23943 opts := PersistentVolumeClaimSpecValidationOptions{}
23944 if tc.expectedFail {
23945 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
23946 t.Errorf("expected failure: %v", errs)
23947 }
23948
23949 } else {
23950 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
23951 t.Errorf("expected success: %v", errs)
23952 }
23953 }
23954 }
23955 }
23956
23957 func testAnyDataSource(t *testing.T, ds, dsRef bool) {
23958 testCases := []struct {
23959 testName string
23960 claimSpec core.PersistentVolumeClaimSpec
23961 expectedFail bool
23962 }{{
23963 testName: "test create from valid snapshot source",
23964 claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
23965 }, {
23966 testName: "test create from valid pvc source",
23967 claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
23968 }, {
23969 testName: "test missing name in snapshot datasource should fail",
23970 claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
23971 expectedFail: true,
23972 }, {
23973 testName: "test missing kind in snapshot datasource should fail",
23974 claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
23975 expectedFail: true,
23976 }, {
23977 testName: "test create from valid generic custom resource source",
23978 claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
23979 }, {
23980 testName: "test invalid datasource should fail",
23981 claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""),
23982 expectedFail: true,
23983 },
23984 }
23985
23986 for _, tc := range testCases {
23987 if dsRef {
23988 tc.claimSpec.DataSourceRef = &core.TypedObjectReference{
23989 APIGroup: tc.claimSpec.DataSource.APIGroup,
23990 Kind: tc.claimSpec.DataSource.Kind,
23991 Name: tc.claimSpec.DataSource.Name,
23992 }
23993 }
23994 if !ds {
23995 tc.claimSpec.DataSource = nil
23996 }
23997 opts := PersistentVolumeClaimSpecValidationOptions{}
23998 if tc.expectedFail {
23999 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
24000 t.Errorf("expected failure: %v", errs)
24001 }
24002 } else {
24003 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
24004 t.Errorf("expected success: %v", errs)
24005 }
24006 }
24007 }
24008 }
24009
24010 func TestAnyDataSource(t *testing.T) {
24011 testAnyDataSource(t, true, false)
24012 testAnyDataSource(t, false, true)
24013 testAnyDataSource(t, true, false)
24014 }
24015
24016 func pvcSpecWithCrossNamespaceSource(apiGroup *string, kind string, namespace *string, name string, isDataSourceSet bool) *core.PersistentVolumeClaimSpec {
24017 scName := "csi-plugin"
24018 spec := core.PersistentVolumeClaimSpec{
24019 AccessModes: []core.PersistentVolumeAccessMode{
24020 core.ReadOnlyMany,
24021 },
24022 Resources: core.VolumeResourceRequirements{
24023 Requests: core.ResourceList{
24024 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
24025 },
24026 },
24027 StorageClassName: &scName,
24028 DataSourceRef: &core.TypedObjectReference{
24029 APIGroup: apiGroup,
24030 Kind: kind,
24031 Namespace: namespace,
24032 Name: name,
24033 },
24034 }
24035
24036 if isDataSourceSet {
24037 spec.DataSource = &core.TypedLocalObjectReference{
24038 APIGroup: apiGroup,
24039 Kind: kind,
24040 Name: name,
24041 }
24042 }
24043 return &spec
24044 }
24045
24046 func TestCrossNamespaceSource(t *testing.T) {
24047 snapAPIGroup := "snapshot.storage.k8s.io"
24048 coreAPIGroup := ""
24049 unsupportedAPIGroup := "unsupported.example.com"
24050 snapKind := "VolumeSnapshot"
24051 pvcKind := "PersistentVolumeClaim"
24052 goodNS := "ns1"
24053 badNS := "a*b"
24054 emptyNS := ""
24055 goodName := "snapshot1"
24056
24057 testCases := []struct {
24058 testName string
24059 expectedFail bool
24060 claimSpec *core.PersistentVolumeClaimSpec
24061 }{{
24062 testName: "Feature gate enabled and valid xns DataSourceRef specified",
24063 expectedFail: false,
24064 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false),
24065 }, {
24066 testName: "Feature gate enabled and xns DataSourceRef with PVC source specified",
24067 expectedFail: false,
24068 claimSpec: pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false),
24069 }, {
24070 testName: "Feature gate enabled and xns DataSourceRef with unsupported source specified",
24071 expectedFail: false,
24072 claimSpec: pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false),
24073 }, {
24074 testName: "Feature gate enabled and xns DataSourceRef with nil apiGroup",
24075 expectedFail: true,
24076 claimSpec: pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false),
24077 }, {
24078 testName: "Feature gate enabled and xns DataSourceRef with invalid namspace specified",
24079 expectedFail: true,
24080 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false),
24081 }, {
24082 testName: "Feature gate enabled and xns DataSourceRef with nil namspace specified",
24083 expectedFail: false,
24084 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false),
24085 }, {
24086 testName: "Feature gate enabled and xns DataSourceRef with empty namspace specified",
24087 expectedFail: false,
24088 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false),
24089 }, {
24090 testName: "Feature gate enabled and both xns DataSourceRef and DataSource specified",
24091 expectedFail: true,
24092 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true),
24093 },
24094 }
24095
24096 for _, tc := range testCases {
24097 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)()
24098 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)()
24099 opts := PersistentVolumeClaimSpecValidationOptions{}
24100 if tc.expectedFail {
24101 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
24102 t.Errorf("%s: expected failure: %v", tc.testName, errs)
24103 }
24104 } else {
24105 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
24106 t.Errorf("%s: expected success: %v", tc.testName, errs)
24107 }
24108 }
24109 }
24110 }
24111
24112 func pvcSpecWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimSpec {
24113 scName := "csi-plugin"
24114 spec := core.PersistentVolumeClaimSpec{
24115 AccessModes: []core.PersistentVolumeAccessMode{
24116 core.ReadOnlyMany,
24117 },
24118 Resources: core.VolumeResourceRequirements{
24119 Requests: core.ResourceList{
24120 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
24121 },
24122 },
24123 StorageClassName: &scName,
24124 VolumeAttributesClassName: vacName,
24125 }
24126 return &spec
24127 }
24128
24129 func TestVolumeAttributesClass(t *testing.T) {
24130 testCases := []struct {
24131 testName string
24132 expectedFail bool
24133 enableVolumeAttributesClass bool
24134 claimSpec *core.PersistentVolumeClaimSpec
24135 }{
24136 {
24137 testName: "Feature gate enabled and valid no volumeAttributesClassName specified",
24138 expectedFail: false,
24139 enableVolumeAttributesClass: true,
24140 claimSpec: pvcSpecWithVolumeAttributesClassName(nil),
24141 },
24142 {
24143 testName: "Feature gate enabled and an empty volumeAttributesClassName specified",
24144 expectedFail: false,
24145 enableVolumeAttributesClass: true,
24146 claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("")),
24147 },
24148 {
24149 testName: "Feature gate enabled and valid volumeAttributesClassName specified",
24150 expectedFail: false,
24151 enableVolumeAttributesClass: true,
24152 claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("foo")),
24153 },
24154 {
24155 testName: "Feature gate enabled and invalid volumeAttributesClassName specified",
24156 expectedFail: true,
24157 enableVolumeAttributesClass: true,
24158 claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("-invalid-")),
24159 },
24160 }
24161 for _, tc := range testCases {
24162 opts := PersistentVolumeClaimSpecValidationOptions{
24163 EnableVolumeAttributesClass: tc.enableVolumeAttributesClass,
24164 }
24165 if tc.expectedFail {
24166 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
24167 t.Errorf("%s: expected failure: %v", tc.testName, errs)
24168 }
24169 } else {
24170 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
24171 t.Errorf("%s: expected success: %v", tc.testName, errs)
24172 }
24173 }
24174 }
24175 }
24176
24177 func TestValidateTopologySpreadConstraints(t *testing.T) {
24178 fieldPath := field.NewPath("field")
24179 subFldPath0 := fieldPath.Index(0)
24180 fieldPathMinDomains := subFldPath0.Child("minDomains")
24181 fieldPathMaxSkew := subFldPath0.Child("maxSkew")
24182 fieldPathTopologyKey := subFldPath0.Child("topologyKey")
24183 fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable")
24184 fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}")
24185 fieldPathMatchLabelKeys := subFldPath0.Child("matchLabelKeys")
24186 nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy")
24187 nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy")
24188 labelSelectorField := subFldPath0.Child("labelSelector")
24189 unknown := core.NodeInclusionPolicy("Unknown")
24190 ignore := core.NodeInclusionPolicyIgnore
24191 honor := core.NodeInclusionPolicyHonor
24192
24193 testCases := []struct {
24194 name string
24195 constraints []core.TopologySpreadConstraint
24196 wantFieldErrors field.ErrorList
24197 opts PodValidationOptions
24198 }{{
24199 name: "all required fields ok",
24200 constraints: []core.TopologySpreadConstraint{{
24201 MaxSkew: 1,
24202 TopologyKey: "k8s.io/zone",
24203 WhenUnsatisfiable: core.DoNotSchedule,
24204 MinDomains: utilpointer.Int32(3),
24205 }},
24206 wantFieldErrors: field.ErrorList{},
24207 }, {
24208 name: "missing MaxSkew",
24209 constraints: []core.TopologySpreadConstraint{
24210 {TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
24211 },
24212 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)},
24213 }, {
24214 name: "negative MaxSkew",
24215 constraints: []core.TopologySpreadConstraint{
24216 {MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
24217 },
24218 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)},
24219 }, {
24220 name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil",
24221 constraints: []core.TopologySpreadConstraint{{
24222 MaxSkew: 1,
24223 TopologyKey: "k8s.io/zone",
24224 WhenUnsatisfiable: core.ScheduleAnyway,
24225 MinDomains: nil,
24226 }},
24227 wantFieldErrors: field.ErrorList{},
24228 }, {
24229 name: "negative minDomains is invalid",
24230 constraints: []core.TopologySpreadConstraint{{
24231 MaxSkew: 1,
24232 TopologyKey: "k8s.io/zone",
24233 WhenUnsatisfiable: core.DoNotSchedule,
24234 MinDomains: utilpointer.Int32(-1),
24235 }},
24236 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)},
24237 }, {
24238 name: "cannot use non-nil MinDomains with ScheduleAnyway",
24239 constraints: []core.TopologySpreadConstraint{{
24240 MaxSkew: 1,
24241 TopologyKey: "k8s.io/zone",
24242 WhenUnsatisfiable: core.ScheduleAnyway,
24243 MinDomains: utilpointer.Int32(10),
24244 }},
24245 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(10), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway)))},
24246 }, {
24247 name: "use negative MinDomains with ScheduleAnyway(invalid)",
24248 constraints: []core.TopologySpreadConstraint{{
24249 MaxSkew: 1,
24250 TopologyKey: "k8s.io/zone",
24251 WhenUnsatisfiable: core.ScheduleAnyway,
24252 MinDomains: utilpointer.Int32(-1),
24253 }},
24254 wantFieldErrors: []*field.Error{
24255 field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg),
24256 field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))),
24257 },
24258 }, {
24259 name: "missing TopologyKey",
24260 constraints: []core.TopologySpreadConstraint{
24261 {MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule},
24262 },
24263 wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
24264 }, {
24265 name: "missing scheduling mode",
24266 constraints: []core.TopologySpreadConstraint{
24267 {MaxSkew: 1, TopologyKey: "k8s.io/zone"},
24268 },
24269 wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), sets.List(supportedScheduleActions))},
24270 }, {
24271 name: "unsupported scheduling mode",
24272 constraints: []core.TopologySpreadConstraint{
24273 {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")},
24274 },
24275 wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), sets.List(supportedScheduleActions))},
24276 }, {
24277 name: "multiple constraints ok with all required fields",
24278 constraints: []core.TopologySpreadConstraint{
24279 {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
24280 {MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway},
24281 },
24282 wantFieldErrors: field.ErrorList{},
24283 }, {
24284 name: "multiple constraints missing TopologyKey on partial ones",
24285 constraints: []core.TopologySpreadConstraint{
24286 {MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway},
24287 {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
24288 },
24289 wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
24290 }, {
24291 name: "duplicate constraints",
24292 constraints: []core.TopologySpreadConstraint{
24293 {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
24294 {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
24295 },
24296 wantFieldErrors: []*field.Error{
24297 field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)),
24298 },
24299 }, {
24300 name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy",
24301 constraints: []core.TopologySpreadConstraint{{
24302 MaxSkew: 1,
24303 TopologyKey: "k8s.io/zone",
24304 WhenUnsatisfiable: core.DoNotSchedule,
24305 NodeAffinityPolicy: &honor,
24306 NodeTaintsPolicy: &ignore,
24307 }},
24308 wantFieldErrors: []*field.Error{},
24309 }, {
24310 name: "unsupported policy name set on NodeAffinityPolicy",
24311 constraints: []core.TopologySpreadConstraint{{
24312 MaxSkew: 1,
24313 TopologyKey: "k8s.io/zone",
24314 WhenUnsatisfiable: core.DoNotSchedule,
24315 NodeAffinityPolicy: &unknown,
24316 NodeTaintsPolicy: &ignore,
24317 }},
24318 wantFieldErrors: []*field.Error{
24319 field.NotSupported(nodeAffinityField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
24320 },
24321 }, {
24322 name: "unsupported policy name set on NodeTaintsPolicy",
24323 constraints: []core.TopologySpreadConstraint{{
24324 MaxSkew: 1,
24325 TopologyKey: "k8s.io/zone",
24326 WhenUnsatisfiable: core.DoNotSchedule,
24327 NodeAffinityPolicy: &honor,
24328 NodeTaintsPolicy: &unknown,
24329 }},
24330 wantFieldErrors: []*field.Error{
24331 field.NotSupported(nodeTaintsField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
24332 },
24333 }, {
24334 name: "key in MatchLabelKeys isn't correctly defined",
24335 constraints: []core.TopologySpreadConstraint{{
24336 MaxSkew: 1,
24337 TopologyKey: "k8s.io/zone",
24338 LabelSelector: &metav1.LabelSelector{},
24339 WhenUnsatisfiable: core.DoNotSchedule,
24340 MatchLabelKeys: []string{"/simple"},
24341 }},
24342 wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")},
24343 }, {
24344 name: "key exists in both matchLabelKeys and labelSelector",
24345 constraints: []core.TopologySpreadConstraint{{
24346 MaxSkew: 1,
24347 TopologyKey: "k8s.io/zone",
24348 WhenUnsatisfiable: core.DoNotSchedule,
24349 MatchLabelKeys: []string{"foo"},
24350 LabelSelector: &metav1.LabelSelector{
24351 MatchExpressions: []metav1.LabelSelectorRequirement{
24352 {
24353 Key: "foo",
24354 Operator: metav1.LabelSelectorOpNotIn,
24355 Values: []string{"value1", "value2"},
24356 },
24357 },
24358 },
24359 }},
24360 wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")},
24361 }, {
24362 name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set",
24363 constraints: []core.TopologySpreadConstraint{{
24364 MaxSkew: 1,
24365 TopologyKey: "k8s.io/zone",
24366 WhenUnsatisfiable: core.DoNotSchedule,
24367 MatchLabelKeys: []string{"foo"},
24368 }},
24369 wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")},
24370 }, {
24371 name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
24372 constraints: []core.TopologySpreadConstraint{{
24373 MaxSkew: 1,
24374 TopologyKey: "k8s.io/zone",
24375 WhenUnsatisfiable: core.DoNotSchedule,
24376 MinDomains: nil,
24377 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
24378 }},
24379 wantFieldErrors: []*field.Error{field.Invalid(labelSelectorField.Child("matchLabels"), "NoUppercaseOrSpecialCharsLike=Equals", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")},
24380 opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
24381 }, {
24382 name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true",
24383 constraints: []core.TopologySpreadConstraint{{
24384 MaxSkew: 1,
24385 TopologyKey: "k8s.io/zone",
24386 WhenUnsatisfiable: core.DoNotSchedule,
24387 MinDomains: nil,
24388 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
24389 }},
24390 wantFieldErrors: []*field.Error{},
24391 opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true},
24392 }, {
24393 name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
24394 constraints: []core.TopologySpreadConstraint{{
24395 MaxSkew: 1,
24396 TopologyKey: "k8s.io/zone",
24397 WhenUnsatisfiable: core.DoNotSchedule,
24398 MinDomains: nil,
24399 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}},
24400 }},
24401 wantFieldErrors: []*field.Error{},
24402 opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
24403 },
24404 }
24405
24406 for _, tc := range testCases {
24407 t.Run(tc.name, func(t *testing.T) {
24408 errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts)
24409 if diff := cmp.Diff(tc.wantFieldErrors, errs); diff != "" {
24410 t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
24411 }
24412 })
24413 }
24414 }
24415
24416 func TestValidateOverhead(t *testing.T) {
24417 successCase := []struct {
24418 Name string
24419 overhead core.ResourceList
24420 }{{
24421 Name: "Valid Overhead for CPU + Memory",
24422 overhead: core.ResourceList{
24423 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
24424 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
24425 },
24426 },
24427 }
24428 for _, tc := range successCase {
24429 if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 {
24430 t.Errorf("%q unexpected error: %v", tc.Name, errs)
24431 }
24432 }
24433
24434 errorCase := []struct {
24435 Name string
24436 overhead core.ResourceList
24437 }{{
24438 Name: "Invalid Overhead Resources",
24439 overhead: core.ResourceList{
24440 core.ResourceName("my.org"): resource.MustParse("10m"),
24441 },
24442 },
24443 }
24444 for _, tc := range errorCase {
24445 if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 {
24446 t.Errorf("%q expected error", tc.Name)
24447 }
24448 }
24449 }
24450
24451
24452 func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod {
24453 return core.Pod{
24454 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
24455 Spec: core.PodSpec{
24456 Containers: []core.Container{{
24457 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
24458 }},
24459 RestartPolicy: core.RestartPolicyAlways,
24460 DNSPolicy: core.DNSClusterFirst,
24461 },
24462 Status: core.PodStatus{
24463 PodIPs: podIPs,
24464 },
24465 }
24466 }
24467 func TestPodIPsValidation(t *testing.T) {
24468 testCases := []struct {
24469 pod core.Pod
24470 expectError bool
24471 }{{
24472 expectError: false,
24473 pod: makePod("nil-ips", "ns", nil),
24474 }, {
24475 expectError: false,
24476 pod: makePod("empty-podips-list", "ns", []core.PodIP{}),
24477 }, {
24478 expectError: false,
24479 pod: makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}),
24480 }, {
24481 expectError: false,
24482 pod: makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}),
24483 }, {
24484 expectError: false,
24485 pod: makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
24486 }, {
24487 expectError: false,
24488 pod: makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
24489 },
24490
24491 {
24492 expectError: true,
24493 pod: makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}),
24494 }, {
24495 expectError: true,
24496 pod: makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}),
24497 }, {
24498 expectError: true,
24499 pod: makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
24500 }, {
24501 expectError: true,
24502 pod: makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
24503 }, {
24504 expectError: true,
24505 pod: makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
24506 },
24507
24508 {
24509 expectError: true,
24510 pod: makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
24511 }, {
24512 expectError: true,
24513 pod: makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
24514 },
24515 }
24516
24517 for _, testCase := range testCases {
24518 t.Run(testCase.pod.Name, func(t *testing.T) {
24519 for _, oldTestCase := range testCases {
24520 newPod := testCase.pod.DeepCopy()
24521 newPod.ResourceVersion = "1"
24522
24523 oldPod := oldTestCase.pod.DeepCopy()
24524 oldPod.ResourceVersion = "1"
24525 oldPod.Name = newPod.Name
24526
24527 errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
24528
24529 if len(errs) == 0 && testCase.expectError {
24530 t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
24531 }
24532 if len(errs) != 0 && !testCase.expectError {
24533 t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
24534 }
24535 }
24536 })
24537 }
24538 }
24539
24540 func makePodWithHostIPs(podName string, podNamespace string, hostIPs []core.HostIP) core.Pod {
24541 hostIP := ""
24542 if len(hostIPs) > 0 {
24543 hostIP = hostIPs[0].IP
24544 }
24545 return core.Pod{
24546 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
24547 Spec: core.PodSpec{
24548 Containers: []core.Container{
24549 {
24550 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
24551 },
24552 },
24553 RestartPolicy: core.RestartPolicyAlways,
24554 DNSPolicy: core.DNSClusterFirst,
24555 },
24556 Status: core.PodStatus{
24557 HostIP: hostIP,
24558 HostIPs: hostIPs,
24559 },
24560 }
24561 }
24562
24563 func TestHostIPsValidation(t *testing.T) {
24564 testCases := []struct {
24565 pod core.Pod
24566 expectError bool
24567 }{
24568 {
24569 expectError: false,
24570 pod: makePodWithHostIPs("nil-ips", "ns", nil),
24571 },
24572 {
24573 expectError: false,
24574 pod: makePodWithHostIPs("empty-HostIPs-list", "ns", []core.HostIP{}),
24575 },
24576 {
24577 expectError: false,
24578 pod: makePodWithHostIPs("single-ip-family-6", "ns", []core.HostIP{{IP: "::1"}}),
24579 },
24580 {
24581 expectError: false,
24582 pod: makePodWithHostIPs("single-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}}),
24583 },
24584 {
24585 expectError: false,
24586 pod: makePodWithHostIPs("dual-stack-4-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
24587 },
24588 {
24589 expectError: false,
24590 pod: makePodWithHostIPs("dual-stack-6-4", "ns", []core.HostIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
24591 },
24592
24593 {
24594 expectError: true,
24595 pod: makePodWithHostIPs("invalid-pod-ip", "ns", []core.HostIP{{IP: "this-is-not-an-ip"}}),
24596 },
24597 {
24598 expectError: true,
24599 pod: makePodWithHostIPs("dualstack-same-ip-family-6", "ns", []core.HostIP{{IP: "::1"}, {IP: "::2"}}),
24600 },
24601 {
24602 expectError: true,
24603 pod: makePodWithHostIPs("dualstack-same-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
24604 },
24605 {
24606 expectError: true,
24607 pod: makePodWithHostIPs("dualstack-repeated-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
24608 },
24609 {
24610 expectError: true,
24611 pod: makePodWithHostIPs("dualstack-repeated-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
24612 },
24613
24614 {
24615 expectError: true,
24616 pod: makePodWithHostIPs("dualstack-duplicate-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
24617 },
24618 {
24619 expectError: true,
24620 pod: makePodWithHostIPs("dualstack-duplicate-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
24621 },
24622 }
24623
24624 for _, testCase := range testCases {
24625 t.Run(testCase.pod.Name, func(t *testing.T) {
24626 for _, oldTestCase := range testCases {
24627 newPod := testCase.pod.DeepCopy()
24628 newPod.ResourceVersion = "1"
24629
24630 oldPod := oldTestCase.pod.DeepCopy()
24631 oldPod.ResourceVersion = "1"
24632 oldPod.Name = newPod.Name
24633
24634 errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
24635
24636 if len(errs) == 0 && testCase.expectError {
24637 t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
24638 }
24639 if len(errs) != 0 && !testCase.expectError {
24640 t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
24641 }
24642 }
24643 })
24644 }
24645 }
24646
24647
24648 func makeNode(nodeName string, podCIDRs []string) core.Node {
24649 return core.Node{
24650 ObjectMeta: metav1.ObjectMeta{
24651 Name: nodeName,
24652 },
24653 Status: core.NodeStatus{
24654 Addresses: []core.NodeAddress{
24655 {Type: core.NodeExternalIP, Address: "something"},
24656 },
24657 Capacity: core.ResourceList{
24658 core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
24659 core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
24660 },
24661 },
24662 Spec: core.NodeSpec{
24663 PodCIDRs: podCIDRs,
24664 },
24665 }
24666 }
24667 func TestValidateNodeCIDRs(t *testing.T) {
24668 testCases := []struct {
24669 expectError bool
24670 node core.Node
24671 }{{
24672 expectError: false,
24673 node: makeNode("nil-pod-cidr", nil),
24674 }, {
24675 expectError: false,
24676 node: makeNode("empty-pod-cidr", []string{}),
24677 }, {
24678 expectError: false,
24679 node: makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}),
24680 }, {
24681 expectError: false,
24682 node: makeNode("single-pod-cidr-6", []string{"2000::/10"}),
24683 },
24684
24685 {
24686 expectError: false,
24687 node: makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}),
24688 }, {
24689 expectError: false,
24690 node: makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}),
24691 },
24692
24693 {
24694 expectError: true,
24695 node: makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}),
24696 }, {
24697 expectError: true,
24698 node: makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}),
24699 }, {
24700 expectError: true,
24701 node: makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}),
24702 }, {
24703 expectError: true,
24704 node: makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}),
24705 }, {
24706 expectError: true,
24707 node: makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}),
24708 }, {
24709 expectError: true,
24710 node: makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}),
24711 }, {
24712 expectError: true,
24713 node: makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}),
24714 },
24715 }
24716 for _, testCase := range testCases {
24717 errs := ValidateNode(&testCase.node)
24718 if len(errs) == 0 && testCase.expectError {
24719 t.Errorf("expected failure for %s, but there were none", testCase.node.Name)
24720 return
24721 }
24722 if len(errs) != 0 && !testCase.expectError {
24723 t.Errorf("expected success for %s, but there were errors: %v", testCase.node.Name, errs)
24724 return
24725 }
24726 }
24727 }
24728
24729 func TestValidateSeccompAnnotationAndField(t *testing.T) {
24730 const containerName = "container"
24731 testProfile := "test"
24732
24733 for _, test := range []struct {
24734 description string
24735 pod *core.Pod
24736 validation func(*testing.T, string, field.ErrorList, *v1.Pod)
24737 }{{
24738 description: "Field type unconfined and annotation does not match",
24739 pod: &core.Pod{
24740 ObjectMeta: metav1.ObjectMeta{
24741 Annotations: map[string]string{
24742 v1.SeccompPodAnnotationKey: "not-matching",
24743 },
24744 },
24745 Spec: core.PodSpec{
24746 SecurityContext: &core.PodSecurityContext{
24747 SeccompProfile: &core.SeccompProfile{
24748 Type: core.SeccompProfileTypeUnconfined,
24749 },
24750 },
24751 },
24752 },
24753 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24754 require.NotNil(t, allErrs, desc)
24755 },
24756 }, {
24757 description: "Field type default and annotation does not match",
24758 pod: &core.Pod{
24759 ObjectMeta: metav1.ObjectMeta{
24760 Annotations: map[string]string{
24761 v1.SeccompPodAnnotationKey: "not-matching",
24762 },
24763 },
24764 Spec: core.PodSpec{
24765 SecurityContext: &core.PodSecurityContext{
24766 SeccompProfile: &core.SeccompProfile{
24767 Type: core.SeccompProfileTypeRuntimeDefault,
24768 },
24769 },
24770 },
24771 },
24772 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24773 require.NotNil(t, allErrs, desc)
24774 },
24775 }, {
24776 description: "Field type localhost and annotation does not match",
24777 pod: &core.Pod{
24778 ObjectMeta: metav1.ObjectMeta{
24779 Annotations: map[string]string{
24780 v1.SeccompPodAnnotationKey: "not-matching",
24781 },
24782 },
24783 Spec: core.PodSpec{
24784 SecurityContext: &core.PodSecurityContext{
24785 SeccompProfile: &core.SeccompProfile{
24786 Type: core.SeccompProfileTypeLocalhost,
24787 LocalhostProfile: &testProfile,
24788 },
24789 },
24790 },
24791 },
24792 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24793 require.NotNil(t, allErrs, desc)
24794 },
24795 }, {
24796 description: "Field type localhost and localhost/ prefixed annotation does not match",
24797 pod: &core.Pod{
24798 ObjectMeta: metav1.ObjectMeta{
24799 Annotations: map[string]string{
24800 v1.SeccompPodAnnotationKey: "localhost/not-matching",
24801 },
24802 },
24803 Spec: core.PodSpec{
24804 SecurityContext: &core.PodSecurityContext{
24805 SeccompProfile: &core.SeccompProfile{
24806 Type: core.SeccompProfileTypeLocalhost,
24807 LocalhostProfile: &testProfile,
24808 },
24809 },
24810 },
24811 },
24812 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24813 require.NotNil(t, allErrs, desc)
24814 },
24815 }, {
24816 description: "Field type unconfined and annotation does not match (container)",
24817 pod: &core.Pod{
24818 ObjectMeta: metav1.ObjectMeta{
24819 Annotations: map[string]string{
24820 v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
24821 },
24822 },
24823 Spec: core.PodSpec{
24824 Containers: []core.Container{{
24825 Name: containerName,
24826 SecurityContext: &core.SecurityContext{
24827 SeccompProfile: &core.SeccompProfile{
24828 Type: core.SeccompProfileTypeUnconfined,
24829 },
24830 },
24831 }},
24832 },
24833 },
24834 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24835 require.NotNil(t, allErrs, desc)
24836 },
24837 }, {
24838 description: "Field type default and annotation does not match (container)",
24839 pod: &core.Pod{
24840 ObjectMeta: metav1.ObjectMeta{
24841 Annotations: map[string]string{
24842 v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
24843 },
24844 },
24845 Spec: core.PodSpec{
24846 Containers: []core.Container{{
24847 Name: containerName,
24848 SecurityContext: &core.SecurityContext{
24849 SeccompProfile: &core.SeccompProfile{
24850 Type: core.SeccompProfileTypeRuntimeDefault,
24851 },
24852 },
24853 }},
24854 },
24855 },
24856 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24857 require.NotNil(t, allErrs, desc)
24858 },
24859 }, {
24860 description: "Field type localhost and annotation does not match (container)",
24861 pod: &core.Pod{
24862 ObjectMeta: metav1.ObjectMeta{
24863 Annotations: map[string]string{
24864 v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
24865 },
24866 },
24867 Spec: core.PodSpec{
24868 Containers: []core.Container{{
24869 Name: containerName,
24870 SecurityContext: &core.SecurityContext{
24871 SeccompProfile: &core.SeccompProfile{
24872 Type: core.SeccompProfileTypeLocalhost,
24873 LocalhostProfile: &testProfile,
24874 },
24875 },
24876 }},
24877 },
24878 },
24879 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24880 require.NotNil(t, allErrs, desc)
24881 },
24882 }, {
24883 description: "Field type localhost and localhost/ prefixed annotation does not match (container)",
24884 pod: &core.Pod{
24885 ObjectMeta: metav1.ObjectMeta{
24886 Annotations: map[string]string{
24887 v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
24888 },
24889 },
24890 Spec: core.PodSpec{
24891 Containers: []core.Container{{
24892 Name: containerName,
24893 SecurityContext: &core.SecurityContext{
24894 SeccompProfile: &core.SeccompProfile{
24895 Type: core.SeccompProfileTypeLocalhost,
24896 LocalhostProfile: &testProfile,
24897 },
24898 },
24899 }},
24900 },
24901 },
24902 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24903 require.NotNil(t, allErrs, desc)
24904 },
24905 }, {
24906 description: "Nil errors must not be appended (pod)",
24907 pod: &core.Pod{
24908 ObjectMeta: metav1.ObjectMeta{
24909 Annotations: map[string]string{
24910 v1.SeccompPodAnnotationKey: "localhost/anyprofile",
24911 },
24912 },
24913 Spec: core.PodSpec{
24914 SecurityContext: &core.PodSecurityContext{
24915 SeccompProfile: &core.SeccompProfile{
24916 Type: "Abc",
24917 },
24918 },
24919 Containers: []core.Container{{
24920 Name: containerName,
24921 }},
24922 },
24923 },
24924 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24925 require.Empty(t, allErrs, desc)
24926 },
24927 }, {
24928 description: "Nil errors must not be appended (container)",
24929 pod: &core.Pod{
24930 ObjectMeta: metav1.ObjectMeta{
24931 Annotations: map[string]string{
24932 v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
24933 },
24934 },
24935 Spec: core.PodSpec{
24936 Containers: []core.Container{{
24937 SecurityContext: &core.SecurityContext{
24938 SeccompProfile: &core.SeccompProfile{
24939 Type: "Abc",
24940 },
24941 },
24942 Name: containerName,
24943 }},
24944 },
24945 },
24946 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
24947 require.Empty(t, allErrs, desc)
24948 },
24949 },
24950 } {
24951 output := &v1.Pod{
24952 ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
24953 }
24954 for i, ctr := range test.pod.Spec.Containers {
24955 output.Spec.Containers = append(output.Spec.Containers, v1.Container{})
24956 if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
24957 output.Spec.Containers[i].SecurityContext = &v1.SecurityContext{
24958 SeccompProfile: &v1.SeccompProfile{
24959 Type: v1.SeccompProfileType(ctr.SecurityContext.SeccompProfile.Type),
24960 LocalhostProfile: ctr.SecurityContext.SeccompProfile.LocalhostProfile,
24961 },
24962 }
24963 }
24964 }
24965 errList := validateSeccompAnnotationsAndFields(test.pod.ObjectMeta, &test.pod.Spec, field.NewPath(""))
24966 test.validation(t, test.description, errList, output)
24967 }
24968 }
24969
24970 func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) {
24971 rootFld := field.NewPath("")
24972 tests := []struct {
24973 description string
24974 annotationValue string
24975 seccompField *core.SeccompProfile
24976 fldPath *field.Path
24977 expectedErr *field.Error
24978 }{{
24979 description: "seccompField nil should return empty",
24980 expectedErr: nil,
24981 }, {
24982 description: "unconfined annotation and SeccompProfileTypeUnconfined should return empty",
24983 annotationValue: "unconfined",
24984 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
24985 expectedErr: nil,
24986 }, {
24987 description: "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
24988 annotationValue: "runtime/default",
24989 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
24990 expectedErr: nil,
24991 }, {
24992 description: "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
24993 annotationValue: "docker/default",
24994 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
24995 expectedErr: nil,
24996 }, {
24997 description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty",
24998 annotationValue: "localhost/test.json",
24999 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")},
25000 expectedErr: nil,
25001 }, {
25002 description: "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error",
25003 annotationValue: "localhost/test.json",
25004 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost},
25005 fldPath: rootFld,
25006 expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
25007 }, {
25008 description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error",
25009 annotationValue: "localhost/test.json",
25010 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")},
25011 fldPath: rootFld,
25012 expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
25013 }, {
25014 description: "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error",
25015 annotationValue: "localhost/test.json",
25016 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
25017 fldPath: rootFld,
25018 expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
25019 }, {
25020 description: "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error",
25021 annotationValue: "localhost/test.json",
25022 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
25023 fldPath: rootFld,
25024 expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
25025 },
25026 }
25027
25028 for i, test := range tests {
25029 err := validateSeccompAnnotationsAndFieldsMatch(test.annotationValue, test.seccompField, test.fldPath)
25030 assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
25031 }
25032 }
25033
25034 func TestValidatePodTemplateSpecSeccomp(t *testing.T) {
25035 rootFld := field.NewPath("template")
25036 tests := []struct {
25037 description string
25038 spec *core.PodTemplateSpec
25039 fldPath *field.Path
25040 expectedErr field.ErrorList
25041 }{{
25042 description: "seccomp field and container annotation must match",
25043 fldPath: rootFld,
25044 expectedErr: field.ErrorList{
25045 field.Forbidden(
25046 rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"),
25047 "seccomp type in annotation and field must match"),
25048 },
25049 spec: &core.PodTemplateSpec{
25050 ObjectMeta: metav1.ObjectMeta{
25051 Annotations: map[string]string{
25052 "container.seccomp.security.alpha.kubernetes.io/test2": "unconfined",
25053 },
25054 },
25055 Spec: core.PodSpec{
25056 Containers: []core.Container{{
25057 Name: "test1",
25058 Image: "alpine",
25059 ImagePullPolicy: core.PullAlways,
25060 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
25061 }, {
25062 SecurityContext: &core.SecurityContext{
25063 SeccompProfile: &core.SeccompProfile{
25064 Type: core.SeccompProfileTypeRuntimeDefault,
25065 },
25066 },
25067 Name: "test2",
25068 Image: "alpine",
25069 ImagePullPolicy: core.PullAlways,
25070 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
25071 }},
25072 RestartPolicy: core.RestartPolicyAlways,
25073 DNSPolicy: core.DNSDefault,
25074 },
25075 },
25076 }, {
25077 description: "seccomp field and pod annotation must match",
25078 fldPath: rootFld,
25079 expectedErr: field.ErrorList{
25080 field.Forbidden(
25081 rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"),
25082 "seccomp type in annotation and field must match"),
25083 },
25084 spec: &core.PodTemplateSpec{
25085 ObjectMeta: metav1.ObjectMeta{
25086 Annotations: map[string]string{
25087 "seccomp.security.alpha.kubernetes.io/pod": "runtime/default",
25088 },
25089 },
25090 Spec: core.PodSpec{
25091 SecurityContext: &core.PodSecurityContext{
25092 SeccompProfile: &core.SeccompProfile{
25093 Type: core.SeccompProfileTypeUnconfined,
25094 },
25095 },
25096 Containers: []core.Container{{
25097 Name: "test",
25098 Image: "alpine",
25099 ImagePullPolicy: core.PullAlways,
25100 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
25101 }},
25102 RestartPolicy: core.RestartPolicyAlways,
25103 DNSPolicy: core.DNSDefault,
25104 },
25105 },
25106 }, {
25107 description: "init seccomp field and container annotation must match",
25108 fldPath: rootFld,
25109 expectedErr: field.ErrorList{
25110 field.Forbidden(
25111 rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"),
25112 "seccomp type in annotation and field must match"),
25113 },
25114 spec: &core.PodTemplateSpec{
25115 ObjectMeta: metav1.ObjectMeta{
25116 Annotations: map[string]string{
25117 "container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined",
25118 },
25119 },
25120 Spec: core.PodSpec{
25121 Containers: []core.Container{{
25122 Name: "test",
25123 Image: "alpine",
25124 ImagePullPolicy: core.PullAlways,
25125 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
25126 }},
25127 InitContainers: []core.Container{{
25128 Name: "init-test",
25129 SecurityContext: &core.SecurityContext{
25130 SeccompProfile: &core.SeccompProfile{
25131 Type: core.SeccompProfileTypeRuntimeDefault,
25132 },
25133 },
25134 Image: "alpine",
25135 ImagePullPolicy: core.PullAlways,
25136 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
25137 }},
25138 RestartPolicy: core.RestartPolicyAlways,
25139 DNSPolicy: core.DNSDefault,
25140 },
25141 },
25142 },
25143 }
25144
25145 for i, test := range tests {
25146 err := ValidatePodTemplateSpec(test.spec, rootFld, PodValidationOptions{})
25147 assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
25148 }
25149 }
25150
25151 func TestValidateResourceRequirements(t *testing.T) {
25152 path := field.NewPath("resources")
25153 tests := []struct {
25154 name string
25155 requirements core.ResourceRequirements
25156 opts PodValidationOptions
25157 }{{
25158 name: "limits and requests of hugepage resource are equal",
25159 requirements: core.ResourceRequirements{
25160 Limits: core.ResourceList{
25161 core.ResourceCPU: resource.MustParse("10"),
25162 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
25163 },
25164 Requests: core.ResourceList{
25165 core.ResourceCPU: resource.MustParse("10"),
25166 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
25167 },
25168 },
25169 opts: PodValidationOptions{},
25170 }, {
25171 name: "limits and requests of memory resource are equal",
25172 requirements: core.ResourceRequirements{
25173 Limits: core.ResourceList{
25174 core.ResourceMemory: resource.MustParse("2Mi"),
25175 },
25176 Requests: core.ResourceList{
25177 core.ResourceMemory: resource.MustParse("2Mi"),
25178 },
25179 },
25180 opts: PodValidationOptions{},
25181 }, {
25182 name: "limits and requests of cpu resource are equal",
25183 requirements: core.ResourceRequirements{
25184 Limits: core.ResourceList{
25185 core.ResourceCPU: resource.MustParse("10"),
25186 },
25187 Requests: core.ResourceList{
25188 core.ResourceCPU: resource.MustParse("10"),
25189 },
25190 },
25191 opts: PodValidationOptions{},
25192 },
25193 }
25194
25195 for _, tc := range tests {
25196 t.Run(tc.name, func(t *testing.T) {
25197 if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) != 0 {
25198 t.Errorf("unexpected errors: %v", errs)
25199 }
25200 })
25201 }
25202
25203 errTests := []struct {
25204 name string
25205 requirements core.ResourceRequirements
25206 opts PodValidationOptions
25207 }{{
25208 name: "hugepage resource without cpu or memory",
25209 requirements: core.ResourceRequirements{
25210 Limits: core.ResourceList{
25211 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
25212 },
25213 Requests: core.ResourceList{
25214 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
25215 },
25216 },
25217 opts: PodValidationOptions{},
25218 },
25219 }
25220
25221 for _, tc := range errTests {
25222 t.Run(tc.name, func(t *testing.T) {
25223 if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) == 0 {
25224 t.Error("expected errors")
25225 }
25226 })
25227 }
25228 }
25229
25230 func TestValidateNonSpecialIP(t *testing.T) {
25231 fp := field.NewPath("ip")
25232
25233
25234 for _, tc := range []struct {
25235 desc string
25236 ip string
25237 }{
25238 {"ipv4", "10.1.2.3"},
25239 {"ipv4 class E", "244.1.2.3"},
25240 {"ipv6", "2000::1"},
25241 } {
25242 t.Run(tc.desc, func(t *testing.T) {
25243 errs := ValidateNonSpecialIP(tc.ip, fp)
25244 if len(errs) != 0 {
25245 t.Errorf("ValidateNonSpecialIP(%q, ...) = %v; want nil", tc.ip, errs)
25246 }
25247 })
25248 }
25249
25250 for _, tc := range []struct {
25251 desc string
25252 ip string
25253 }{
25254 {"ipv4 unspecified", "0.0.0.0"},
25255 {"ipv6 unspecified", "::0"},
25256 {"ipv4 localhost", "127.0.0.0"},
25257 {"ipv4 localhost", "127.255.255.255"},
25258 {"ipv6 localhost", "::1"},
25259 {"ipv6 link local", "fe80::"},
25260 {"ipv6 local multicast", "ff02::"},
25261 } {
25262 t.Run(tc.desc, func(t *testing.T) {
25263 errs := ValidateNonSpecialIP(tc.ip, fp)
25264 if len(errs) == 0 {
25265 t.Errorf("ValidateNonSpecialIP(%q, ...) = nil; want non-nil (errors)", tc.ip)
25266 }
25267 })
25268 }
25269 }
25270
25271 func TestValidateHostUsers(t *testing.T) {
25272 falseVar := false
25273 trueVar := true
25274
25275 cases := []struct {
25276 name string
25277 success bool
25278 spec *core.PodSpec
25279 }{{
25280 name: "empty",
25281 success: true,
25282 spec: &core.PodSpec{},
25283 }, {
25284 name: "hostUsers unset",
25285 success: true,
25286 spec: &core.PodSpec{
25287 SecurityContext: &core.PodSecurityContext{},
25288 },
25289 }, {
25290 name: "hostUsers=false",
25291 success: true,
25292 spec: &core.PodSpec{
25293 SecurityContext: &core.PodSecurityContext{
25294 HostUsers: &falseVar,
25295 },
25296 },
25297 }, {
25298 name: "hostUsers=true",
25299 success: true,
25300 spec: &core.PodSpec{
25301 SecurityContext: &core.PodSecurityContext{
25302 HostUsers: &trueVar,
25303 },
25304 },
25305 }, {
25306 name: "hostUsers=false & volumes",
25307 success: true,
25308 spec: &core.PodSpec{
25309 SecurityContext: &core.PodSecurityContext{
25310 HostUsers: &falseVar,
25311 },
25312 Volumes: []core.Volume{{
25313 Name: "configmap",
25314 VolumeSource: core.VolumeSource{
25315 ConfigMap: &core.ConfigMapVolumeSource{
25316 LocalObjectReference: core.LocalObjectReference{Name: "configmap"},
25317 },
25318 },
25319 }, {
25320 Name: "secret",
25321 VolumeSource: core.VolumeSource{
25322 Secret: &core.SecretVolumeSource{
25323 SecretName: "secret",
25324 },
25325 },
25326 }, {
25327 Name: "downward-api",
25328 VolumeSource: core.VolumeSource{
25329 DownwardAPI: &core.DownwardAPIVolumeSource{},
25330 },
25331 }, {
25332 Name: "proj",
25333 VolumeSource: core.VolumeSource{
25334 Projected: &core.ProjectedVolumeSource{},
25335 },
25336 }, {
25337 Name: "empty-dir",
25338 VolumeSource: core.VolumeSource{
25339 EmptyDir: &core.EmptyDirVolumeSource{},
25340 },
25341 }},
25342 },
25343 }, {
25344 name: "hostUsers=false - stateful volume",
25345 success: true,
25346 spec: &core.PodSpec{
25347 SecurityContext: &core.PodSecurityContext{
25348 HostUsers: &falseVar,
25349 },
25350 Volumes: []core.Volume{{
25351 Name: "host-path",
25352 VolumeSource: core.VolumeSource{
25353 HostPath: &core.HostPathVolumeSource{},
25354 },
25355 }},
25356 },
25357 }, {
25358 name: "hostUsers=true - unsupported volume",
25359 success: true,
25360 spec: &core.PodSpec{
25361 SecurityContext: &core.PodSecurityContext{
25362 HostUsers: &trueVar,
25363 },
25364 Volumes: []core.Volume{{
25365 Name: "host-path",
25366 VolumeSource: core.VolumeSource{
25367 HostPath: &core.HostPathVolumeSource{},
25368 },
25369 }},
25370 },
25371 }, {
25372 name: "hostUsers=false & HostNetwork",
25373 success: false,
25374 spec: &core.PodSpec{
25375 SecurityContext: &core.PodSecurityContext{
25376 HostUsers: &falseVar,
25377 HostNetwork: true,
25378 },
25379 },
25380 }, {
25381 name: "hostUsers=false & HostPID",
25382 success: false,
25383 spec: &core.PodSpec{
25384 SecurityContext: &core.PodSecurityContext{
25385 HostUsers: &falseVar,
25386 HostPID: true,
25387 },
25388 },
25389 }, {
25390 name: "hostUsers=false & HostIPC",
25391 success: false,
25392 spec: &core.PodSpec{
25393 SecurityContext: &core.PodSecurityContext{
25394 HostUsers: &falseVar,
25395 HostIPC: true,
25396 },
25397 },
25398 },
25399 }
25400
25401 for _, tc := range cases {
25402 t.Run(tc.name, func(t *testing.T) {
25403 fPath := field.NewPath("spec")
25404
25405 allErrs := validateHostUsers(tc.spec, fPath)
25406 if !tc.success && len(allErrs) == 0 {
25407 t.Errorf("Unexpected success")
25408 }
25409 if tc.success && len(allErrs) != 0 {
25410 t.Errorf("Unexpected error(s): %v", allErrs)
25411 }
25412 })
25413 }
25414 }
25415
25416 func TestValidateWindowsHostProcessPod(t *testing.T) {
25417 const containerName = "container"
25418 falseVar := false
25419 trueVar := true
25420
25421 testCases := []struct {
25422 name string
25423 expectError bool
25424 allowPrivileged bool
25425 podSpec *core.PodSpec
25426 }{{
25427 name: "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate",
25428 expectError: true,
25429 allowPrivileged: true,
25430 podSpec: &core.PodSpec{
25431 SecurityContext: &core.PodSecurityContext{
25432 WindowsOptions: &core.WindowsSecurityContextOptions{
25433 HostProcess: &trueVar,
25434 },
25435 },
25436 Containers: []core.Container{{
25437 Name: containerName,
25438 }},
25439 },
25440 }, {
25441 name: "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate",
25442 expectError: false,
25443 allowPrivileged: true,
25444 podSpec: &core.PodSpec{
25445 SecurityContext: &core.PodSecurityContext{
25446 HostNetwork: true,
25447 WindowsOptions: &core.WindowsSecurityContextOptions{
25448 HostProcess: &trueVar,
25449 },
25450 },
25451 Containers: []core.Container{{
25452 Name: containerName,
25453 }},
25454 },
25455 }, {
25456 name: "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate",
25457 expectError: false,
25458 allowPrivileged: true,
25459 podSpec: &core.PodSpec{
25460 SecurityContext: &core.PodSecurityContext{
25461 HostNetwork: true,
25462 WindowsOptions: &core.WindowsSecurityContextOptions{
25463 HostProcess: &trueVar,
25464 },
25465 },
25466 Containers: []core.Container{{
25467 Name: containerName,
25468 SecurityContext: &core.SecurityContext{
25469 WindowsOptions: &core.WindowsSecurityContextOptions{
25470 HostProcess: &trueVar,
25471 },
25472 },
25473 }},
25474 InitContainers: []core.Container{{
25475 Name: containerName,
25476 SecurityContext: &core.SecurityContext{
25477 WindowsOptions: &core.WindowsSecurityContextOptions{
25478 HostProcess: &trueVar,
25479 },
25480 },
25481 }},
25482 },
25483 }, {
25484 name: "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate",
25485 expectError: false,
25486 allowPrivileged: true,
25487 podSpec: &core.PodSpec{
25488 SecurityContext: &core.PodSecurityContext{
25489 HostNetwork: true,
25490 },
25491 Containers: []core.Container{{
25492 Name: containerName,
25493 SecurityContext: &core.SecurityContext{
25494 WindowsOptions: &core.WindowsSecurityContextOptions{
25495 HostProcess: &trueVar,
25496 },
25497 },
25498 }},
25499 InitContainers: []core.Container{{
25500 Name: containerName,
25501 SecurityContext: &core.SecurityContext{
25502 WindowsOptions: &core.WindowsSecurityContextOptions{
25503 HostProcess: &trueVar,
25504 },
25505 },
25506 }},
25507 },
25508 }, {
25509 name: "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
25510 expectError: true,
25511 allowPrivileged: true,
25512 podSpec: &core.PodSpec{
25513 SecurityContext: &core.PodSecurityContext{
25514 HostNetwork: true,
25515 },
25516 Containers: []core.Container{{
25517 Name: containerName,
25518 SecurityContext: &core.SecurityContext{
25519 WindowsOptions: &core.WindowsSecurityContextOptions{
25520 HostProcess: &trueVar,
25521 },
25522 },
25523 }},
25524 InitContainers: []core.Container{{
25525 Name: containerName,
25526 SecurityContext: &core.SecurityContext{
25527 WindowsOptions: &core.WindowsSecurityContextOptions{
25528 HostProcess: &falseVar,
25529 },
25530 },
25531 }},
25532 },
25533 }, {
25534 name: "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate",
25535 expectError: true,
25536 allowPrivileged: true,
25537 podSpec: &core.PodSpec{
25538 SecurityContext: &core.PodSecurityContext{
25539 HostNetwork: true,
25540 },
25541 Containers: []core.Container{{
25542 Name: containerName,
25543 SecurityContext: &core.SecurityContext{
25544 WindowsOptions: &core.WindowsSecurityContextOptions{
25545 HostProcess: &trueVar,
25546 },
25547 },
25548 }},
25549 InitContainers: []core.Container{{
25550 Name: containerName,
25551 }},
25552 },
25553 }, {
25554 name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate",
25555 expectError: true,
25556 allowPrivileged: true,
25557 podSpec: &core.PodSpec{
25558 SecurityContext: &core.PodSecurityContext{
25559 HostNetwork: true,
25560 WindowsOptions: &core.WindowsSecurityContextOptions{
25561 HostProcess: &trueVar,
25562 },
25563 },
25564 Containers: []core.Container{{
25565 Name: containerName,
25566 SecurityContext: &core.SecurityContext{
25567 WindowsOptions: &core.WindowsSecurityContextOptions{
25568 HostProcess: &trueVar,
25569 },
25570 },
25571 }},
25572 InitContainers: []core.Container{{
25573 Name: containerName,
25574 SecurityContext: &core.SecurityContext{
25575 WindowsOptions: &core.WindowsSecurityContextOptions{
25576 HostProcess: &falseVar,
25577 },
25578 },
25579 }},
25580 },
25581 }, {
25582 name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
25583 expectError: true,
25584 allowPrivileged: true,
25585 podSpec: &core.PodSpec{
25586 SecurityContext: &core.PodSecurityContext{
25587 HostNetwork: true,
25588 WindowsOptions: &core.WindowsSecurityContextOptions{
25589 HostProcess: &trueVar,
25590 },
25591 },
25592 Containers: []core.Container{{
25593 Name: containerName,
25594 SecurityContext: &core.SecurityContext{
25595 WindowsOptions: &core.WindowsSecurityContextOptions{
25596 HostProcess: &trueVar,
25597 },
25598 },
25599 }, {
25600 Name: containerName,
25601 SecurityContext: &core.SecurityContext{
25602 WindowsOptions: &core.WindowsSecurityContextOptions{
25603 HostProcess: &falseVar,
25604 },
25605 },
25606 }},
25607 },
25608 }, {
25609 name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate",
25610 expectError: false,
25611 allowPrivileged: true,
25612 podSpec: &core.PodSpec{
25613 SecurityContext: &core.PodSecurityContext{
25614 HostNetwork: true,
25615 WindowsOptions: &core.WindowsSecurityContextOptions{
25616 HostProcess: &trueVar,
25617 },
25618 },
25619 Containers: []core.Container{{
25620 Name: containerName,
25621 SecurityContext: &core.SecurityContext{
25622 WindowsOptions: &core.WindowsSecurityContextOptions{
25623 HostProcess: &trueVar,
25624 },
25625 },
25626 }},
25627 InitContainers: []core.Container{{
25628 Name: containerName,
25629 }},
25630 },
25631 }, {
25632 name: "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate",
25633 expectError: true,
25634 allowPrivileged: true,
25635 podSpec: &core.PodSpec{
25636 SecurityContext: &core.PodSecurityContext{
25637 HostNetwork: true,
25638 WindowsOptions: &core.WindowsSecurityContextOptions{
25639 HostProcess: &falseVar,
25640 },
25641 },
25642 Containers: []core.Container{{
25643 Name: containerName,
25644 SecurityContext: &core.SecurityContext{
25645 WindowsOptions: &core.WindowsSecurityContextOptions{
25646 HostProcess: &trueVar,
25647 },
25648 },
25649 }},
25650 InitContainers: []core.Container{{
25651 Name: containerName,
25652 }},
25653 },
25654 }, {
25655 name: "Pod's HostProcess set to true but all containers override to false should not validate",
25656 expectError: true,
25657 allowPrivileged: true,
25658 podSpec: &core.PodSpec{
25659 SecurityContext: &core.PodSecurityContext{
25660 HostNetwork: true,
25661 WindowsOptions: &core.WindowsSecurityContextOptions{
25662 HostProcess: &trueVar,
25663 },
25664 },
25665 Containers: []core.Container{{
25666 Name: containerName,
25667 SecurityContext: &core.SecurityContext{
25668 WindowsOptions: &core.WindowsSecurityContextOptions{
25669 HostProcess: &falseVar,
25670 },
25671 },
25672 }},
25673 },
25674 }, {
25675 name: "Valid HostProcess pod should spec should not validate if allowPrivileged is not set",
25676 expectError: true,
25677 allowPrivileged: false,
25678 podSpec: &core.PodSpec{
25679 SecurityContext: &core.PodSecurityContext{
25680 HostNetwork: true,
25681 },
25682 Containers: []core.Container{{
25683 Name: containerName,
25684 SecurityContext: &core.SecurityContext{
25685 WindowsOptions: &core.WindowsSecurityContextOptions{
25686 HostProcess: &trueVar,
25687 },
25688 },
25689 }},
25690 },
25691 }, {
25692 name: "Non-HostProcess ephemeral container in HostProcess pod should not validate",
25693 expectError: true,
25694 allowPrivileged: true,
25695 podSpec: &core.PodSpec{
25696 SecurityContext: &core.PodSecurityContext{
25697 HostNetwork: true,
25698 WindowsOptions: &core.WindowsSecurityContextOptions{
25699 HostProcess: &trueVar,
25700 },
25701 },
25702 Containers: []core.Container{{
25703 Name: containerName,
25704 }},
25705 EphemeralContainers: []core.EphemeralContainer{{
25706 EphemeralContainerCommon: core.EphemeralContainerCommon{
25707 SecurityContext: &core.SecurityContext{
25708 WindowsOptions: &core.WindowsSecurityContextOptions{
25709 HostProcess: &falseVar,
25710 },
25711 },
25712 },
25713 }},
25714 },
25715 }, {
25716 name: "HostProcess ephemeral container in HostProcess pod should validate",
25717 expectError: false,
25718 allowPrivileged: true,
25719 podSpec: &core.PodSpec{
25720 SecurityContext: &core.PodSecurityContext{
25721 HostNetwork: true,
25722 WindowsOptions: &core.WindowsSecurityContextOptions{
25723 HostProcess: &trueVar,
25724 },
25725 },
25726 Containers: []core.Container{{
25727 Name: containerName,
25728 }},
25729 EphemeralContainers: []core.EphemeralContainer{{
25730 EphemeralContainerCommon: core.EphemeralContainerCommon{},
25731 }},
25732 },
25733 }, {
25734 name: "Non-HostProcess ephemeral container in Non-HostProcess pod should validate",
25735 expectError: false,
25736 allowPrivileged: true,
25737 podSpec: &core.PodSpec{
25738 Containers: []core.Container{{
25739 Name: containerName,
25740 }},
25741 EphemeralContainers: []core.EphemeralContainer{{
25742 EphemeralContainerCommon: core.EphemeralContainerCommon{
25743 SecurityContext: &core.SecurityContext{
25744 WindowsOptions: &core.WindowsSecurityContextOptions{
25745 HostProcess: &falseVar,
25746 },
25747 },
25748 },
25749 }},
25750 },
25751 }, {
25752 name: "HostProcess ephemeral container in Non-HostProcess pod should not validate",
25753 expectError: true,
25754 allowPrivileged: true,
25755 podSpec: &core.PodSpec{
25756 Containers: []core.Container{{
25757 Name: containerName,
25758 }},
25759 EphemeralContainers: []core.EphemeralContainer{{
25760 EphemeralContainerCommon: core.EphemeralContainerCommon{
25761 SecurityContext: &core.SecurityContext{
25762 WindowsOptions: &core.WindowsSecurityContextOptions{
25763 HostProcess: &trueVar,
25764 },
25765 },
25766 },
25767 }},
25768 },
25769 },
25770 }
25771
25772 for _, testCase := range testCases {
25773 t.Run(testCase.name, func(t *testing.T) {
25774
25775 capabilities.SetForTests(capabilities.Capabilities{
25776 AllowPrivileged: testCase.allowPrivileged,
25777 })
25778
25779 errs := validateWindowsHostProcessPod(testCase.podSpec, field.NewPath("spec"))
25780 if testCase.expectError && len(errs) == 0 {
25781 t.Errorf("Unexpected success")
25782 }
25783 if !testCase.expectError && len(errs) != 0 {
25784 t.Errorf("Unexpected error(s): %v", errs)
25785 }
25786 })
25787 }
25788 }
25789
25790 func TestValidateOS(t *testing.T) {
25791 testCases := []struct {
25792 name string
25793 expectError bool
25794 podSpec *core.PodSpec
25795 }{{
25796 name: "no OS field, featuregate",
25797 expectError: false,
25798 podSpec: &core.PodSpec{OS: nil},
25799 }, {
25800 name: "empty OS field, featuregate",
25801 expectError: true,
25802 podSpec: &core.PodSpec{OS: &core.PodOS{}},
25803 }, {
25804 name: "OS field, featuregate, valid OS",
25805 expectError: false,
25806 podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Linux}},
25807 }, {
25808 name: "OS field, featuregate, valid OS",
25809 expectError: false,
25810 podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Windows}},
25811 }, {
25812 name: "OS field, featuregate, empty OS",
25813 expectError: true,
25814 podSpec: &core.PodSpec{OS: &core.PodOS{Name: ""}},
25815 }, {
25816 name: "OS field, featuregate, invalid OS",
25817 expectError: true,
25818 podSpec: &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}},
25819 },
25820 }
25821 for _, testCase := range testCases {
25822 t.Run(testCase.name, func(t *testing.T) {
25823 errs := validateOS(testCase.podSpec, field.NewPath("spec"), PodValidationOptions{})
25824 if testCase.expectError && len(errs) == 0 {
25825 t.Errorf("Unexpected success")
25826 }
25827 if !testCase.expectError && len(errs) != 0 {
25828 t.Errorf("Unexpected error(s): %v", errs)
25829 }
25830 })
25831 }
25832 }
25833
25834 func TestValidateAppArmorProfileFormat(t *testing.T) {
25835 tests := []struct {
25836 profile string
25837 expectValid bool
25838 }{
25839 {"", true},
25840 {v1.DeprecatedAppArmorBetaProfileRuntimeDefault, true},
25841 {v1.DeprecatedAppArmorBetaProfileNameUnconfined, true},
25842 {"baz", false},
25843 {v1.DeprecatedAppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
25844 {v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo-bar", true},
25845 }
25846
25847 for _, test := range tests {
25848 err := ValidateAppArmorProfileFormat(test.profile)
25849 if test.expectValid {
25850 assert.NoError(t, err, "Profile %s should be valid", test.profile)
25851 } else {
25852 assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile))
25853 }
25854 }
25855 }
25856
25857 func TestValidateDownwardAPIHostIPs(t *testing.T) {
25858 testCases := []struct {
25859 name string
25860 expectError bool
25861 featureEnabled bool
25862 fieldSel *core.ObjectFieldSelector
25863 }{
25864 {
25865 name: "has no hostIPs field, featuregate enabled",
25866 expectError: false,
25867 featureEnabled: true,
25868 fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
25869 },
25870 {
25871 name: "has hostIPs field, featuregate enabled",
25872 expectError: false,
25873 featureEnabled: true,
25874 fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
25875 },
25876 }
25877 for _, testCase := range testCases {
25878 t.Run(testCase.name, func(t *testing.T) {
25879 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, testCase.featureEnabled)()
25880
25881 errs := validateDownwardAPIHostIPs(testCase.fieldSel, field.NewPath("fieldSel"), PodValidationOptions{AllowHostIPsField: testCase.featureEnabled})
25882 if testCase.expectError && len(errs) == 0 {
25883 t.Errorf("Unexpected success")
25884 }
25885 if !testCase.expectError && len(errs) != 0 {
25886 t.Errorf("Unexpected error(s): %v", errs)
25887 }
25888 })
25889 }
25890 }
25891
25892 func TestValidatePVSecretReference(t *testing.T) {
25893 rootFld := field.NewPath("name")
25894 type args struct {
25895 secretRef *core.SecretReference
25896 fldPath *field.Path
25897 }
25898 tests := []struct {
25899 name string
25900 args args
25901 expectError bool
25902 expectedError string
25903 }{{
25904 name: "invalid secret ref name",
25905 args: args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld},
25906 expectError: true,
25907 expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
25908 }, {
25909 name: "invalid secret ref namespace",
25910 args: args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld},
25911 expectError: true,
25912 expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg,
25913 }, {
25914 name: "invalid secret: missing namespace",
25915 args: args{&core.SecretReference{Name: "valid"}, rootFld},
25916 expectError: true,
25917 expectedError: "name.namespace: Required value",
25918 }, {
25919 name: "invalid secret : missing name",
25920 args: args{&core.SecretReference{Namespace: "default"}, rootFld},
25921 expectError: true,
25922 expectedError: "name.name: Required value",
25923 }, {
25924 name: "valid secret",
25925 args: args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld},
25926 expectError: false,
25927 expectedError: "",
25928 },
25929 }
25930 for _, tt := range tests {
25931 t.Run(tt.name, func(t *testing.T) {
25932 errs := validatePVSecretReference(tt.args.secretRef, tt.args.fldPath)
25933 if tt.expectError && len(errs) == 0 {
25934 t.Errorf("Unexpected success")
25935 }
25936 if tt.expectError && len(errs) != 0 {
25937 str := errs[0].Error()
25938 if str != "" && !strings.Contains(str, tt.expectedError) {
25939 t.Errorf("%s: expected error detail either empty or %q, got %q", tt.name, tt.expectedError, str)
25940 }
25941 }
25942 if !tt.expectError && len(errs) != 0 {
25943 t.Errorf("Unexpected error(s): %v", errs)
25944 }
25945 })
25946 }
25947 }
25948
25949 func TestValidateDynamicResourceAllocation(t *testing.T) {
25950 externalClaimName := "some-claim"
25951 externalClaimTemplateName := "some-claim-template"
25952 goodClaimSource := core.ClaimSource{
25953 ResourceClaimName: &externalClaimName,
25954 }
25955 shortPodName := &metav1.ObjectMeta{
25956 Name: "some-pod",
25957 }
25958 brokenPodName := &metav1.ObjectMeta{
25959 Name: ".dot.com",
25960 }
25961 goodClaimTemplate := core.PodSpec{
25962 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-template"}}}}},
25963 RestartPolicy: core.RestartPolicyAlways,
25964 DNSPolicy: core.DNSClusterFirst,
25965 ResourceClaims: []core.PodResourceClaim{{
25966 Name: "my-claim-template",
25967 Source: core.ClaimSource{
25968 ResourceClaimTemplateName: &externalClaimTemplateName,
25969 },
25970 }},
25971 }
25972 goodClaimReference := core.PodSpec{
25973 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-reference"}}}}},
25974 RestartPolicy: core.RestartPolicyAlways,
25975 DNSPolicy: core.DNSClusterFirst,
25976 ResourceClaims: []core.PodResourceClaim{{
25977 Name: "my-claim-reference",
25978 Source: core.ClaimSource{
25979 ResourceClaimName: &externalClaimName,
25980 },
25981 }},
25982 }
25983
25984 successCases := map[string]core.PodSpec{
25985 "resource claim reference": goodClaimTemplate,
25986 "resource claim template": goodClaimTemplate,
25987 "multiple claims": {
25988 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}},
25989 RestartPolicy: core.RestartPolicyAlways,
25990 DNSPolicy: core.DNSClusterFirst,
25991 ResourceClaims: []core.PodResourceClaim{{
25992 Name: "my-claim",
25993 Source: goodClaimSource,
25994 }, {
25995 Name: "another-claim",
25996 Source: goodClaimSource,
25997 }},
25998 },
25999 "init container": {
26000 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26001 InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26002 RestartPolicy: core.RestartPolicyAlways,
26003 DNSPolicy: core.DNSClusterFirst,
26004 ResourceClaims: []core.PodResourceClaim{{
26005 Name: "my-claim",
26006 Source: goodClaimSource,
26007 }},
26008 },
26009 }
26010 for k, v := range successCases {
26011 t.Run(k, func(t *testing.T) {
26012 if errs := ValidatePodSpec(&v, shortPodName, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
26013 t.Errorf("expected success: %v", errs)
26014 }
26015 })
26016 }
26017
26018 failureCases := map[string]core.PodSpec{
26019 "pod claim name with prefix": {
26020 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
26021 RestartPolicy: core.RestartPolicyAlways,
26022 DNSPolicy: core.DNSClusterFirst,
26023 ResourceClaims: []core.PodResourceClaim{{
26024 Name: "../my-claim",
26025 Source: goodClaimSource,
26026 }},
26027 },
26028 "pod claim name with path": {
26029 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
26030 RestartPolicy: core.RestartPolicyAlways,
26031 DNSPolicy: core.DNSClusterFirst,
26032 ResourceClaims: []core.PodResourceClaim{{
26033 Name: "my/claim",
26034 Source: goodClaimSource,
26035 }},
26036 },
26037 "pod claim name empty": {
26038 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
26039 RestartPolicy: core.RestartPolicyAlways,
26040 DNSPolicy: core.DNSClusterFirst,
26041 ResourceClaims: []core.PodResourceClaim{{
26042 Name: "",
26043 Source: goodClaimSource,
26044 }},
26045 },
26046 "duplicate pod claim entries": {
26047 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
26048 RestartPolicy: core.RestartPolicyAlways,
26049 DNSPolicy: core.DNSClusterFirst,
26050 ResourceClaims: []core.PodResourceClaim{{
26051 Name: "my-claim",
26052 Source: goodClaimSource,
26053 }, {
26054 Name: "my-claim",
26055 Source: goodClaimSource,
26056 }},
26057 },
26058 "resource claim source empty": {
26059 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26060 RestartPolicy: core.RestartPolicyAlways,
26061 DNSPolicy: core.DNSClusterFirst,
26062 ResourceClaims: []core.PodResourceClaim{{
26063 Name: "my-claim",
26064 Source: core.ClaimSource{},
26065 }},
26066 },
26067 "resource claim reference and template": {
26068 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26069 RestartPolicy: core.RestartPolicyAlways,
26070 DNSPolicy: core.DNSClusterFirst,
26071 ResourceClaims: []core.PodResourceClaim{{
26072 Name: "my-claim",
26073 Source: core.ClaimSource{
26074 ResourceClaimName: &externalClaimName,
26075 ResourceClaimTemplateName: &externalClaimTemplateName,
26076 },
26077 }},
26078 },
26079 "claim not found": {
26080 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}},
26081 RestartPolicy: core.RestartPolicyAlways,
26082 DNSPolicy: core.DNSClusterFirst,
26083 ResourceClaims: []core.PodResourceClaim{{
26084 Name: "my-claim",
26085 Source: goodClaimSource,
26086 }},
26087 },
26088 "claim name empty": {
26089 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}},
26090 RestartPolicy: core.RestartPolicyAlways,
26091 DNSPolicy: core.DNSClusterFirst,
26092 ResourceClaims: []core.PodResourceClaim{{
26093 Name: "my-claim",
26094 Source: goodClaimSource,
26095 }},
26096 },
26097 "pod claim name duplicates": {
26098 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}},
26099 RestartPolicy: core.RestartPolicyAlways,
26100 DNSPolicy: core.DNSClusterFirst,
26101 ResourceClaims: []core.PodResourceClaim{{
26102 Name: "my-claim",
26103 Source: goodClaimSource,
26104 }},
26105 },
26106 "no claims defined": {
26107 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26108 RestartPolicy: core.RestartPolicyAlways,
26109 DNSPolicy: core.DNSClusterFirst,
26110 },
26111 "duplicate pod claim name": {
26112 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26113 RestartPolicy: core.RestartPolicyAlways,
26114 DNSPolicy: core.DNSClusterFirst,
26115 ResourceClaims: []core.PodResourceClaim{{
26116 Name: "my-claim",
26117 Source: goodClaimSource,
26118 }, {
26119 Name: "my-claim",
26120 Source: goodClaimSource,
26121 }},
26122 },
26123 "ephemeral container don't support resource requirements": {
26124 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
26125 EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr-ephemeral", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}, TargetContainerName: "ctr"}},
26126 RestartPolicy: core.RestartPolicyAlways,
26127 DNSPolicy: core.DNSClusterFirst,
26128 ResourceClaims: []core.PodResourceClaim{{
26129 Name: "my-claim",
26130 Source: goodClaimSource,
26131 }},
26132 },
26133 "invalid claim template name": func() core.PodSpec {
26134 spec := goodClaimTemplate.DeepCopy()
26135 notLabel := ".foo_bar"
26136 spec.ResourceClaims[0].Source.ResourceClaimTemplateName = ¬Label
26137 return *spec
26138 }(),
26139 "invalid claim reference name": func() core.PodSpec {
26140 spec := goodClaimReference.DeepCopy()
26141 notLabel := ".foo_bar"
26142 spec.ResourceClaims[0].Source.ResourceClaimName = ¬Label
26143 return *spec
26144 }(),
26145 }
26146 for k, v := range failureCases {
26147 if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
26148 t.Errorf("expected failure for %q", k)
26149 }
26150 }
26151
26152 t.Run("generated-claim-name", func(t *testing.T) {
26153 for _, spec := range []*core.PodSpec{&goodClaimTemplate, &goodClaimReference} {
26154 claimName := spec.ResourceClaims[0].Name
26155 t.Run(claimName, func(t *testing.T) {
26156 for _, podMeta := range []*metav1.ObjectMeta{shortPodName, brokenPodName} {
26157 t.Run(podMeta.Name, func(t *testing.T) {
26158 errs := ValidatePodSpec(spec, podMeta, field.NewPath("field"), PodValidationOptions{})
26159
26160 expectError := spec == &goodClaimTemplate && podMeta == brokenPodName
26161 if expectError && len(errs) == 0 {
26162 t.Error("did not get the expected failure")
26163 }
26164 if !expectError && len(errs) > 0 {
26165 t.Errorf("unexpected failures: %+v", errs)
26166 }
26167 })
26168 }
26169 })
26170 }
26171 })
26172 }
26173
26174 func TestValidateLoadBalancerStatus(t *testing.T) {
26175 ipModeVIP := core.LoadBalancerIPModeVIP
26176 ipModeProxy := core.LoadBalancerIPModeProxy
26177 ipModeDummy := core.LoadBalancerIPMode("dummy")
26178
26179 testCases := []struct {
26180 name string
26181 ipModeEnabled bool
26182 nonLBAllowed bool
26183 tweakLBStatus func(s *core.LoadBalancerStatus)
26184 tweakSvcSpec func(s *core.ServiceSpec)
26185 numErrs int
26186 }{
26187 {
26188 name: "type is not LB",
26189 nonLBAllowed: false,
26190 tweakSvcSpec: func(s *core.ServiceSpec) {
26191 s.Type = core.ServiceTypeClusterIP
26192 },
26193 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26194 s.Ingress = []core.LoadBalancerIngress{{
26195 IP: "1.2.3.4",
26196 }}
26197 },
26198 numErrs: 1,
26199 }, {
26200 name: "type is not LB. back-compat",
26201 nonLBAllowed: true,
26202 tweakSvcSpec: func(s *core.ServiceSpec) {
26203 s.Type = core.ServiceTypeClusterIP
26204 },
26205 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26206 s.Ingress = []core.LoadBalancerIngress{{
26207 IP: "1.2.3.4",
26208 }}
26209 },
26210 numErrs: 0,
26211 }, {
26212 name: "valid vip ipMode",
26213 ipModeEnabled: true,
26214 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26215 s.Ingress = []core.LoadBalancerIngress{{
26216 IP: "1.2.3.4",
26217 IPMode: &ipModeVIP,
26218 }}
26219 },
26220 numErrs: 0,
26221 }, {
26222 name: "valid proxy ipMode",
26223 ipModeEnabled: true,
26224 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26225 s.Ingress = []core.LoadBalancerIngress{{
26226 IP: "1.2.3.4",
26227 IPMode: &ipModeProxy,
26228 }}
26229 },
26230 numErrs: 0,
26231 }, {
26232 name: "invalid ipMode",
26233 ipModeEnabled: true,
26234 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26235 s.Ingress = []core.LoadBalancerIngress{{
26236 IP: "1.2.3.4",
26237 IPMode: &ipModeDummy,
26238 }}
26239 },
26240 numErrs: 1,
26241 }, {
26242 name: "missing ipMode with LoadbalancerIPMode enabled",
26243 ipModeEnabled: true,
26244 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26245 s.Ingress = []core.LoadBalancerIngress{{
26246 IP: "1.2.3.4",
26247 }}
26248 },
26249 numErrs: 1,
26250 }, {
26251 name: "missing ipMode with LoadbalancerIPMode disabled",
26252 ipModeEnabled: false,
26253 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26254 s.Ingress = []core.LoadBalancerIngress{{
26255 IP: "1.2.3.4",
26256 }}
26257 },
26258 numErrs: 0,
26259 }, {
26260 name: "missing ip with ipMode present",
26261 ipModeEnabled: true,
26262 tweakLBStatus: func(s *core.LoadBalancerStatus) {
26263 s.Ingress = []core.LoadBalancerIngress{{
26264 IPMode: &ipModeProxy,
26265 }}
26266 },
26267 numErrs: 1,
26268 },
26269 }
26270 for _, tc := range testCases {
26271 t.Run(tc.name, func(t *testing.T) {
26272 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled)()
26273 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowServiceLBStatusOnNonLB, tc.nonLBAllowed)()
26274 status := core.LoadBalancerStatus{}
26275 tc.tweakLBStatus(&status)
26276 spec := core.ServiceSpec{Type: core.ServiceTypeLoadBalancer}
26277 if tc.tweakSvcSpec != nil {
26278 tc.tweakSvcSpec(&spec)
26279 }
26280 errs := ValidateLoadBalancerStatus(&status, field.NewPath("status"), &spec)
26281 if len(errs) != tc.numErrs {
26282 t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
26283 }
26284 })
26285 }
26286 }
26287
26288 func TestValidateSleepAction(t *testing.T) {
26289 fldPath := field.NewPath("root")
26290 getInvalidStr := func(gracePeriod int64) string {
26291 return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod)
26292 }
26293
26294 testCases := []struct {
26295 name string
26296 action *core.SleepAction
26297 gracePeriod int64
26298 expectErr field.ErrorList
26299 }{
26300 {
26301 name: "valid setting",
26302 action: &core.SleepAction{
26303 Seconds: 5,
26304 },
26305 gracePeriod: 30,
26306 },
26307 {
26308 name: "negative seconds",
26309 action: &core.SleepAction{
26310 Seconds: -1,
26311 },
26312 gracePeriod: 30,
26313 expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
26314 },
26315 {
26316 name: "longer than gracePeriod",
26317 action: &core.SleepAction{
26318 Seconds: 5,
26319 },
26320 gracePeriod: 3,
26321 expectErr: field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))},
26322 },
26323 }
26324
26325 for _, tc := range testCases {
26326 t.Run(tc.name, func(t *testing.T) {
26327 errs := validateSleepAction(tc.action, tc.gracePeriod, fldPath)
26328
26329 if len(tc.expectErr) > 0 && len(errs) == 0 {
26330 t.Errorf("Unexpected success")
26331 } else if len(tc.expectErr) == 0 && len(errs) != 0 {
26332 t.Errorf("Unexpected error(s): %v", errs)
26333 } else if len(tc.expectErr) > 0 {
26334 if tc.expectErr[0].Error() != errs[0].Error() {
26335 t.Errorf("Unexpected error(s): %v", errs)
26336 }
26337 }
26338 })
26339 }
26340 }
26341
View as plain text