k8s.io/kubernetes@v1.29.3/pkg/apis/core/validation/validation_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 defaultGracePeriod = int64(30) 57 ) 58 59 var ( 60 containerRestartPolicyAlways = core.ContainerRestartPolicyAlways 61 containerRestartPolicyOnFailure = core.ContainerRestartPolicy("OnFailure") 62 containerRestartPolicyNever = core.ContainerRestartPolicy("Never") 63 containerRestartPolicyInvalid = core.ContainerRestartPolicy("invalid") 64 containerRestartPolicyEmpty = core.ContainerRestartPolicy("") 65 ) 66 67 type topologyPair struct { 68 key string 69 value string 70 } 71 72 func line() string { 73 _, _, line, ok := runtime.Caller(1) 74 var s string 75 if ok { 76 s = fmt.Sprintf("%d", line) 77 } else { 78 s = "<??>" 79 } 80 return s 81 } 82 83 func prettyErrorList(errs field.ErrorList) string { 84 var s string 85 for _, e := range errs { 86 s += fmt.Sprintf("\t%s\n", e) 87 } 88 return s 89 } 90 91 func newHostPathType(pathType string) *core.HostPathType { 92 hostPathType := new(core.HostPathType) 93 *hostPathType = core.HostPathType(pathType) 94 return hostPathType 95 } 96 97 func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *core.PersistentVolume { 98 objMeta := metav1.ObjectMeta{Name: name} 99 if namespace != "" { 100 objMeta.Namespace = namespace 101 } 102 103 return &core.PersistentVolume{ 104 ObjectMeta: objMeta, 105 Spec: spec, 106 } 107 } 108 109 func TestValidatePersistentVolumes(t *testing.T) { 110 validMode := core.PersistentVolumeFilesystem 111 invalidMode := core.PersistentVolumeMode("fakeVolumeMode") 112 scenarios := map[string]struct { 113 isExpectedFailure bool 114 enableVolumeAttributesClass bool 115 volume *core.PersistentVolume 116 }{ 117 "good-volume": { 118 isExpectedFailure: false, 119 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 120 Capacity: core.ResourceList{ 121 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 122 }, 123 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 124 PersistentVolumeSource: core.PersistentVolumeSource{ 125 HostPath: &core.HostPathVolumeSource{ 126 Path: "/foo", 127 Type: newHostPathType(string(core.HostPathDirectory)), 128 }, 129 }, 130 }), 131 }, 132 "good-volume-with-capacity-unit": { 133 isExpectedFailure: false, 134 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 135 Capacity: core.ResourceList{ 136 core.ResourceName(core.ResourceStorage): resource.MustParse("10Gi"), 137 }, 138 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 139 PersistentVolumeSource: core.PersistentVolumeSource{ 140 HostPath: &core.HostPathVolumeSource{ 141 Path: "/foo", 142 Type: newHostPathType(string(core.HostPathDirectory)), 143 }, 144 }, 145 }), 146 }, 147 "good-volume-without-capacity-unit": { 148 isExpectedFailure: false, 149 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 150 Capacity: core.ResourceList{ 151 core.ResourceName(core.ResourceStorage): resource.MustParse("10"), 152 }, 153 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 154 PersistentVolumeSource: core.PersistentVolumeSource{ 155 HostPath: &core.HostPathVolumeSource{ 156 Path: "/foo", 157 Type: newHostPathType(string(core.HostPathDirectory)), 158 }, 159 }, 160 }), 161 }, 162 "good-volume-with-storage-class": { 163 isExpectedFailure: false, 164 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 165 Capacity: core.ResourceList{ 166 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 167 }, 168 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 169 PersistentVolumeSource: core.PersistentVolumeSource{ 170 HostPath: &core.HostPathVolumeSource{ 171 Path: "/foo", 172 Type: newHostPathType(string(core.HostPathDirectory)), 173 }, 174 }, 175 StorageClassName: "valid", 176 }), 177 }, 178 "good-volume-with-retain-policy": { 179 isExpectedFailure: false, 180 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 181 Capacity: core.ResourceList{ 182 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 183 }, 184 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 185 PersistentVolumeSource: core.PersistentVolumeSource{ 186 HostPath: &core.HostPathVolumeSource{ 187 Path: "/foo", 188 Type: newHostPathType(string(core.HostPathDirectory)), 189 }, 190 }, 191 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRetain, 192 }), 193 }, 194 "good-volume-with-volume-mode": { 195 isExpectedFailure: false, 196 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 197 Capacity: core.ResourceList{ 198 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 199 }, 200 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 201 PersistentVolumeSource: core.PersistentVolumeSource{ 202 HostPath: &core.HostPathVolumeSource{ 203 Path: "/foo", 204 Type: newHostPathType(string(core.HostPathDirectory)), 205 }, 206 }, 207 VolumeMode: &validMode, 208 }), 209 }, 210 "invalid-accessmode": { 211 isExpectedFailure: true, 212 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 213 Capacity: core.ResourceList{ 214 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 215 }, 216 AccessModes: []core.PersistentVolumeAccessMode{"fakemode"}, 217 PersistentVolumeSource: core.PersistentVolumeSource{ 218 HostPath: &core.HostPathVolumeSource{ 219 Path: "/foo", 220 Type: newHostPathType(string(core.HostPathDirectory)), 221 }, 222 }, 223 }), 224 }, 225 "invalid-reclaimpolicy": { 226 isExpectedFailure: true, 227 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 228 Capacity: core.ResourceList{ 229 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 230 }, 231 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 232 PersistentVolumeSource: core.PersistentVolumeSource{ 233 HostPath: &core.HostPathVolumeSource{ 234 Path: "/foo", 235 Type: newHostPathType(string(core.HostPathDirectory)), 236 }, 237 }, 238 PersistentVolumeReclaimPolicy: "fakeReclaimPolicy", 239 }), 240 }, 241 "invalid-volume-mode": { 242 isExpectedFailure: true, 243 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 244 Capacity: core.ResourceList{ 245 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 246 }, 247 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 248 PersistentVolumeSource: core.PersistentVolumeSource{ 249 HostPath: &core.HostPathVolumeSource{ 250 Path: "/foo", 251 Type: newHostPathType(string(core.HostPathDirectory)), 252 }, 253 }, 254 VolumeMode: &invalidMode, 255 }), 256 }, 257 "with-read-write-once-pod": { 258 isExpectedFailure: false, 259 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 260 Capacity: core.ResourceList{ 261 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 262 }, 263 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"}, 264 PersistentVolumeSource: core.PersistentVolumeSource{ 265 HostPath: &core.HostPathVolumeSource{ 266 Path: "/foo", 267 Type: newHostPathType(string(core.HostPathDirectory)), 268 }, 269 }, 270 }), 271 }, 272 "with-read-write-once-pod-and-others": { 273 isExpectedFailure: true, 274 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 275 Capacity: core.ResourceList{ 276 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 277 }, 278 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"}, 279 PersistentVolumeSource: core.PersistentVolumeSource{ 280 HostPath: &core.HostPathVolumeSource{ 281 Path: "/foo", 282 Type: newHostPathType(string(core.HostPathDirectory)), 283 }, 284 }, 285 }), 286 }, 287 "unexpected-namespace": { 288 isExpectedFailure: true, 289 volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{ 290 Capacity: core.ResourceList{ 291 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 292 }, 293 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 294 PersistentVolumeSource: core.PersistentVolumeSource{ 295 HostPath: &core.HostPathVolumeSource{ 296 Path: "/foo", 297 Type: newHostPathType(string(core.HostPathDirectory)), 298 }, 299 }, 300 }), 301 }, 302 "missing-volume-source": { 303 isExpectedFailure: true, 304 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 305 Capacity: core.ResourceList{ 306 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 307 }, 308 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 309 }), 310 }, 311 "bad-name": { 312 isExpectedFailure: true, 313 volume: testVolume("123*Bad(Name", "unexpected-namespace", core.PersistentVolumeSpec{ 314 Capacity: core.ResourceList{ 315 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 316 }, 317 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 318 PersistentVolumeSource: core.PersistentVolumeSource{ 319 HostPath: &core.HostPathVolumeSource{ 320 Path: "/foo", 321 Type: newHostPathType(string(core.HostPathDirectory)), 322 }, 323 }, 324 }), 325 }, 326 "missing-name": { 327 isExpectedFailure: true, 328 volume: testVolume("", "", core.PersistentVolumeSpec{ 329 Capacity: core.ResourceList{ 330 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 331 }, 332 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 333 PersistentVolumeSource: core.PersistentVolumeSource{ 334 HostPath: &core.HostPathVolumeSource{ 335 Path: "/foo", 336 Type: newHostPathType(string(core.HostPathDirectory)), 337 }, 338 }, 339 }), 340 }, 341 "missing-capacity": { 342 isExpectedFailure: true, 343 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 344 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 345 PersistentVolumeSource: core.PersistentVolumeSource{ 346 HostPath: &core.HostPathVolumeSource{ 347 Path: "/foo", 348 Type: newHostPathType(string(core.HostPathDirectory)), 349 }, 350 }, 351 }), 352 }, 353 "bad-volume-zero-capacity": { 354 isExpectedFailure: true, 355 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 356 Capacity: core.ResourceList{ 357 core.ResourceName(core.ResourceStorage): resource.MustParse("0"), 358 }, 359 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 360 PersistentVolumeSource: core.PersistentVolumeSource{ 361 HostPath: &core.HostPathVolumeSource{ 362 Path: "/foo", 363 Type: newHostPathType(string(core.HostPathDirectory)), 364 }, 365 }, 366 }), 367 }, 368 "missing-accessmodes": { 369 isExpectedFailure: true, 370 volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{ 371 Capacity: core.ResourceList{ 372 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 373 }, 374 PersistentVolumeSource: core.PersistentVolumeSource{ 375 HostPath: &core.HostPathVolumeSource{ 376 Path: "/foo", 377 Type: newHostPathType(string(core.HostPathDirectory)), 378 }, 379 }, 380 }), 381 }, 382 "too-many-sources": { 383 isExpectedFailure: true, 384 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 385 Capacity: core.ResourceList{ 386 core.ResourceName(core.ResourceStorage): resource.MustParse("5G"), 387 }, 388 PersistentVolumeSource: core.PersistentVolumeSource{ 389 HostPath: &core.HostPathVolumeSource{ 390 Path: "/foo", 391 Type: newHostPathType(string(core.HostPathDirectory)), 392 }, 393 GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"}, 394 }, 395 }), 396 }, 397 "host mount of / with recycle reclaim policy": { 398 isExpectedFailure: true, 399 volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{ 400 Capacity: core.ResourceList{ 401 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 402 }, 403 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 404 PersistentVolumeSource: core.PersistentVolumeSource{ 405 HostPath: &core.HostPathVolumeSource{ 406 Path: "/", 407 Type: newHostPathType(string(core.HostPathDirectory)), 408 }, 409 }, 410 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle, 411 }), 412 }, 413 "host mount of / with recycle reclaim policy 2": { 414 isExpectedFailure: true, 415 volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{ 416 Capacity: core.ResourceList{ 417 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 418 }, 419 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 420 PersistentVolumeSource: core.PersistentVolumeSource{ 421 HostPath: &core.HostPathVolumeSource{ 422 Path: "/a/..", 423 Type: newHostPathType(string(core.HostPathDirectory)), 424 }, 425 }, 426 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle, 427 }), 428 }, 429 "invalid-storage-class-name": { 430 isExpectedFailure: true, 431 volume: testVolume("invalid-storage-class-name", "", core.PersistentVolumeSpec{ 432 Capacity: core.ResourceList{ 433 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 434 }, 435 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 436 PersistentVolumeSource: core.PersistentVolumeSource{ 437 HostPath: &core.HostPathVolumeSource{ 438 Path: "/foo", 439 Type: newHostPathType(string(core.HostPathDirectory)), 440 }, 441 }, 442 StorageClassName: "-invalid-", 443 }), 444 }, 445 "bad-hostpath-volume-backsteps": { 446 isExpectedFailure: true, 447 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 448 Capacity: core.ResourceList{ 449 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 450 }, 451 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 452 PersistentVolumeSource: core.PersistentVolumeSource{ 453 HostPath: &core.HostPathVolumeSource{ 454 Path: "/foo/..", 455 Type: newHostPathType(string(core.HostPathDirectory)), 456 }, 457 }, 458 StorageClassName: "backstep-hostpath", 459 }), 460 }, 461 "volume-node-affinity": { 462 isExpectedFailure: false, 463 volume: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 464 }, 465 "volume-empty-node-affinity": { 466 isExpectedFailure: true, 467 volume: testVolumeWithNodeAffinity(&core.VolumeNodeAffinity{}), 468 }, 469 "volume-bad-node-affinity": { 470 isExpectedFailure: true, 471 volume: testVolumeWithNodeAffinity( 472 &core.VolumeNodeAffinity{ 473 Required: &core.NodeSelector{ 474 NodeSelectorTerms: []core.NodeSelectorTerm{{ 475 MatchExpressions: []core.NodeSelectorRequirement{{ 476 Operator: core.NodeSelectorOpIn, 477 Values: []string{"test-label-value"}, 478 }}, 479 }}, 480 }, 481 }), 482 }, 483 "invalid-volume-attributes-class-name": { 484 isExpectedFailure: true, 485 enableVolumeAttributesClass: true, 486 volume: testVolume("invalid-volume-attributes-class-name", "", core.PersistentVolumeSpec{ 487 Capacity: core.ResourceList{ 488 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 489 }, 490 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 491 PersistentVolumeSource: core.PersistentVolumeSource{ 492 HostPath: &core.HostPathVolumeSource{ 493 Path: "/foo", 494 Type: newHostPathType(string(core.HostPathDirectory)), 495 }, 496 }, 497 StorageClassName: "invalid", 498 VolumeAttributesClassName: ptr.To("-invalid-"), 499 }), 500 }, 501 "invalid-empty-volume-attributes-class-name": { 502 isExpectedFailure: true, 503 enableVolumeAttributesClass: true, 504 volume: testVolume("invalid-empty-volume-attributes-class-name", "", core.PersistentVolumeSpec{ 505 Capacity: core.ResourceList{ 506 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 507 }, 508 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 509 PersistentVolumeSource: core.PersistentVolumeSource{ 510 HostPath: &core.HostPathVolumeSource{ 511 Path: "/foo", 512 Type: newHostPathType(string(core.HostPathDirectory)), 513 }, 514 }, 515 StorageClassName: "invalid", 516 VolumeAttributesClassName: ptr.To(""), 517 }), 518 }, 519 "volume-with-good-volume-attributes-class-and-matched-volume-resource-when-feature-gate-is-on": { 520 isExpectedFailure: false, 521 enableVolumeAttributesClass: true, 522 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 523 Capacity: core.ResourceList{ 524 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 525 }, 526 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 527 PersistentVolumeSource: core.PersistentVolumeSource{ 528 CSI: &core.CSIPersistentVolumeSource{ 529 Driver: "test-driver", 530 VolumeHandle: "test-123", 531 }, 532 }, 533 StorageClassName: "valid", 534 VolumeAttributesClassName: ptr.To("valid"), 535 }), 536 }, 537 "volume-with-good-volume-attributes-class-and-mismatched-volume-resource-when-feature-gate-is-on": { 538 isExpectedFailure: true, 539 enableVolumeAttributesClass: true, 540 volume: testVolume("foo", "", core.PersistentVolumeSpec{ 541 Capacity: core.ResourceList{ 542 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 543 }, 544 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 545 PersistentVolumeSource: core.PersistentVolumeSource{ 546 HostPath: &core.HostPathVolumeSource{ 547 Path: "/foo", 548 Type: newHostPathType(string(core.HostPathDirectory)), 549 }, 550 }, 551 StorageClassName: "valid", 552 VolumeAttributesClassName: ptr.To("valid"), 553 }), 554 }, 555 } 556 557 for name, scenario := range scenarios { 558 t.Run(name, func(t *testing.T) { 559 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)() 560 561 opts := ValidationOptionsForPersistentVolume(scenario.volume, nil) 562 errs := ValidatePersistentVolume(scenario.volume, opts) 563 if len(errs) == 0 && scenario.isExpectedFailure { 564 t.Errorf("Unexpected success for scenario: %s", name) 565 } 566 if len(errs) > 0 && !scenario.isExpectedFailure { 567 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 568 } 569 }) 570 } 571 572 } 573 574 func TestValidatePersistentVolumeSpec(t *testing.T) { 575 fsmode := core.PersistentVolumeFilesystem 576 blockmode := core.PersistentVolumeBlock 577 scenarios := map[string]struct { 578 isExpectedFailure bool 579 isInlineSpec bool 580 pvSpec *core.PersistentVolumeSpec 581 }{ 582 "pv-pvspec-valid": { 583 isExpectedFailure: false, 584 isInlineSpec: false, 585 pvSpec: &core.PersistentVolumeSpec{ 586 Capacity: core.ResourceList{ 587 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 588 }, 589 StorageClassName: "testclass", 590 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle, 591 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 592 PersistentVolumeSource: core.PersistentVolumeSource{ 593 HostPath: &core.HostPathVolumeSource{ 594 Path: "/foo", 595 Type: newHostPathType(string(core.HostPathDirectory)), 596 }, 597 }, 598 VolumeMode: &fsmode, 599 NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"), 600 }, 601 }, 602 "inline-pvspec-with-capacity": { 603 isExpectedFailure: true, 604 isInlineSpec: true, 605 pvSpec: &core.PersistentVolumeSpec{ 606 Capacity: core.ResourceList{ 607 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 608 }, 609 PersistentVolumeSource: core.PersistentVolumeSource{ 610 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 611 }, 612 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 613 }, 614 }, 615 "inline-pvspec-with-podSec": { 616 isExpectedFailure: true, 617 isInlineSpec: true, 618 pvSpec: &core.PersistentVolumeSpec{ 619 PersistentVolumeSource: core.PersistentVolumeSource{ 620 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 621 }, 622 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 623 StorageClassName: "testclass", 624 }, 625 }, 626 "inline-pvspec-with-non-fs-volume-mode": { 627 isExpectedFailure: true, 628 isInlineSpec: true, 629 pvSpec: &core.PersistentVolumeSpec{ 630 PersistentVolumeSource: core.PersistentVolumeSource{ 631 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 632 }, 633 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 634 VolumeMode: &blockmode, 635 }, 636 }, 637 "inline-pvspec-with-non-retain-reclaim-policy": { 638 isExpectedFailure: true, 639 isInlineSpec: true, 640 pvSpec: &core.PersistentVolumeSpec{ 641 PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle, 642 PersistentVolumeSource: core.PersistentVolumeSource{ 643 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 644 }, 645 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 646 }, 647 }, 648 "inline-pvspec-with-node-affinity": { 649 isExpectedFailure: true, 650 isInlineSpec: true, 651 pvSpec: &core.PersistentVolumeSpec{ 652 PersistentVolumeSource: core.PersistentVolumeSource{ 653 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 654 }, 655 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 656 NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"), 657 }, 658 }, 659 "inline-pvspec-with-non-csi-source": { 660 isExpectedFailure: true, 661 isInlineSpec: true, 662 pvSpec: &core.PersistentVolumeSpec{ 663 PersistentVolumeSource: core.PersistentVolumeSource{ 664 HostPath: &core.HostPathVolumeSource{ 665 Path: "/foo", 666 Type: newHostPathType(string(core.HostPathDirectory)), 667 }, 668 }, 669 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 670 }, 671 }, 672 "inline-pvspec-valid-with-access-modes-and-mount-options": { 673 isExpectedFailure: false, 674 isInlineSpec: true, 675 pvSpec: &core.PersistentVolumeSpec{ 676 PersistentVolumeSource: core.PersistentVolumeSource{ 677 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 678 }, 679 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 680 MountOptions: []string{"soft", "read-write"}, 681 }, 682 }, 683 "inline-pvspec-valid-with-access-modes": { 684 isExpectedFailure: false, 685 isInlineSpec: true, 686 pvSpec: &core.PersistentVolumeSpec{ 687 PersistentVolumeSource: core.PersistentVolumeSource{ 688 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 689 }, 690 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 691 }, 692 }, 693 "inline-pvspec-with-missing-acess-modes": { 694 isExpectedFailure: true, 695 isInlineSpec: true, 696 pvSpec: &core.PersistentVolumeSpec{ 697 PersistentVolumeSource: core.PersistentVolumeSource{ 698 CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 699 }, 700 MountOptions: []string{"soft", "read-write"}, 701 }, 702 }, 703 } 704 for name, scenario := range scenarios { 705 opts := PersistentVolumeSpecValidationOptions{} 706 errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"), opts) 707 if len(errs) == 0 && scenario.isExpectedFailure { 708 t.Errorf("Unexpected success for scenario: %s", name) 709 } 710 if len(errs) > 0 && !scenario.isExpectedFailure { 711 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 712 } 713 } 714 } 715 716 func TestValidatePersistentVolumeSourceUpdate(t *testing.T) { 717 validVolume := testVolume("foo", "", core.PersistentVolumeSpec{ 718 Capacity: core.ResourceList{ 719 core.ResourceName(core.ResourceStorage): resource.MustParse("1G"), 720 }, 721 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 722 PersistentVolumeSource: core.PersistentVolumeSource{ 723 HostPath: &core.HostPathVolumeSource{ 724 Path: "/foo", 725 Type: newHostPathType(string(core.HostPathDirectory)), 726 }, 727 }, 728 StorageClassName: "valid", 729 }) 730 validPvSourceNoUpdate := validVolume.DeepCopy() 731 invalidPvSourceUpdateType := validVolume.DeepCopy() 732 invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{ 733 FlexVolume: &core.FlexPersistentVolumeSource{ 734 Driver: "kubernetes.io/blue", 735 FSType: "ext4", 736 }, 737 } 738 invalidPvSourceUpdateDeep := validVolume.DeepCopy() 739 invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{ 740 HostPath: &core.HostPathVolumeSource{ 741 Path: "/updated", 742 Type: newHostPathType(string(core.HostPathDirectory)), 743 }, 744 } 745 746 validCSIVolume := testVolume("csi-volume", "", core.PersistentVolumeSpec{ 747 Capacity: core.ResourceList{ 748 core.ResourceName(core.ResourceStorage): resource.MustParse("1G"), 749 }, 750 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 751 PersistentVolumeSource: core.PersistentVolumeSource{ 752 CSI: &core.CSIPersistentVolumeSource{ 753 Driver: "come.google.gcepd", 754 VolumeHandle: "foobar", 755 }, 756 }, 757 StorageClassName: "gp2", 758 }) 759 760 expandSecretRef := &core.SecretReference{ 761 Name: "expansion-secret", 762 Namespace: "default", 763 } 764 765 // shortSecretRef refers to the secretRefs which are validated with IsDNS1035Label 766 shortSecretName := "key-name" 767 shortSecretRef := &core.SecretReference{ 768 Name: shortSecretName, 769 Namespace: "default", 770 } 771 772 // longSecretRef refers to the secretRefs which are validated with IsDNS1123Subdomain 773 longSecretName := "key-name.example.com" 774 longSecretRef := &core.SecretReference{ 775 Name: longSecretName, 776 Namespace: "default", 777 } 778 779 // invalidSecrets missing name, namespace and both 780 inValidSecretRef := &core.SecretReference{ 781 Name: "", 782 Namespace: "", 783 } 784 invalidSecretRefmissingName := &core.SecretReference{ 785 Name: "", 786 Namespace: "default", 787 } 788 invalidSecretRefmissingNamespace := &core.SecretReference{ 789 Name: "invalidnamespace", 790 Namespace: "", 791 } 792 793 scenarios := map[string]struct { 794 isExpectedFailure bool 795 oldVolume *core.PersistentVolume 796 newVolume *core.PersistentVolume 797 }{ 798 "condition-no-update": { 799 isExpectedFailure: false, 800 oldVolume: validVolume, 801 newVolume: validPvSourceNoUpdate, 802 }, 803 "condition-update-source-type": { 804 isExpectedFailure: true, 805 oldVolume: validVolume, 806 newVolume: invalidPvSourceUpdateType, 807 }, 808 "condition-update-source-deep": { 809 isExpectedFailure: true, 810 oldVolume: validVolume, 811 newVolume: invalidPvSourceUpdateDeep, 812 }, 813 "csi-expansion-enabled-with-pv-secret": { 814 isExpectedFailure: false, 815 oldVolume: validCSIVolume, 816 newVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"), 817 }, 818 "csi-expansion-enabled-with-old-pv-secret": { 819 isExpectedFailure: true, 820 oldVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"), 821 newVolume: getCSIVolumeWithSecret(validCSIVolume, &core.SecretReference{ 822 Name: "foo-secret", 823 Namespace: "default", 824 }, "controllerExpand"), 825 }, 826 "csi-expansion-enabled-with-shortSecretRef": { 827 isExpectedFailure: false, 828 oldVolume: validCSIVolume, 829 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"), 830 }, 831 "csi-expansion-enabled-with-longSecretRef": { 832 isExpectedFailure: false, // updating controllerExpandSecretRef is allowed only from nil 833 oldVolume: validCSIVolume, 834 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"), 835 }, 836 "csi-expansion-enabled-from-shortSecretRef-to-shortSecretRef": { 837 isExpectedFailure: false, 838 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"), 839 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"), 840 }, 841 "csi-expansion-enabled-from-shortSecretRef-to-longSecretRef": { 842 isExpectedFailure: true, // updating controllerExpandSecretRef is allowed only from nil 843 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"), 844 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"), 845 }, 846 "csi-expansion-enabled-from-longSecretRef-to-longSecretRef": { 847 isExpectedFailure: false, 848 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"), 849 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"), 850 }, 851 "csi-cntrlpublish-enabled-with-shortSecretRef": { 852 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 853 oldVolume: validCSIVolume, 854 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"), 855 }, 856 "csi-cntrlpublish-enabled-with-longSecretRef": { 857 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 858 oldVolume: validCSIVolume, 859 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"), 860 }, 861 "csi-cntrlpublish-enabled-from-shortSecretRef-to-shortSecretRef": { 862 isExpectedFailure: false, 863 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"), 864 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"), 865 }, 866 "csi-cntrlpublish-enabled-from-shortSecretRef-to-longSecretRef": { 867 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 868 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"), 869 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"), 870 }, 871 "csi-cntrlpublish-enabled-from-longSecretRef-to-longSecretRef": { 872 isExpectedFailure: false, 873 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"), 874 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"), 875 }, 876 "csi-nodepublish-enabled-with-shortSecretRef": { 877 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 878 oldVolume: validCSIVolume, 879 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"), 880 }, 881 "csi-nodepublish-enabled-with-longSecretRef": { 882 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 883 oldVolume: validCSIVolume, 884 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"), 885 }, 886 "csi-nodepublish-enabled-from-shortSecretRef-to-shortSecretRef": { 887 isExpectedFailure: false, 888 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"), 889 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"), 890 }, 891 "csi-nodepublish-enabled-from-shortSecretRef-to-longSecretRef": { 892 isExpectedFailure: true, 893 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"), 894 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"), 895 }, 896 "csi-nodepublish-enabled-from-longSecretRef-to-longSecretRef": { 897 isExpectedFailure: false, 898 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"), 899 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"), 900 }, 901 "csi-nodestage-enabled-with-shortSecretRef": { 902 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 903 oldVolume: validCSIVolume, 904 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"), 905 }, 906 "csi-nodestage-enabled-with-longSecretRef": { 907 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 908 oldVolume: validCSIVolume, 909 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"), 910 }, 911 "csi-nodestage-enabled-from-shortSecretRef-to-longSecretRef": { 912 isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid 913 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"), 914 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"), 915 }, 916 917 // At present, there is no validation exist for nodeStage secretRef in 918 // ValidatePersistentVolumeSpec->validateCSIPersistentVolumeSource, due to that, below 919 // checks/validations pass! 920 921 "csi-nodestage-enabled-from-invalidSecretRef-to-invalidSecretRef": { 922 isExpectedFailure: false, 923 oldVolume: getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"), 924 newVolume: getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"), 925 }, 926 "csi-nodestage-enabled-from-invalidSecretRefmissingname-to-invalidSecretRefmissingname": { 927 isExpectedFailure: false, 928 oldVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"), 929 newVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"), 930 }, 931 "csi-nodestage-enabled-from-invalidSecretRefmissingnamespace-to-invalidSecretRefmissingnamespace": { 932 isExpectedFailure: false, 933 oldVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"), 934 newVolume: getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"), 935 }, 936 "csi-nodestage-enabled-from-shortSecretRef-to-shortSecretRef": { 937 isExpectedFailure: false, 938 oldVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"), 939 newVolume: getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"), 940 }, 941 "csi-nodestage-enabled-from-longSecretRef-to-longSecretRef": { 942 isExpectedFailure: false, 943 oldVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"), 944 newVolume: getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"), 945 }, 946 } 947 for name, scenario := range scenarios { 948 opts := ValidationOptionsForPersistentVolume(scenario.newVolume, scenario.oldVolume) 949 errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume, opts) 950 if len(errs) == 0 && scenario.isExpectedFailure { 951 t.Errorf("Unexpected success for scenario: %s", name) 952 } 953 if len(errs) > 0 && !scenario.isExpectedFailure { 954 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 955 } 956 } 957 } 958 959 func TestValidationOptionsForPersistentVolume(t *testing.T) { 960 tests := map[string]struct { 961 oldPv *core.PersistentVolume 962 enableVolumeAttributesClass bool 963 expectValidationOpts PersistentVolumeSpecValidationOptions 964 }{ 965 "nil old pv": { 966 oldPv: nil, 967 expectValidationOpts: PersistentVolumeSpecValidationOptions{}, 968 }, 969 "nil old pv and feature-gate VolumeAttrributesClass is on": { 970 oldPv: nil, 971 enableVolumeAttributesClass: true, 972 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true}, 973 }, 974 "nil old pv and feature-gate VolumeAttrributesClass is off": { 975 oldPv: nil, 976 enableVolumeAttributesClass: false, 977 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: false}, 978 }, 979 "old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is on": { 980 oldPv: &core.PersistentVolume{ 981 Spec: core.PersistentVolumeSpec{ 982 VolumeAttributesClassName: ptr.To("foo"), 983 }, 984 }, 985 enableVolumeAttributesClass: true, 986 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true}, 987 }, 988 "old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is off": { 989 oldPv: &core.PersistentVolume{ 990 Spec: core.PersistentVolumeSpec{ 991 VolumeAttributesClassName: ptr.To("foo"), 992 }, 993 }, 994 enableVolumeAttributesClass: false, 995 expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true}, 996 }, 997 } 998 999 for name, tc := range tests { 1000 t.Run(name, func(t *testing.T) { 1001 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)() 1002 1003 opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv) 1004 if opts != tc.expectValidationOpts { 1005 t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts) 1006 } 1007 }) 1008 } 1009 } 1010 1011 func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference, secretfield string) *core.PersistentVolume { 1012 pvCopy := pv.DeepCopy() 1013 switch secretfield { 1014 case "controllerExpand": 1015 pvCopy.Spec.CSI.ControllerExpandSecretRef = secret 1016 case "controllerPublish": 1017 pvCopy.Spec.CSI.ControllerPublishSecretRef = secret 1018 case "nodePublish": 1019 pvCopy.Spec.CSI.NodePublishSecretRef = secret 1020 case "nodeStage": 1021 pvCopy.Spec.CSI.NodeStageSecretRef = secret 1022 default: 1023 panic("unknown string") 1024 } 1025 1026 return pvCopy 1027 } 1028 1029 func pvcWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaim { 1030 return &core.PersistentVolumeClaim{ 1031 Spec: core.PersistentVolumeClaimSpec{ 1032 VolumeAttributesClassName: vacName, 1033 }, 1034 } 1035 } 1036 1037 func pvcWithDataSource(dataSource *core.TypedLocalObjectReference) *core.PersistentVolumeClaim { 1038 return &core.PersistentVolumeClaim{ 1039 Spec: core.PersistentVolumeClaimSpec{ 1040 DataSource: dataSource, 1041 }, 1042 } 1043 } 1044 func pvcWithDataSourceRef(ref *core.TypedObjectReference) *core.PersistentVolumeClaim { 1045 return &core.PersistentVolumeClaim{ 1046 Spec: core.PersistentVolumeClaimSpec{ 1047 DataSourceRef: ref, 1048 }, 1049 } 1050 } 1051 1052 func pvcTemplateWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimTemplate { 1053 return &core.PersistentVolumeClaimTemplate{ 1054 Spec: core.PersistentVolumeClaimSpec{ 1055 VolumeAttributesClassName: vacName, 1056 }, 1057 } 1058 } 1059 1060 func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec { 1061 return core.PersistentVolumeSpec{ 1062 Capacity: core.ResourceList{ 1063 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1064 }, 1065 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 1066 PersistentVolumeSource: core.PersistentVolumeSource{ 1067 Local: &core.LocalVolumeSource{ 1068 Path: path, 1069 }, 1070 }, 1071 NodeAffinity: affinity, 1072 StorageClassName: "test-storage-class", 1073 } 1074 } 1075 1076 func TestValidateLocalVolumes(t *testing.T) { 1077 scenarios := map[string]struct { 1078 isExpectedFailure bool 1079 volume *core.PersistentVolume 1080 }{ 1081 "alpha invalid local volume nil annotations": { 1082 isExpectedFailure: true, 1083 volume: testVolume( 1084 "invalid-local-volume-nil-annotations", 1085 "", 1086 testLocalVolume("/foo", nil)), 1087 }, 1088 "valid local volume": { 1089 isExpectedFailure: false, 1090 volume: testVolume("valid-local-volume", "", 1091 testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))), 1092 }, 1093 "invalid local volume no node affinity": { 1094 isExpectedFailure: true, 1095 volume: testVolume("invalid-local-volume-no-node-affinity", "", 1096 testLocalVolume("/foo", nil)), 1097 }, 1098 "invalid local volume empty path": { 1099 isExpectedFailure: true, 1100 volume: testVolume("invalid-local-volume-empty-path", "", 1101 testLocalVolume("", simpleVolumeNodeAffinity("foo", "bar"))), 1102 }, 1103 "invalid-local-volume-backsteps": { 1104 isExpectedFailure: true, 1105 volume: testVolume("foo", "", 1106 testLocalVolume("/foo/..", simpleVolumeNodeAffinity("foo", "bar"))), 1107 }, 1108 "valid-local-volume-relative-path": { 1109 isExpectedFailure: false, 1110 volume: testVolume("foo", "", 1111 testLocalVolume("foo", simpleVolumeNodeAffinity("foo", "bar"))), 1112 }, 1113 } 1114 1115 for name, scenario := range scenarios { 1116 opts := ValidationOptionsForPersistentVolume(scenario.volume, nil) 1117 errs := ValidatePersistentVolume(scenario.volume, opts) 1118 if len(errs) == 0 && scenario.isExpectedFailure { 1119 t.Errorf("Unexpected success for scenario: %s", name) 1120 } 1121 if len(errs) > 0 && !scenario.isExpectedFailure { 1122 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 1123 } 1124 } 1125 } 1126 1127 func testVolumeWithVolumeAttributesClass(vacName *string) *core.PersistentVolume { 1128 return testVolume("test-volume-with-volume-attributes-class", "", 1129 core.PersistentVolumeSpec{ 1130 Capacity: core.ResourceList{ 1131 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1132 }, 1133 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 1134 PersistentVolumeSource: core.PersistentVolumeSource{ 1135 CSI: &core.CSIPersistentVolumeSource{ 1136 Driver: "test-driver", 1137 VolumeHandle: "test-123", 1138 }, 1139 }, 1140 StorageClassName: "test-storage-class", 1141 VolumeAttributesClassName: vacName, 1142 }) 1143 } 1144 1145 func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume { 1146 return testVolume("test-affinity-volume", "", 1147 core.PersistentVolumeSpec{ 1148 Capacity: core.ResourceList{ 1149 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1150 }, 1151 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 1152 PersistentVolumeSource: core.PersistentVolumeSource{ 1153 GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ 1154 PDName: "foo", 1155 }, 1156 }, 1157 StorageClassName: "test-storage-class", 1158 NodeAffinity: affinity, 1159 }) 1160 } 1161 1162 func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity { 1163 return &core.VolumeNodeAffinity{ 1164 Required: &core.NodeSelector{ 1165 NodeSelectorTerms: []core.NodeSelectorTerm{{ 1166 MatchExpressions: []core.NodeSelectorRequirement{{ 1167 Key: key, 1168 Operator: core.NodeSelectorOpIn, 1169 Values: []string{value}, 1170 }}, 1171 }}, 1172 }, 1173 } 1174 } 1175 1176 func multipleVolumeNodeAffinity(terms [][]topologyPair) *core.VolumeNodeAffinity { 1177 nodeSelectorTerms := []core.NodeSelectorTerm{} 1178 for _, term := range terms { 1179 matchExpressions := []core.NodeSelectorRequirement{} 1180 for _, topology := range term { 1181 matchExpressions = append(matchExpressions, core.NodeSelectorRequirement{ 1182 Key: topology.key, 1183 Operator: core.NodeSelectorOpIn, 1184 Values: []string{topology.value}, 1185 }) 1186 } 1187 nodeSelectorTerms = append(nodeSelectorTerms, core.NodeSelectorTerm{ 1188 MatchExpressions: matchExpressions, 1189 }) 1190 } 1191 1192 return &core.VolumeNodeAffinity{ 1193 Required: &core.NodeSelector{ 1194 NodeSelectorTerms: nodeSelectorTerms, 1195 }, 1196 } 1197 } 1198 1199 func TestValidateVolumeNodeAffinityUpdate(t *testing.T) { 1200 scenarios := map[string]struct { 1201 isExpectedFailure bool 1202 oldPV *core.PersistentVolume 1203 newPV *core.PersistentVolume 1204 }{ 1205 "nil-nothing-changed": { 1206 isExpectedFailure: false, 1207 oldPV: testVolumeWithNodeAffinity(nil), 1208 newPV: testVolumeWithNodeAffinity(nil), 1209 }, 1210 "affinity-nothing-changed": { 1211 isExpectedFailure: false, 1212 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1213 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1214 }, 1215 "affinity-changed": { 1216 isExpectedFailure: true, 1217 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1218 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar2")), 1219 }, 1220 "affinity-non-beta-label-changed": { 1221 isExpectedFailure: true, 1222 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1223 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo2", "bar")), 1224 }, 1225 "affinity-zone-beta-unchanged": { 1226 isExpectedFailure: false, 1227 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")), 1228 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")), 1229 }, 1230 "affinity-zone-beta-label-to-GA": { 1231 isExpectedFailure: false, 1232 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")), 1233 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")), 1234 }, 1235 "affinity-zone-beta-label-to-non-GA": { 1236 isExpectedFailure: true, 1237 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")), 1238 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1239 }, 1240 "affinity-zone-GA-label-changed": { 1241 isExpectedFailure: true, 1242 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")), 1243 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")), 1244 }, 1245 "affinity-region-beta-unchanged": { 1246 isExpectedFailure: false, 1247 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")), 1248 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")), 1249 }, 1250 "affinity-region-beta-label-to-GA": { 1251 isExpectedFailure: false, 1252 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")), 1253 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")), 1254 }, 1255 "affinity-region-beta-label-to-non-GA": { 1256 isExpectedFailure: true, 1257 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")), 1258 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1259 }, 1260 "affinity-region-GA-label-changed": { 1261 isExpectedFailure: true, 1262 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")), 1263 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")), 1264 }, 1265 "affinity-os-beta-label-unchanged": { 1266 isExpectedFailure: false, 1267 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")), 1268 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")), 1269 }, 1270 "affinity-os-beta-label-to-GA": { 1271 isExpectedFailure: false, 1272 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")), 1273 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")), 1274 }, 1275 "affinity-os-beta-label-to-non-GA": { 1276 isExpectedFailure: true, 1277 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")), 1278 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1279 }, 1280 "affinity-os-GA-label-changed": { 1281 isExpectedFailure: true, 1282 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")), 1283 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")), 1284 }, 1285 "affinity-arch-beta-label-unchanged": { 1286 isExpectedFailure: false, 1287 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")), 1288 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")), 1289 }, 1290 "affinity-arch-beta-label-to-GA": { 1291 isExpectedFailure: false, 1292 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")), 1293 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")), 1294 }, 1295 "affinity-arch-beta-label-to-non-GA": { 1296 isExpectedFailure: true, 1297 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")), 1298 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1299 }, 1300 "affinity-arch-GA-label-changed": { 1301 isExpectedFailure: true, 1302 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")), 1303 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")), 1304 }, 1305 "affinity-instanceType-beta-label-unchanged": { 1306 isExpectedFailure: false, 1307 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")), 1308 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")), 1309 }, 1310 "affinity-instanceType-beta-label-to-GA": { 1311 isExpectedFailure: false, 1312 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")), 1313 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")), 1314 }, 1315 "affinity-instanceType-beta-label-to-non-GA": { 1316 isExpectedFailure: true, 1317 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")), 1318 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1319 }, 1320 "affinity-instanceType-GA-label-changed": { 1321 isExpectedFailure: true, 1322 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")), 1323 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")), 1324 }, 1325 "affinity-same-terms-expressions-length-beta-to-GA-partially-changed": { 1326 isExpectedFailure: false, 1327 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1328 topologyPair{"foo", "bar"}, 1329 }, { 1330 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1331 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, 1332 }, { 1333 topologyPair{kubeletapis.LabelOS, "bar"}, 1334 topologyPair{kubeletapis.LabelArch, "bar"}, 1335 topologyPair{v1.LabelInstanceType, "bar"}, 1336 }, 1337 })), 1338 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1339 topologyPair{"foo", "bar"}, 1340 }, { 1341 topologyPair{v1.LabelTopologyZone, "bar"}, 1342 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, 1343 }, { 1344 topologyPair{kubeletapis.LabelOS, "bar"}, 1345 topologyPair{v1.LabelArchStable, "bar"}, 1346 topologyPair{v1.LabelInstanceTypeStable, "bar"}, 1347 }, 1348 })), 1349 }, 1350 "affinity-same-terms-expressions-length-beta-to-non-GA-partially-changed": { 1351 isExpectedFailure: true, 1352 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1353 topologyPair{"foo", "bar"}, 1354 }, { 1355 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1356 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, 1357 }, 1358 })), 1359 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1360 topologyPair{"foo", "bar"}, 1361 }, { 1362 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1363 topologyPair{"foo", "bar"}, 1364 }, 1365 })), 1366 }, 1367 "affinity-same-terms-expressions-length-GA-partially-changed": { 1368 isExpectedFailure: true, 1369 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1370 topologyPair{"foo", "bar"}, 1371 }, { 1372 topologyPair{v1.LabelTopologyZone, "bar"}, 1373 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1374 topologyPair{v1.LabelOSStable, "bar"}, 1375 }, 1376 })), 1377 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1378 topologyPair{"foo", "bar"}, 1379 }, { 1380 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1381 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1382 topologyPair{v1.LabelOSStable, "bar"}, 1383 }, 1384 })), 1385 }, 1386 "affinity-same-terms-expressions-length-beta-fully-changed": { 1387 isExpectedFailure: false, 1388 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1389 topologyPair{"foo", "bar"}, 1390 }, { 1391 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1392 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, 1393 }, { 1394 topologyPair{kubeletapis.LabelOS, "bar"}, 1395 topologyPair{kubeletapis.LabelArch, "bar"}, 1396 topologyPair{v1.LabelInstanceType, "bar"}, 1397 }, 1398 })), 1399 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1400 topologyPair{"foo", "bar"}, 1401 }, { 1402 topologyPair{v1.LabelTopologyZone, "bar"}, 1403 topologyPair{v1.LabelTopologyRegion, "bar"}, 1404 }, { 1405 topologyPair{v1.LabelOSStable, "bar"}, 1406 topologyPair{v1.LabelArchStable, "bar"}, 1407 topologyPair{v1.LabelInstanceTypeStable, "bar"}, 1408 }, 1409 })), 1410 }, 1411 "affinity-same-terms-expressions-length-beta-GA-mixed-fully-changed": { 1412 isExpectedFailure: true, 1413 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1414 topologyPair{"foo", "bar"}, 1415 }, { 1416 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1417 topologyPair{v1.LabelTopologyZone, "bar"}, 1418 }, 1419 })), 1420 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1421 topologyPair{"foo", "bar"}, 1422 }, { 1423 topologyPair{v1.LabelTopologyZone, "bar"}, 1424 topologyPair{v1.LabelFailureDomainBetaZone, "bar2"}, 1425 }, 1426 })), 1427 }, 1428 "affinity-same-terms-length-different-expressions-length-beta-changed": { 1429 isExpectedFailure: true, 1430 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1431 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1432 }, 1433 })), 1434 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1435 topologyPair{v1.LabelTopologyZone, "bar"}, 1436 topologyPair{v1.LabelFailureDomainBetaRegion, "bar"}, 1437 }, 1438 })), 1439 }, 1440 "affinity-different-terms-expressions-length-beta-changed": { 1441 isExpectedFailure: true, 1442 oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1443 topologyPair{v1.LabelFailureDomainBetaZone, "bar"}, 1444 }, 1445 })), 1446 newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{ 1447 topologyPair{v1.LabelTopologyZone, "bar"}, 1448 }, { 1449 topologyPair{v1.LabelArchStable, "bar"}, 1450 }, 1451 })), 1452 }, 1453 "nil-to-obj": { 1454 isExpectedFailure: false, 1455 oldPV: testVolumeWithNodeAffinity(nil), 1456 newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1457 }, 1458 "obj-to-nil": { 1459 isExpectedFailure: true, 1460 oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), 1461 newPV: testVolumeWithNodeAffinity(nil), 1462 }, 1463 } 1464 1465 for name, scenario := range scenarios { 1466 originalNewPV := scenario.newPV.DeepCopy() 1467 originalOldPV := scenario.oldPV.DeepCopy() 1468 opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV) 1469 errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts) 1470 if len(errs) == 0 && scenario.isExpectedFailure { 1471 t.Errorf("Unexpected success for scenario: %s", name) 1472 } 1473 if len(errs) > 0 && !scenario.isExpectedFailure { 1474 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 1475 } 1476 if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 { 1477 t.Errorf("newPV was modified: %s", diff) 1478 } 1479 if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 { 1480 t.Errorf("oldPV was modified: %s", diff) 1481 } 1482 } 1483 } 1484 1485 func TestValidatePeristentVolumeAttributesClassUpdate(t *testing.T) { 1486 scenarios := map[string]struct { 1487 isExpectedFailure bool 1488 enableVolumeAttributesClass bool 1489 oldPV *core.PersistentVolume 1490 newPV *core.PersistentVolume 1491 }{ 1492 "nil-nothing-changed": { 1493 isExpectedFailure: false, 1494 enableVolumeAttributesClass: true, 1495 oldPV: testVolumeWithVolumeAttributesClass(nil), 1496 newPV: testVolumeWithVolumeAttributesClass(nil), 1497 }, 1498 "vac-nothing-changed": { 1499 isExpectedFailure: false, 1500 enableVolumeAttributesClass: true, 1501 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1502 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1503 }, 1504 "vac-changed": { 1505 isExpectedFailure: false, 1506 enableVolumeAttributesClass: true, 1507 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1508 newPV: testVolumeWithVolumeAttributesClass(ptr.To("bar")), 1509 }, 1510 "nil-to-string": { 1511 isExpectedFailure: false, 1512 enableVolumeAttributesClass: true, 1513 oldPV: testVolumeWithVolumeAttributesClass(nil), 1514 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1515 }, 1516 "nil-to-empty-string": { 1517 isExpectedFailure: true, 1518 enableVolumeAttributesClass: true, 1519 oldPV: testVolumeWithVolumeAttributesClass(nil), 1520 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")), 1521 }, 1522 "string-to-nil": { 1523 isExpectedFailure: true, 1524 enableVolumeAttributesClass: true, 1525 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1526 newPV: testVolumeWithVolumeAttributesClass(nil), 1527 }, 1528 "string-to-empty-string": { 1529 isExpectedFailure: true, 1530 enableVolumeAttributesClass: true, 1531 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1532 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")), 1533 }, 1534 "vac-nothing-changed-when-feature-gate-is-off": { 1535 isExpectedFailure: false, 1536 enableVolumeAttributesClass: false, 1537 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1538 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1539 }, 1540 "vac-changed-when-feature-gate-is-off": { 1541 isExpectedFailure: true, 1542 enableVolumeAttributesClass: false, 1543 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1544 newPV: testVolumeWithVolumeAttributesClass(ptr.To("bar")), 1545 }, 1546 "nil-to-string-when-feature-gate-is-off": { 1547 isExpectedFailure: true, 1548 enableVolumeAttributesClass: false, 1549 oldPV: testVolumeWithVolumeAttributesClass(nil), 1550 newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1551 }, 1552 "nil-to-empty-string-when-feature-gate-is-off": { 1553 isExpectedFailure: true, 1554 enableVolumeAttributesClass: false, 1555 oldPV: testVolumeWithVolumeAttributesClass(nil), 1556 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")), 1557 }, 1558 "string-to-nil-when-feature-gate-is-off": { 1559 isExpectedFailure: true, 1560 enableVolumeAttributesClass: false, 1561 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1562 newPV: testVolumeWithVolumeAttributesClass(nil), 1563 }, 1564 "string-to-empty-string-when-feature-gate-is-off": { 1565 isExpectedFailure: true, 1566 enableVolumeAttributesClass: false, 1567 oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")), 1568 newPV: testVolumeWithVolumeAttributesClass(ptr.To("")), 1569 }, 1570 } 1571 1572 for name, scenario := range scenarios { 1573 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)() 1574 1575 originalNewPV := scenario.newPV.DeepCopy() 1576 originalOldPV := scenario.oldPV.DeepCopy() 1577 opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV) 1578 errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts) 1579 if len(errs) == 0 && scenario.isExpectedFailure { 1580 t.Errorf("Unexpected success for scenario: %s", name) 1581 } 1582 if len(errs) > 0 && !scenario.isExpectedFailure { 1583 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 1584 } 1585 if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 { 1586 t.Errorf("newPV was modified: %s", diff) 1587 } 1588 if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 { 1589 t.Errorf("oldPV was modified: %s", diff) 1590 } 1591 } 1592 } 1593 1594 func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1595 return &core.PersistentVolumeClaim{ 1596 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 1597 Spec: spec, 1598 } 1599 } 1600 1601 func testVolumeClaimWithStatus( 1602 name, namespace string, 1603 spec core.PersistentVolumeClaimSpec, 1604 status core.PersistentVolumeClaimStatus) *core.PersistentVolumeClaim { 1605 return &core.PersistentVolumeClaim{ 1606 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 1607 Spec: spec, 1608 Status: status, 1609 } 1610 } 1611 1612 func testVolumeClaimStorageClass(name string, namespace string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1613 annotations := map[string]string{ 1614 v1.BetaStorageClassAnnotation: annval, 1615 } 1616 1617 return &core.PersistentVolumeClaim{ 1618 ObjectMeta: metav1.ObjectMeta{ 1619 Name: name, 1620 Namespace: namespace, 1621 Annotations: annotations, 1622 }, 1623 Spec: spec, 1624 } 1625 } 1626 1627 func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1628 annotations := map[string]string{ 1629 ann: annval, 1630 } 1631 1632 return &core.PersistentVolumeClaim{ 1633 ObjectMeta: metav1.ObjectMeta{ 1634 Name: name, 1635 Namespace: namespace, 1636 Annotations: annotations, 1637 }, 1638 Spec: spec, 1639 } 1640 } 1641 1642 func testVolumeClaimStorageClassInSpec(name, namespace, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1643 spec.StorageClassName = &scName 1644 return &core.PersistentVolumeClaim{ 1645 ObjectMeta: metav1.ObjectMeta{ 1646 Name: name, 1647 Namespace: namespace, 1648 }, 1649 Spec: spec, 1650 } 1651 } 1652 1653 func testVolumeClaimStorageClassNilInSpec(name, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1654 spec.StorageClassName = nil 1655 return &core.PersistentVolumeClaim{ 1656 ObjectMeta: metav1.ObjectMeta{ 1657 Name: name, 1658 Namespace: namespace, 1659 }, 1660 Spec: spec, 1661 } 1662 } 1663 1664 func testVolumeSnapshotDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec { 1665 scName := "csi-plugin" 1666 dataSourceInSpec := core.PersistentVolumeClaimSpec{ 1667 AccessModes: []core.PersistentVolumeAccessMode{ 1668 core.ReadOnlyMany, 1669 }, 1670 Resources: core.VolumeResourceRequirements{ 1671 Requests: core.ResourceList{ 1672 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1673 }, 1674 }, 1675 StorageClassName: &scName, 1676 DataSource: &core.TypedLocalObjectReference{ 1677 APIGroup: &apiGroup, 1678 Kind: kind, 1679 Name: name, 1680 }, 1681 } 1682 1683 return &dataSourceInSpec 1684 } 1685 1686 func TestAlphaVolumeSnapshotDataSource(t *testing.T) { 1687 successTestCases := []core.PersistentVolumeClaimSpec{ 1688 *testVolumeSnapshotDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), 1689 } 1690 failedTestCases := []core.PersistentVolumeClaimSpec{ 1691 *testVolumeSnapshotDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), 1692 *testVolumeSnapshotDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), 1693 } 1694 1695 for _, tc := range successTestCases { 1696 opts := PersistentVolumeClaimSpecValidationOptions{} 1697 if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) != 0 { 1698 t.Errorf("expected success: %v", errs) 1699 } 1700 } 1701 for _, tc := range failedTestCases { 1702 opts := PersistentVolumeClaimSpecValidationOptions{} 1703 if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) == 0 { 1704 t.Errorf("expected failure: %v", errs) 1705 } 1706 } 1707 } 1708 1709 func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1710 spec.StorageClassName = &scName 1711 return &core.PersistentVolumeClaim{ 1712 ObjectMeta: metav1.ObjectMeta{ 1713 Name: name, 1714 Namespace: namespace, 1715 Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn}, 1716 }, 1717 Spec: spec, 1718 } 1719 } 1720 1721 func testVolumeClaimStorageClassInAnnotationAndNilInSpec(name, namespace, scNameInAnn string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim { 1722 spec.StorageClassName = nil 1723 return &core.PersistentVolumeClaim{ 1724 ObjectMeta: metav1.ObjectMeta{ 1725 Name: name, 1726 Namespace: namespace, 1727 Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn}, 1728 }, 1729 Spec: spec, 1730 } 1731 } 1732 1733 func testValidatePVC(t *testing.T, ephemeral bool) { 1734 invalidClassName := "-invalid-" 1735 validClassName := "valid" 1736 invalidAPIGroup := "^invalid" 1737 invalidMode := core.PersistentVolumeMode("fakeVolumeMode") 1738 validMode := core.PersistentVolumeFilesystem 1739 goodName := "foo" 1740 goodNS := "ns" 1741 if ephemeral { 1742 // Must be empty for ephemeral inline volumes. 1743 goodName = "" 1744 goodNS = "" 1745 } 1746 goodClaimSpec := core.PersistentVolumeClaimSpec{ 1747 Selector: &metav1.LabelSelector{ 1748 MatchExpressions: []metav1.LabelSelectorRequirement{{ 1749 Key: "key2", 1750 Operator: "Exists", 1751 }}, 1752 }, 1753 AccessModes: []core.PersistentVolumeAccessMode{ 1754 core.ReadWriteOnce, 1755 core.ReadOnlyMany, 1756 }, 1757 Resources: core.VolumeResourceRequirements{ 1758 Requests: core.ResourceList{ 1759 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1760 }, 1761 }, 1762 StorageClassName: &validClassName, 1763 VolumeMode: &validMode, 1764 } 1765 now := metav1.Now() 1766 ten := int64(10) 1767 1768 scenarios := map[string]struct { 1769 isExpectedFailure bool 1770 enableVolumeAttributesClass bool 1771 claim *core.PersistentVolumeClaim 1772 }{ 1773 "good-claim": { 1774 isExpectedFailure: false, 1775 claim: testVolumeClaim(goodName, goodNS, goodClaimSpec), 1776 }, 1777 "missing-name": { 1778 isExpectedFailure: !ephemeral, 1779 claim: testVolumeClaim("", goodNS, goodClaimSpec), 1780 }, 1781 "missing-namespace": { 1782 isExpectedFailure: !ephemeral, 1783 claim: testVolumeClaim(goodName, "", goodClaimSpec), 1784 }, 1785 "with-generate-name": { 1786 isExpectedFailure: ephemeral, 1787 claim: func() *core.PersistentVolumeClaim { 1788 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1789 claim.GenerateName = "pvc-" 1790 return claim 1791 }(), 1792 }, 1793 "with-uid": { 1794 isExpectedFailure: ephemeral, 1795 claim: func() *core.PersistentVolumeClaim { 1796 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1797 claim.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d" 1798 return claim 1799 }(), 1800 }, 1801 "with-resource-version": { 1802 isExpectedFailure: ephemeral, 1803 claim: func() *core.PersistentVolumeClaim { 1804 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1805 claim.ResourceVersion = "1" 1806 return claim 1807 }(), 1808 }, 1809 "with-generation": { 1810 isExpectedFailure: ephemeral, 1811 claim: func() *core.PersistentVolumeClaim { 1812 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1813 claim.Generation = 100 1814 return claim 1815 }(), 1816 }, 1817 "with-creation-timestamp": { 1818 isExpectedFailure: ephemeral, 1819 claim: func() *core.PersistentVolumeClaim { 1820 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1821 claim.CreationTimestamp = now 1822 return claim 1823 }(), 1824 }, 1825 "with-deletion-grace-period-seconds": { 1826 isExpectedFailure: ephemeral, 1827 claim: func() *core.PersistentVolumeClaim { 1828 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1829 claim.DeletionGracePeriodSeconds = &ten 1830 return claim 1831 }(), 1832 }, 1833 "with-owner-references": { 1834 isExpectedFailure: ephemeral, 1835 claim: func() *core.PersistentVolumeClaim { 1836 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1837 claim.OwnerReferences = []metav1.OwnerReference{{ 1838 APIVersion: "v1", 1839 Kind: "pod", 1840 Name: "foo", 1841 UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d", 1842 }, 1843 } 1844 return claim 1845 }(), 1846 }, 1847 "with-finalizers": { 1848 isExpectedFailure: ephemeral, 1849 claim: func() *core.PersistentVolumeClaim { 1850 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1851 claim.Finalizers = []string{ 1852 "example.com/foo", 1853 } 1854 return claim 1855 }(), 1856 }, 1857 "with-managed-fields": { 1858 isExpectedFailure: ephemeral, 1859 claim: func() *core.PersistentVolumeClaim { 1860 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1861 claim.ManagedFields = []metav1.ManagedFieldsEntry{{ 1862 FieldsType: "FieldsV1", 1863 Operation: "Apply", 1864 APIVersion: "apps/v1", 1865 Manager: "foo", 1866 }, 1867 } 1868 return claim 1869 }(), 1870 }, 1871 "with-good-labels": { 1872 claim: func() *core.PersistentVolumeClaim { 1873 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1874 claim.Labels = map[string]string{ 1875 "apps.kubernetes.io/name": "test", 1876 } 1877 return claim 1878 }(), 1879 }, 1880 "with-bad-labels": { 1881 isExpectedFailure: true, 1882 claim: func() *core.PersistentVolumeClaim { 1883 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1884 claim.Labels = map[string]string{ 1885 "hello-world": "hyphen not allowed", 1886 } 1887 return claim 1888 }(), 1889 }, 1890 "with-good-annotations": { 1891 claim: func() *core.PersistentVolumeClaim { 1892 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1893 claim.Labels = map[string]string{ 1894 "foo": "bar", 1895 } 1896 return claim 1897 }(), 1898 }, 1899 "with-bad-annotations": { 1900 isExpectedFailure: true, 1901 claim: func() *core.PersistentVolumeClaim { 1902 claim := testVolumeClaim(goodName, goodNS, goodClaimSpec) 1903 claim.Labels = map[string]string{ 1904 "hello-world": "hyphen not allowed", 1905 } 1906 return claim 1907 }(), 1908 }, 1909 "with-read-write-once-pod": { 1910 isExpectedFailure: false, 1911 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1912 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"}, 1913 Resources: core.VolumeResourceRequirements{ 1914 Requests: core.ResourceList{ 1915 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1916 }, 1917 }, 1918 }), 1919 }, 1920 "with-read-write-once-pod-and-others": { 1921 isExpectedFailure: true, 1922 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1923 AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"}, 1924 Resources: core.VolumeResourceRequirements{ 1925 Requests: core.ResourceList{ 1926 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1927 }, 1928 }, 1929 }), 1930 }, 1931 "invalid-claim-zero-capacity": { 1932 isExpectedFailure: true, 1933 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1934 Selector: &metav1.LabelSelector{ 1935 MatchExpressions: []metav1.LabelSelectorRequirement{{ 1936 Key: "key2", 1937 Operator: "Exists", 1938 }}, 1939 }, 1940 AccessModes: []core.PersistentVolumeAccessMode{ 1941 core.ReadWriteOnce, 1942 core.ReadOnlyMany, 1943 }, 1944 Resources: core.VolumeResourceRequirements{ 1945 Requests: core.ResourceList{ 1946 core.ResourceName(core.ResourceStorage): resource.MustParse("0G"), 1947 }, 1948 }, 1949 StorageClassName: &validClassName, 1950 }), 1951 }, 1952 "invalid-label-selector": { 1953 isExpectedFailure: true, 1954 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1955 Selector: &metav1.LabelSelector{ 1956 MatchExpressions: []metav1.LabelSelectorRequirement{{ 1957 Key: "key2", 1958 Operator: "InvalidOp", 1959 Values: []string{"value1", "value2"}, 1960 }}, 1961 }, 1962 AccessModes: []core.PersistentVolumeAccessMode{ 1963 core.ReadWriteOnce, 1964 core.ReadOnlyMany, 1965 }, 1966 Resources: core.VolumeResourceRequirements{ 1967 Requests: core.ResourceList{ 1968 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1969 }, 1970 }, 1971 }), 1972 }, 1973 "invalid-accessmode": { 1974 isExpectedFailure: true, 1975 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1976 AccessModes: []core.PersistentVolumeAccessMode{"fakemode"}, 1977 Resources: core.VolumeResourceRequirements{ 1978 Requests: core.ResourceList{ 1979 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1980 }, 1981 }, 1982 }), 1983 }, 1984 "no-access-modes": { 1985 isExpectedFailure: true, 1986 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1987 Resources: core.VolumeResourceRequirements{ 1988 Requests: core.ResourceList{ 1989 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 1990 }, 1991 }, 1992 }), 1993 }, 1994 "no-resource-requests": { 1995 isExpectedFailure: true, 1996 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 1997 AccessModes: []core.PersistentVolumeAccessMode{ 1998 core.ReadWriteOnce, 1999 }, 2000 }), 2001 }, 2002 "invalid-resource-requests": { 2003 isExpectedFailure: true, 2004 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2005 AccessModes: []core.PersistentVolumeAccessMode{ 2006 core.ReadWriteOnce, 2007 }, 2008 Resources: core.VolumeResourceRequirements{ 2009 Requests: core.ResourceList{ 2010 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 2011 }, 2012 }, 2013 }), 2014 }, 2015 "negative-storage-request": { 2016 isExpectedFailure: true, 2017 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2018 Selector: &metav1.LabelSelector{ 2019 MatchExpressions: []metav1.LabelSelectorRequirement{{ 2020 Key: "key2", 2021 Operator: "Exists", 2022 }}, 2023 }, 2024 AccessModes: []core.PersistentVolumeAccessMode{ 2025 core.ReadWriteOnce, 2026 core.ReadOnlyMany, 2027 }, 2028 Resources: core.VolumeResourceRequirements{ 2029 Requests: core.ResourceList{ 2030 core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"), 2031 }, 2032 }, 2033 }), 2034 }, 2035 "zero-storage-request": { 2036 isExpectedFailure: true, 2037 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2038 Selector: &metav1.LabelSelector{ 2039 MatchExpressions: []metav1.LabelSelectorRequirement{{ 2040 Key: "key2", 2041 Operator: "Exists", 2042 }}, 2043 }, 2044 AccessModes: []core.PersistentVolumeAccessMode{ 2045 core.ReadWriteOnce, 2046 core.ReadOnlyMany, 2047 }, 2048 Resources: core.VolumeResourceRequirements{ 2049 Requests: core.ResourceList{ 2050 core.ResourceName(core.ResourceStorage): resource.MustParse("0G"), 2051 }, 2052 }, 2053 }), 2054 }, 2055 "invalid-storage-class-name": { 2056 isExpectedFailure: true, 2057 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2058 Selector: &metav1.LabelSelector{ 2059 MatchExpressions: []metav1.LabelSelectorRequirement{{ 2060 Key: "key2", 2061 Operator: "Exists", 2062 }}, 2063 }, 2064 AccessModes: []core.PersistentVolumeAccessMode{ 2065 core.ReadWriteOnce, 2066 core.ReadOnlyMany, 2067 }, 2068 Resources: core.VolumeResourceRequirements{ 2069 Requests: core.ResourceList{ 2070 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2071 }, 2072 }, 2073 StorageClassName: &invalidClassName, 2074 }), 2075 }, 2076 "invalid-volume-mode": { 2077 isExpectedFailure: true, 2078 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2079 AccessModes: []core.PersistentVolumeAccessMode{ 2080 core.ReadWriteOnce, 2081 core.ReadOnlyMany, 2082 }, 2083 Resources: core.VolumeResourceRequirements{ 2084 Requests: core.ResourceList{ 2085 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2086 }, 2087 }, 2088 VolumeMode: &invalidMode, 2089 }), 2090 }, 2091 "mismatch-data-source-and-ref": { 2092 isExpectedFailure: true, 2093 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2094 AccessModes: []core.PersistentVolumeAccessMode{ 2095 core.ReadWriteOnce, 2096 }, 2097 Resources: core.VolumeResourceRequirements{ 2098 Requests: core.ResourceList{ 2099 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2100 }, 2101 }, 2102 DataSource: &core.TypedLocalObjectReference{ 2103 Kind: "PersistentVolumeClaim", 2104 Name: "pvc1", 2105 }, 2106 DataSourceRef: &core.TypedObjectReference{ 2107 Kind: "PersistentVolumeClaim", 2108 Name: "pvc2", 2109 }, 2110 }), 2111 }, 2112 "invaild-apigroup-in-data-source": { 2113 isExpectedFailure: true, 2114 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2115 AccessModes: []core.PersistentVolumeAccessMode{ 2116 core.ReadWriteOnce, 2117 }, 2118 Resources: core.VolumeResourceRequirements{ 2119 Requests: core.ResourceList{ 2120 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2121 }, 2122 }, 2123 DataSource: &core.TypedLocalObjectReference{ 2124 APIGroup: &invalidAPIGroup, 2125 Kind: "Foo", 2126 Name: "foo1", 2127 }, 2128 }), 2129 }, 2130 "invaild-apigroup-in-data-source-ref": { 2131 isExpectedFailure: true, 2132 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2133 AccessModes: []core.PersistentVolumeAccessMode{ 2134 core.ReadWriteOnce, 2135 }, 2136 Resources: core.VolumeResourceRequirements{ 2137 Requests: core.ResourceList{ 2138 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2139 }, 2140 }, 2141 DataSourceRef: &core.TypedObjectReference{ 2142 APIGroup: &invalidAPIGroup, 2143 Kind: "Foo", 2144 Name: "foo1", 2145 }, 2146 }), 2147 }, 2148 "invalid-volume-attributes-class-name": { 2149 isExpectedFailure: true, 2150 enableVolumeAttributesClass: true, 2151 claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{ 2152 Selector: &metav1.LabelSelector{ 2153 MatchExpressions: []metav1.LabelSelectorRequirement{{ 2154 Key: "key2", 2155 Operator: "Exists", 2156 }}, 2157 }, 2158 AccessModes: []core.PersistentVolumeAccessMode{ 2159 core.ReadWriteOnce, 2160 core.ReadOnlyMany, 2161 }, 2162 Resources: core.VolumeResourceRequirements{ 2163 Requests: core.ResourceList{ 2164 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2165 }, 2166 }, 2167 VolumeAttributesClassName: &invalidClassName, 2168 }), 2169 }, 2170 } 2171 2172 for name, scenario := range scenarios { 2173 t.Run(name, func(t *testing.T) { 2174 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)() 2175 2176 var errs field.ErrorList 2177 if ephemeral { 2178 volumes := []core.Volume{{ 2179 Name: "foo", 2180 VolumeSource: core.VolumeSource{ 2181 Ephemeral: &core.EphemeralVolumeSource{ 2182 VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{ 2183 ObjectMeta: scenario.claim.ObjectMeta, 2184 Spec: scenario.claim.Spec, 2185 }, 2186 }, 2187 }, 2188 }, 2189 } 2190 opts := PodValidationOptions{} 2191 _, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts) 2192 } else { 2193 opts := ValidationOptionsForPersistentVolumeClaim(scenario.claim, nil) 2194 errs = ValidatePersistentVolumeClaim(scenario.claim, opts) 2195 } 2196 if len(errs) == 0 && scenario.isExpectedFailure { 2197 t.Error("Unexpected success for scenario") 2198 } 2199 if len(errs) > 0 && !scenario.isExpectedFailure { 2200 t.Errorf("Unexpected failure: %+v", errs) 2201 } 2202 }) 2203 } 2204 } 2205 2206 func TestValidatePersistentVolumeClaim(t *testing.T) { 2207 testValidatePVC(t, false) 2208 } 2209 2210 func TestValidateEphemeralVolume(t *testing.T) { 2211 testValidatePVC(t, true) 2212 } 2213 2214 func TestAlphaPVVolumeModeUpdate(t *testing.T) { 2215 block := core.PersistentVolumeBlock 2216 file := core.PersistentVolumeFilesystem 2217 2218 scenarios := map[string]struct { 2219 isExpectedFailure bool 2220 oldPV *core.PersistentVolume 2221 newPV *core.PersistentVolume 2222 }{ 2223 "valid-update-volume-mode-block-to-block": { 2224 isExpectedFailure: false, 2225 oldPV: createTestVolModePV(&block), 2226 newPV: createTestVolModePV(&block), 2227 }, 2228 "valid-update-volume-mode-file-to-file": { 2229 isExpectedFailure: false, 2230 oldPV: createTestVolModePV(&file), 2231 newPV: createTestVolModePV(&file), 2232 }, 2233 "invalid-update-volume-mode-to-block": { 2234 isExpectedFailure: true, 2235 oldPV: createTestVolModePV(&file), 2236 newPV: createTestVolModePV(&block), 2237 }, 2238 "invalid-update-volume-mode-to-file": { 2239 isExpectedFailure: true, 2240 oldPV: createTestVolModePV(&block), 2241 newPV: createTestVolModePV(&file), 2242 }, 2243 "invalid-update-volume-mode-nil-to-file": { 2244 isExpectedFailure: true, 2245 oldPV: createTestVolModePV(nil), 2246 newPV: createTestVolModePV(&file), 2247 }, 2248 "invalid-update-volume-mode-nil-to-block": { 2249 isExpectedFailure: true, 2250 oldPV: createTestVolModePV(nil), 2251 newPV: createTestVolModePV(&block), 2252 }, 2253 "invalid-update-volume-mode-file-to-nil": { 2254 isExpectedFailure: true, 2255 oldPV: createTestVolModePV(&file), 2256 newPV: createTestVolModePV(nil), 2257 }, 2258 "invalid-update-volume-mode-block-to-nil": { 2259 isExpectedFailure: true, 2260 oldPV: createTestVolModePV(&block), 2261 newPV: createTestVolModePV(nil), 2262 }, 2263 "invalid-update-volume-mode-nil-to-nil": { 2264 isExpectedFailure: false, 2265 oldPV: createTestVolModePV(nil), 2266 newPV: createTestVolModePV(nil), 2267 }, 2268 "invalid-update-volume-mode-empty-to-mode": { 2269 isExpectedFailure: true, 2270 oldPV: createTestPV(), 2271 newPV: createTestVolModePV(&block), 2272 }, 2273 } 2274 2275 for name, scenario := range scenarios { 2276 t.Run(name, func(t *testing.T) { 2277 opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV) 2278 // ensure we have a resource version specified for updates 2279 errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts) 2280 if len(errs) == 0 && scenario.isExpectedFailure { 2281 t.Errorf("Unexpected success for scenario: %s", name) 2282 } 2283 if len(errs) > 0 && !scenario.isExpectedFailure { 2284 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 2285 } 2286 }) 2287 } 2288 } 2289 2290 func TestValidatePersistentVolumeClaimUpdate(t *testing.T) { 2291 block := core.PersistentVolumeBlock 2292 file := core.PersistentVolumeFilesystem 2293 invaildAPIGroup := "^invalid" 2294 2295 validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2296 AccessModes: []core.PersistentVolumeAccessMode{ 2297 core.ReadWriteOnce, 2298 core.ReadOnlyMany, 2299 }, 2300 Resources: core.VolumeResourceRequirements{ 2301 Requests: core.ResourceList{ 2302 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2303 }, 2304 }, 2305 }, core.PersistentVolumeClaimStatus{ 2306 Phase: core.ClaimBound, 2307 }) 2308 2309 validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", core.PersistentVolumeClaimSpec{ 2310 AccessModes: []core.PersistentVolumeAccessMode{ 2311 core.ReadOnlyMany, 2312 }, 2313 Resources: core.VolumeResourceRequirements{ 2314 Requests: core.ResourceList{ 2315 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2316 }, 2317 }, 2318 }) 2319 validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", core.PersistentVolumeClaimSpec{ 2320 AccessModes: []core.PersistentVolumeAccessMode{ 2321 core.ReadOnlyMany, 2322 }, 2323 Resources: core.VolumeResourceRequirements{ 2324 Requests: core.ResourceList{ 2325 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2326 }, 2327 }, 2328 }) 2329 validUpdateClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2330 AccessModes: []core.PersistentVolumeAccessMode{ 2331 core.ReadWriteOnce, 2332 core.ReadOnlyMany, 2333 }, 2334 Resources: core.VolumeResourceRequirements{ 2335 Requests: core.ResourceList{ 2336 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2337 }, 2338 }, 2339 VolumeName: "volume", 2340 }) 2341 invalidUpdateClaimResources := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2342 AccessModes: []core.PersistentVolumeAccessMode{ 2343 core.ReadWriteOnce, 2344 core.ReadOnlyMany, 2345 }, 2346 Resources: core.VolumeResourceRequirements{ 2347 Requests: core.ResourceList{ 2348 core.ResourceName(core.ResourceStorage): resource.MustParse("20G"), 2349 }, 2350 }, 2351 VolumeName: "volume", 2352 }) 2353 invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2354 AccessModes: []core.PersistentVolumeAccessMode{ 2355 core.ReadWriteOnce, 2356 }, 2357 Resources: core.VolumeResourceRequirements{ 2358 Requests: core.ResourceList{ 2359 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2360 }, 2361 }, 2362 VolumeName: "volume", 2363 }) 2364 validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2365 AccessModes: []core.PersistentVolumeAccessMode{ 2366 core.ReadWriteOnce, 2367 }, 2368 VolumeMode: &file, 2369 Resources: core.VolumeResourceRequirements{ 2370 Requests: core.ResourceList{ 2371 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2372 }, 2373 }, 2374 VolumeName: "volume", 2375 }) 2376 validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2377 AccessModes: []core.PersistentVolumeAccessMode{ 2378 core.ReadWriteOnce, 2379 }, 2380 VolumeMode: &block, 2381 Resources: core.VolumeResourceRequirements{ 2382 Requests: core.ResourceList{ 2383 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2384 }, 2385 }, 2386 VolumeName: "volume", 2387 }) 2388 invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2389 AccessModes: []core.PersistentVolumeAccessMode{ 2390 core.ReadWriteOnce, 2391 }, 2392 VolumeMode: nil, 2393 Resources: core.VolumeResourceRequirements{ 2394 Requests: core.ResourceList{ 2395 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2396 }, 2397 }, 2398 VolumeName: "volume", 2399 }) 2400 invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{ 2401 AccessModes: []core.PersistentVolumeAccessMode{ 2402 core.ReadOnlyMany, 2403 }, 2404 Resources: core.VolumeResourceRequirements{ 2405 Requests: core.ResourceList{ 2406 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2407 }, 2408 }, 2409 VolumeName: "volume", 2410 }) 2411 validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{ 2412 AccessModes: []core.PersistentVolumeAccessMode{ 2413 core.ReadOnlyMany, 2414 }, 2415 Resources: core.VolumeResourceRequirements{ 2416 Requests: core.ResourceList{ 2417 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2418 }, 2419 }, 2420 VolumeName: "volume", 2421 }) 2422 validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{ 2423 AccessModes: []core.PersistentVolumeAccessMode{ 2424 core.ReadWriteOnce, 2425 core.ReadOnlyMany, 2426 }, 2427 Resources: core.VolumeResourceRequirements{ 2428 Requests: core.ResourceList{ 2429 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2430 }, 2431 }, 2432 VolumeName: "volume", 2433 }) 2434 validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2435 AccessModes: []core.PersistentVolumeAccessMode{ 2436 core.ReadWriteOnce, 2437 core.ReadOnlyMany, 2438 }, 2439 Resources: core.VolumeResourceRequirements{ 2440 Requests: core.ResourceList{ 2441 core.ResourceName(core.ResourceStorage): resource.MustParse("15G"), 2442 }, 2443 }, 2444 }, core.PersistentVolumeClaimStatus{ 2445 Phase: core.ClaimBound, 2446 }) 2447 2448 invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2449 AccessModes: []core.PersistentVolumeAccessMode{ 2450 core.ReadWriteOnce, 2451 core.ReadOnlyMany, 2452 }, 2453 Resources: core.VolumeResourceRequirements{ 2454 Requests: core.ResourceList{ 2455 core.ResourceName(core.ResourceStorage): resource.MustParse("5G"), 2456 }, 2457 }, 2458 }, core.PersistentVolumeClaimStatus{ 2459 Phase: core.ClaimBound, 2460 }) 2461 2462 unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2463 AccessModes: []core.PersistentVolumeAccessMode{ 2464 core.ReadWriteOnce, 2465 core.ReadOnlyMany, 2466 }, 2467 Resources: core.VolumeResourceRequirements{ 2468 Requests: core.ResourceList{ 2469 core.ResourceName(core.ResourceStorage): resource.MustParse("12G"), 2470 }, 2471 }, 2472 }, core.PersistentVolumeClaimStatus{ 2473 Phase: core.ClaimPending, 2474 }) 2475 2476 validClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast", core.PersistentVolumeClaimSpec{ 2477 AccessModes: []core.PersistentVolumeAccessMode{ 2478 core.ReadOnlyMany, 2479 }, 2480 Resources: core.VolumeResourceRequirements{ 2481 Requests: core.ResourceList{ 2482 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2483 }, 2484 }, 2485 }) 2486 2487 validClaimStorageClassInSpecChanged := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{ 2488 AccessModes: []core.PersistentVolumeAccessMode{ 2489 core.ReadOnlyMany, 2490 }, 2491 Resources: core.VolumeResourceRequirements{ 2492 Requests: core.ResourceList{ 2493 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2494 }, 2495 }, 2496 }) 2497 2498 validClaimStorageClassNil := testVolumeClaimStorageClassNilInSpec("foo", "ns", core.PersistentVolumeClaimSpec{ 2499 AccessModes: []core.PersistentVolumeAccessMode{ 2500 core.ReadOnlyMany, 2501 }, 2502 Resources: core.VolumeResourceRequirements{ 2503 Requests: core.ResourceList{ 2504 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2505 }, 2506 }, 2507 }) 2508 2509 invalidClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{ 2510 AccessModes: []core.PersistentVolumeAccessMode{ 2511 core.ReadOnlyMany, 2512 }, 2513 Resources: core.VolumeResourceRequirements{ 2514 Requests: core.ResourceList{ 2515 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2516 }, 2517 }, 2518 }) 2519 2520 validClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec( 2521 "foo", "ns", "fast", "fast", core.PersistentVolumeClaimSpec{ 2522 AccessModes: []core.PersistentVolumeAccessMode{ 2523 core.ReadOnlyMany, 2524 }, 2525 Resources: core.VolumeResourceRequirements{ 2526 Requests: core.ResourceList{ 2527 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2528 }, 2529 }, 2530 }) 2531 2532 validClaimStorageClassInAnnotationAndNilInSpec := testVolumeClaimStorageClassInAnnotationAndNilInSpec( 2533 "foo", "ns", "fast", core.PersistentVolumeClaimSpec{ 2534 AccessModes: []core.PersistentVolumeAccessMode{ 2535 core.ReadOnlyMany, 2536 }, 2537 Resources: core.VolumeResourceRequirements{ 2538 Requests: core.ResourceList{ 2539 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2540 }, 2541 }, 2542 }) 2543 2544 invalidClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec( 2545 "foo", "ns", "fast2", "fast", core.PersistentVolumeClaimSpec{ 2546 AccessModes: []core.PersistentVolumeAccessMode{ 2547 core.ReadOnlyMany, 2548 }, 2549 Resources: core.VolumeResourceRequirements{ 2550 Requests: core.ResourceList{ 2551 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2552 }, 2553 }, 2554 }) 2555 2556 validClaimRWOPAccessMode := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2557 AccessModes: []core.PersistentVolumeAccessMode{ 2558 core.ReadWriteOncePod, 2559 }, 2560 Resources: core.VolumeResourceRequirements{ 2561 Requests: core.ResourceList{ 2562 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2563 }, 2564 }, 2565 VolumeName: "volume", 2566 }) 2567 2568 validClaimRWOPAccessModeAddAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{ 2569 AccessModes: []core.PersistentVolumeAccessMode{ 2570 core.ReadWriteOncePod, 2571 }, 2572 Resources: core.VolumeResourceRequirements{ 2573 Requests: core.ResourceList{ 2574 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2575 }, 2576 }, 2577 VolumeName: "volume", 2578 }) 2579 validClaimShrinkInitial := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2580 AccessModes: []core.PersistentVolumeAccessMode{ 2581 core.ReadWriteOnce, 2582 core.ReadOnlyMany, 2583 }, 2584 Resources: core.VolumeResourceRequirements{ 2585 Requests: core.ResourceList{ 2586 core.ResourceName(core.ResourceStorage): resource.MustParse("15G"), 2587 }, 2588 }, 2589 }, core.PersistentVolumeClaimStatus{ 2590 Phase: core.ClaimBound, 2591 Capacity: core.ResourceList{ 2592 core.ResourceStorage: resource.MustParse("10G"), 2593 }, 2594 }) 2595 2596 unboundShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2597 AccessModes: []core.PersistentVolumeAccessMode{ 2598 core.ReadWriteOnce, 2599 core.ReadOnlyMany, 2600 }, 2601 Resources: core.VolumeResourceRequirements{ 2602 Requests: core.ResourceList{ 2603 core.ResourceName(core.ResourceStorage): resource.MustParse("12G"), 2604 }, 2605 }, 2606 }, core.PersistentVolumeClaimStatus{ 2607 Phase: core.ClaimPending, 2608 Capacity: core.ResourceList{ 2609 core.ResourceStorage: resource.MustParse("10G"), 2610 }, 2611 }) 2612 2613 validClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2614 AccessModes: []core.PersistentVolumeAccessMode{ 2615 core.ReadWriteOnce, 2616 core.ReadOnlyMany, 2617 }, 2618 Resources: core.VolumeResourceRequirements{ 2619 Requests: core.ResourceList{ 2620 core.ResourceStorage: resource.MustParse("13G"), 2621 }, 2622 }, 2623 }, core.PersistentVolumeClaimStatus{ 2624 Phase: core.ClaimBound, 2625 Capacity: core.ResourceList{ 2626 core.ResourceStorage: resource.MustParse("10G"), 2627 }, 2628 }) 2629 2630 invalidShrinkToStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2631 AccessModes: []core.PersistentVolumeAccessMode{ 2632 core.ReadWriteOnce, 2633 core.ReadOnlyMany, 2634 }, 2635 Resources: core.VolumeResourceRequirements{ 2636 Requests: core.ResourceList{ 2637 core.ResourceStorage: resource.MustParse("10G"), 2638 }, 2639 }, 2640 }, core.PersistentVolumeClaimStatus{ 2641 Phase: core.ClaimBound, 2642 Capacity: core.ResourceList{ 2643 core.ResourceStorage: resource.MustParse("10G"), 2644 }, 2645 }) 2646 2647 invalidClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2648 AccessModes: []core.PersistentVolumeAccessMode{ 2649 core.ReadWriteOnce, 2650 core.ReadOnlyMany, 2651 }, 2652 Resources: core.VolumeResourceRequirements{ 2653 Requests: core.ResourceList{ 2654 core.ResourceStorage: resource.MustParse("3G"), 2655 }, 2656 }, 2657 }, core.PersistentVolumeClaimStatus{ 2658 Phase: core.ClaimBound, 2659 Capacity: core.ResourceList{ 2660 core.ResourceStorage: resource.MustParse("10G"), 2661 }, 2662 }) 2663 2664 invalidClaimDataSourceAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2665 AccessModes: []core.PersistentVolumeAccessMode{ 2666 core.ReadWriteOnce, 2667 }, 2668 VolumeMode: &file, 2669 Resources: core.VolumeResourceRequirements{ 2670 Requests: core.ResourceList{ 2671 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2672 }, 2673 }, 2674 VolumeName: "volume", 2675 DataSource: &core.TypedLocalObjectReference{ 2676 APIGroup: &invaildAPIGroup, 2677 Kind: "Foo", 2678 Name: "foo", 2679 }, 2680 }) 2681 2682 invalidClaimDataSourceRefAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 2683 AccessModes: []core.PersistentVolumeAccessMode{ 2684 core.ReadWriteOnce, 2685 }, 2686 VolumeMode: &file, 2687 Resources: core.VolumeResourceRequirements{ 2688 Requests: core.ResourceList{ 2689 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2690 }, 2691 }, 2692 VolumeName: "volume", 2693 DataSourceRef: &core.TypedObjectReference{ 2694 APIGroup: &invaildAPIGroup, 2695 Kind: "Foo", 2696 Name: "foo", 2697 }, 2698 }) 2699 2700 validClaimNilVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2701 AccessModes: []core.PersistentVolumeAccessMode{ 2702 core.ReadWriteOnce, 2703 core.ReadOnlyMany, 2704 }, 2705 Resources: core.VolumeResourceRequirements{ 2706 Requests: core.ResourceList{ 2707 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2708 }, 2709 }, 2710 }, core.PersistentVolumeClaimStatus{ 2711 Phase: core.ClaimBound, 2712 }) 2713 validClaimEmptyVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2714 VolumeAttributesClassName: utilpointer.String(""), 2715 AccessModes: []core.PersistentVolumeAccessMode{ 2716 core.ReadWriteOnce, 2717 core.ReadOnlyMany, 2718 }, 2719 Resources: core.VolumeResourceRequirements{ 2720 Requests: core.ResourceList{ 2721 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2722 }, 2723 }, 2724 }, core.PersistentVolumeClaimStatus{ 2725 Phase: core.ClaimBound, 2726 }) 2727 validClaimVolumeAttributesClass1 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2728 VolumeAttributesClassName: utilpointer.String("vac1"), 2729 AccessModes: []core.PersistentVolumeAccessMode{ 2730 core.ReadWriteOnce, 2731 core.ReadOnlyMany, 2732 }, 2733 Resources: core.VolumeResourceRequirements{ 2734 Requests: core.ResourceList{ 2735 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2736 }, 2737 }, 2738 }, core.PersistentVolumeClaimStatus{ 2739 Phase: core.ClaimBound, 2740 }) 2741 validClaimVolumeAttributesClass2 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 2742 VolumeAttributesClassName: utilpointer.String("vac2"), 2743 AccessModes: []core.PersistentVolumeAccessMode{ 2744 core.ReadWriteOnce, 2745 core.ReadOnlyMany, 2746 }, 2747 Resources: core.VolumeResourceRequirements{ 2748 Requests: core.ResourceList{ 2749 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 2750 }, 2751 }, 2752 }, core.PersistentVolumeClaimStatus{ 2753 Phase: core.ClaimBound, 2754 }) 2755 2756 scenarios := map[string]struct { 2757 isExpectedFailure bool 2758 oldClaim *core.PersistentVolumeClaim 2759 newClaim *core.PersistentVolumeClaim 2760 enableRecoverFromExpansion bool 2761 enableVolumeAttributesClass bool 2762 }{ 2763 "valid-update-volumeName-only": { 2764 isExpectedFailure: false, 2765 oldClaim: validClaim, 2766 newClaim: validUpdateClaim, 2767 }, 2768 "valid-no-op-update": { 2769 isExpectedFailure: false, 2770 oldClaim: validUpdateClaim, 2771 newClaim: validUpdateClaim, 2772 }, 2773 "invalid-update-change-resources-on-bound-claim": { 2774 isExpectedFailure: true, 2775 oldClaim: validUpdateClaim, 2776 newClaim: invalidUpdateClaimResources, 2777 }, 2778 "invalid-update-change-access-modes-on-bound-claim": { 2779 isExpectedFailure: true, 2780 oldClaim: validUpdateClaim, 2781 newClaim: invalidUpdateClaimAccessModes, 2782 }, 2783 "valid-update-volume-mode-block-to-block": { 2784 isExpectedFailure: false, 2785 oldClaim: validClaimVolumeModeBlock, 2786 newClaim: validClaimVolumeModeBlock, 2787 }, 2788 "valid-update-volume-mode-file-to-file": { 2789 isExpectedFailure: false, 2790 oldClaim: validClaimVolumeModeFile, 2791 newClaim: validClaimVolumeModeFile, 2792 }, 2793 "invalid-update-volume-mode-to-block": { 2794 isExpectedFailure: true, 2795 oldClaim: validClaimVolumeModeFile, 2796 newClaim: validClaimVolumeModeBlock, 2797 }, 2798 "invalid-update-volume-mode-to-file": { 2799 isExpectedFailure: true, 2800 oldClaim: validClaimVolumeModeBlock, 2801 newClaim: validClaimVolumeModeFile, 2802 }, 2803 "invalid-update-volume-mode-nil-to-file": { 2804 isExpectedFailure: true, 2805 oldClaim: invalidClaimVolumeModeNil, 2806 newClaim: validClaimVolumeModeFile, 2807 }, 2808 "invalid-update-volume-mode-nil-to-block": { 2809 isExpectedFailure: true, 2810 oldClaim: invalidClaimVolumeModeNil, 2811 newClaim: validClaimVolumeModeBlock, 2812 }, 2813 "invalid-update-volume-mode-block-to-nil": { 2814 isExpectedFailure: true, 2815 oldClaim: validClaimVolumeModeBlock, 2816 newClaim: invalidClaimVolumeModeNil, 2817 }, 2818 "invalid-update-volume-mode-file-to-nil": { 2819 isExpectedFailure: true, 2820 oldClaim: validClaimVolumeModeFile, 2821 newClaim: invalidClaimVolumeModeNil, 2822 }, 2823 "invalid-update-volume-mode-empty-to-mode": { 2824 isExpectedFailure: true, 2825 oldClaim: validClaim, 2826 newClaim: validClaimVolumeModeBlock, 2827 }, 2828 "invalid-update-volume-mode-mode-to-empty": { 2829 isExpectedFailure: true, 2830 oldClaim: validClaimVolumeModeBlock, 2831 newClaim: validClaim, 2832 }, 2833 "invalid-update-change-storage-class-annotation-after-creation": { 2834 isExpectedFailure: true, 2835 oldClaim: validClaimStorageClass, 2836 newClaim: invalidUpdateClaimStorageClass, 2837 }, 2838 "valid-update-mutable-annotation": { 2839 isExpectedFailure: false, 2840 oldClaim: validClaimAnnotation, 2841 newClaim: validUpdateClaimMutableAnnotation, 2842 }, 2843 "valid-update-add-annotation": { 2844 isExpectedFailure: false, 2845 oldClaim: validClaim, 2846 newClaim: validAddClaimAnnotation, 2847 }, 2848 "valid-size-update-resize-disabled": { 2849 oldClaim: validClaim, 2850 newClaim: validSizeUpdate, 2851 }, 2852 "valid-size-update-resize-enabled": { 2853 isExpectedFailure: false, 2854 oldClaim: validClaim, 2855 newClaim: validSizeUpdate, 2856 }, 2857 "invalid-size-update-resize-enabled": { 2858 isExpectedFailure: true, 2859 oldClaim: validClaim, 2860 newClaim: invalidSizeUpdate, 2861 }, 2862 "unbound-size-update-resize-enabled": { 2863 isExpectedFailure: true, 2864 oldClaim: validClaim, 2865 newClaim: unboundSizeUpdate, 2866 }, 2867 "valid-upgrade-storage-class-annotation-to-spec": { 2868 isExpectedFailure: false, 2869 oldClaim: validClaimStorageClass, 2870 newClaim: validClaimStorageClassInSpec, 2871 }, 2872 "valid-upgrade-nil-storage-class-spec-to-spec": { 2873 isExpectedFailure: false, 2874 oldClaim: validClaimStorageClassNil, 2875 newClaim: validClaimStorageClassInSpec, 2876 }, 2877 "invalid-upgrade-not-nil-storage-class-spec-to-spec": { 2878 isExpectedFailure: true, 2879 oldClaim: validClaimStorageClassInSpec, 2880 newClaim: validClaimStorageClassInSpecChanged, 2881 }, 2882 "invalid-upgrade-to-nil-storage-class-spec-to-spec": { 2883 isExpectedFailure: true, 2884 oldClaim: validClaimStorageClassInSpec, 2885 newClaim: validClaimStorageClassNil, 2886 }, 2887 "valid-upgrade-storage-class-annotation-and-nil-spec-to-spec-retro": { 2888 isExpectedFailure: false, 2889 oldClaim: validClaimStorageClassInAnnotationAndNilInSpec, 2890 newClaim: validClaimStorageClassInAnnotationAndSpec, 2891 }, 2892 "invalid-upgrade-storage-class-annotation-and-spec-to-spec-retro": { 2893 isExpectedFailure: true, 2894 oldClaim: validClaimStorageClassInAnnotationAndSpec, 2895 newClaim: validClaimStorageClassInSpecChanged, 2896 }, 2897 "invalid-upgrade-storage-class-annotation-and-no-spec": { 2898 isExpectedFailure: true, 2899 oldClaim: validClaimStorageClassInAnnotationAndNilInSpec, 2900 newClaim: validClaimStorageClassInSpecChanged, 2901 }, 2902 "invalid-upgrade-storage-class-annotation-to-spec": { 2903 isExpectedFailure: true, 2904 oldClaim: validClaimStorageClass, 2905 newClaim: invalidClaimStorageClassInSpec, 2906 }, 2907 "valid-upgrade-storage-class-annotation-to-annotation-and-spec": { 2908 isExpectedFailure: false, 2909 oldClaim: validClaimStorageClass, 2910 newClaim: validClaimStorageClassInAnnotationAndSpec, 2911 }, 2912 "invalid-upgrade-storage-class-annotation-to-annotation-and-spec": { 2913 isExpectedFailure: true, 2914 oldClaim: validClaimStorageClass, 2915 newClaim: invalidClaimStorageClassInAnnotationAndSpec, 2916 }, 2917 "invalid-upgrade-storage-class-in-spec": { 2918 isExpectedFailure: true, 2919 oldClaim: validClaimStorageClassInSpec, 2920 newClaim: invalidClaimStorageClassInSpec, 2921 }, 2922 "invalid-downgrade-storage-class-spec-to-annotation": { 2923 isExpectedFailure: true, 2924 oldClaim: validClaimStorageClassInSpec, 2925 newClaim: validClaimStorageClass, 2926 }, 2927 "valid-update-rwop-used-and-rwop-feature-disabled": { 2928 isExpectedFailure: false, 2929 oldClaim: validClaimRWOPAccessMode, 2930 newClaim: validClaimRWOPAccessModeAddAnnotation, 2931 }, 2932 "valid-expand-shrink-resize-enabled": { 2933 oldClaim: validClaimShrinkInitial, 2934 newClaim: validClaimShrink, 2935 enableRecoverFromExpansion: true, 2936 }, 2937 "invalid-expand-shrink-resize-enabled": { 2938 oldClaim: validClaimShrinkInitial, 2939 newClaim: invalidClaimShrink, 2940 enableRecoverFromExpansion: true, 2941 isExpectedFailure: true, 2942 }, 2943 "invalid-expand-shrink-to-status-resize-enabled": { 2944 oldClaim: validClaimShrinkInitial, 2945 newClaim: invalidShrinkToStatus, 2946 enableRecoverFromExpansion: true, 2947 isExpectedFailure: true, 2948 }, 2949 "invalid-expand-shrink-recover-disabled": { 2950 oldClaim: validClaimShrinkInitial, 2951 newClaim: validClaimShrink, 2952 enableRecoverFromExpansion: false, 2953 isExpectedFailure: true, 2954 }, 2955 "unbound-size-shrink-resize-enabled": { 2956 oldClaim: validClaimShrinkInitial, 2957 newClaim: unboundShrink, 2958 enableRecoverFromExpansion: true, 2959 isExpectedFailure: true, 2960 }, 2961 "allow-update-pvc-when-data-source-used": { 2962 oldClaim: invalidClaimDataSourceAPIGroup, 2963 newClaim: invalidClaimDataSourceAPIGroup, 2964 isExpectedFailure: false, 2965 }, 2966 "allow-update-pvc-when-data-source-ref-used": { 2967 oldClaim: invalidClaimDataSourceRefAPIGroup, 2968 newClaim: invalidClaimDataSourceRefAPIGroup, 2969 isExpectedFailure: false, 2970 }, 2971 "valid-update-volume-attributes-class-from-nil": { 2972 oldClaim: validClaimNilVolumeAttributesClass, 2973 newClaim: validClaimVolumeAttributesClass1, 2974 enableVolumeAttributesClass: true, 2975 isExpectedFailure: false, 2976 }, 2977 "valid-update-volume-attributes-class-from-empty": { 2978 oldClaim: validClaimEmptyVolumeAttributesClass, 2979 newClaim: validClaimVolumeAttributesClass1, 2980 enableVolumeAttributesClass: true, 2981 isExpectedFailure: false, 2982 }, 2983 "valid-update-volume-attributes-class": { 2984 oldClaim: validClaimVolumeAttributesClass1, 2985 newClaim: validClaimVolumeAttributesClass2, 2986 enableVolumeAttributesClass: true, 2987 isExpectedFailure: false, 2988 }, 2989 "invalid-update-volume-attributes-class": { 2990 oldClaim: validClaimVolumeAttributesClass1, 2991 newClaim: validClaimNilVolumeAttributesClass, 2992 enableVolumeAttributesClass: true, 2993 isExpectedFailure: true, 2994 }, 2995 "invalid-update-volume-attributes-class-to-nil": { 2996 oldClaim: validClaimVolumeAttributesClass1, 2997 newClaim: validClaimNilVolumeAttributesClass, 2998 enableVolumeAttributesClass: true, 2999 isExpectedFailure: true, 3000 }, 3001 "invalid-update-volume-attributes-class-to-empty": { 3002 oldClaim: validClaimVolumeAttributesClass1, 3003 newClaim: validClaimEmptyVolumeAttributesClass, 3004 enableVolumeAttributesClass: true, 3005 isExpectedFailure: true, 3006 }, 3007 "invalid-update-volume-attributes-class-to-nil-without-featuregate-enabled": { 3008 oldClaim: validClaimVolumeAttributesClass1, 3009 newClaim: validClaimNilVolumeAttributesClass, 3010 enableVolumeAttributesClass: false, 3011 isExpectedFailure: true, 3012 }, 3013 "invalid-update-volume-attributes-class-without-featuregate-enabled": { 3014 oldClaim: validClaimVolumeAttributesClass1, 3015 newClaim: validClaimVolumeAttributesClass2, 3016 enableVolumeAttributesClass: false, 3017 isExpectedFailure: true, 3018 }, 3019 } 3020 3021 for name, scenario := range scenarios { 3022 t.Run(name, func(t *testing.T) { 3023 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)() 3024 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)() 3025 3026 scenario.oldClaim.ResourceVersion = "1" 3027 scenario.newClaim.ResourceVersion = "1" 3028 opts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim) 3029 errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim, opts) 3030 if len(errs) == 0 && scenario.isExpectedFailure { 3031 t.Errorf("Unexpected success for scenario: %s", name) 3032 } 3033 if len(errs) > 0 && !scenario.isExpectedFailure { 3034 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 3035 } 3036 }) 3037 } 3038 } 3039 3040 func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) { 3041 invaildAPIGroup := "^invalid" 3042 3043 tests := map[string]struct { 3044 oldPvc *core.PersistentVolumeClaim 3045 enableVolumeAttributesClass bool 3046 expectValidationOpts PersistentVolumeClaimSpecValidationOptions 3047 }{ 3048 "nil pv": { 3049 oldPvc: nil, 3050 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{ 3051 EnableRecoverFromExpansionFailure: false, 3052 EnableVolumeAttributesClass: false, 3053 }, 3054 }, 3055 "invaild apiGroup in dataSource allowed because the old pvc is used": { 3056 oldPvc: pvcWithDataSource(&core.TypedLocalObjectReference{APIGroup: &invaildAPIGroup}), 3057 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{ 3058 AllowInvalidAPIGroupInDataSourceOrRef: true, 3059 }, 3060 }, 3061 "invaild apiGroup in dataSourceRef allowed because the old pvc is used": { 3062 oldPvc: pvcWithDataSourceRef(&core.TypedObjectReference{APIGroup: &invaildAPIGroup}), 3063 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{ 3064 AllowInvalidAPIGroupInDataSourceOrRef: true, 3065 }, 3066 }, 3067 "volume attributes class allowed because feature enable": { 3068 oldPvc: pvcWithVolumeAttributesClassName(utilpointer.String("foo")), 3069 enableVolumeAttributesClass: true, 3070 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{ 3071 EnableRecoverFromExpansionFailure: false, 3072 EnableVolumeAttributesClass: true, 3073 }, 3074 }, 3075 "volume attributes class validated because used and feature disabled": { 3076 oldPvc: pvcWithVolumeAttributesClassName(utilpointer.String("foo")), 3077 enableVolumeAttributesClass: false, 3078 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{ 3079 EnableRecoverFromExpansionFailure: false, 3080 EnableVolumeAttributesClass: true, 3081 }, 3082 }, 3083 } 3084 3085 for name, tc := range tests { 3086 t.Run(name, func(t *testing.T) { 3087 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)() 3088 3089 opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc) 3090 if opts != tc.expectValidationOpts { 3091 t.Errorf("Expected opts: %+v, received: %+v", tc.expectValidationOpts, opts) 3092 } 3093 }) 3094 } 3095 } 3096 3097 func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) { 3098 tests := map[string]struct { 3099 oldPvcTemplate *core.PersistentVolumeClaimTemplate 3100 enableVolumeAttributesClass bool 3101 expectValidationOpts PersistentVolumeClaimSpecValidationOptions 3102 }{ 3103 "nil pv": { 3104 oldPvcTemplate: nil, 3105 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{}, 3106 }, 3107 "volume attributes class allowed because feature enable": { 3108 oldPvcTemplate: pvcTemplateWithVolumeAttributesClassName(utilpointer.String("foo")), 3109 enableVolumeAttributesClass: true, 3110 expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{ 3111 EnableVolumeAttributesClass: true, 3112 }, 3113 }, 3114 } 3115 3116 for name, tc := range tests { 3117 t.Run(name, func(t *testing.T) { 3118 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)() 3119 3120 opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate) 3121 if opts != tc.expectValidationOpts { 3122 t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts) 3123 } 3124 }) 3125 } 3126 } 3127 3128 func TestValidateKeyToPath(t *testing.T) { 3129 testCases := []struct { 3130 kp core.KeyToPath 3131 ok bool 3132 errtype field.ErrorType 3133 }{{ 3134 kp: core.KeyToPath{Key: "k", Path: "p"}, 3135 ok: true, 3136 }, { 3137 kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"}, 3138 ok: true, 3139 }, { 3140 kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"}, 3141 ok: true, 3142 }, { 3143 kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(0644)}, 3144 ok: true, 3145 }, { 3146 kp: core.KeyToPath{Key: "", Path: "p"}, 3147 ok: false, 3148 errtype: field.ErrorTypeRequired, 3149 }, { 3150 kp: core.KeyToPath{Key: "k", Path: ""}, 3151 ok: false, 3152 errtype: field.ErrorTypeRequired, 3153 }, { 3154 kp: core.KeyToPath{Key: "k", Path: "..p"}, 3155 ok: false, 3156 errtype: field.ErrorTypeInvalid, 3157 }, { 3158 kp: core.KeyToPath{Key: "k", Path: "../p"}, 3159 ok: false, 3160 errtype: field.ErrorTypeInvalid, 3161 }, { 3162 kp: core.KeyToPath{Key: "k", Path: "p/../p"}, 3163 ok: false, 3164 errtype: field.ErrorTypeInvalid, 3165 }, { 3166 kp: core.KeyToPath{Key: "k", Path: "p/.."}, 3167 ok: false, 3168 errtype: field.ErrorTypeInvalid, 3169 }, { 3170 kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(01000)}, 3171 ok: false, 3172 errtype: field.ErrorTypeInvalid, 3173 }, { 3174 kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(-1)}, 3175 ok: false, 3176 errtype: field.ErrorTypeInvalid, 3177 }, 3178 } 3179 3180 for i, tc := range testCases { 3181 errs := validateKeyToPath(&tc.kp, field.NewPath("field")) 3182 if tc.ok && len(errs) > 0 { 3183 t.Errorf("[%d] unexpected errors: %v", i, errs) 3184 } else if !tc.ok && len(errs) == 0 { 3185 t.Errorf("[%d] expected error type %v", i, tc.errtype) 3186 } else if len(errs) > 1 { 3187 t.Errorf("[%d] expected only one error, got %d", i, len(errs)) 3188 } else if !tc.ok { 3189 if errs[0].Type != tc.errtype { 3190 t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type) 3191 } 3192 } 3193 } 3194 } 3195 3196 func TestValidateNFSVolumeSource(t *testing.T) { 3197 testCases := []struct { 3198 name string 3199 nfs *core.NFSVolumeSource 3200 errtype field.ErrorType 3201 errfield string 3202 errdetail string 3203 }{{ 3204 name: "missing server", 3205 nfs: &core.NFSVolumeSource{Server: "", Path: "/tmp"}, 3206 errtype: field.ErrorTypeRequired, 3207 errfield: "server", 3208 }, { 3209 name: "missing path", 3210 nfs: &core.NFSVolumeSource{Server: "my-server", Path: ""}, 3211 errtype: field.ErrorTypeRequired, 3212 errfield: "path", 3213 }, { 3214 name: "abs path", 3215 nfs: &core.NFSVolumeSource{Server: "my-server", Path: "tmp"}, 3216 errtype: field.ErrorTypeInvalid, 3217 errfield: "path", 3218 errdetail: "must be an absolute path", 3219 }, 3220 } 3221 3222 for i, tc := range testCases { 3223 errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field")) 3224 3225 if len(errs) > 0 && tc.errtype == "" { 3226 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) 3227 } else if len(errs) == 0 && tc.errtype != "" { 3228 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) 3229 } else if len(errs) >= 1 { 3230 if errs[0].Type != tc.errtype { 3231 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) 3232 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { 3233 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) 3234 } else if !strings.Contains(errs[0].Detail, tc.errdetail) { 3235 t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail) 3236 } 3237 } 3238 } 3239 } 3240 3241 func TestValidateGlusterfs(t *testing.T) { 3242 testCases := []struct { 3243 name string 3244 gfs *core.GlusterfsVolumeSource 3245 errtype field.ErrorType 3246 errfield string 3247 }{{ 3248 name: "missing endpointname", 3249 gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"}, 3250 errtype: field.ErrorTypeRequired, 3251 errfield: "endpoints", 3252 }, { 3253 name: "missing path", 3254 gfs: &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""}, 3255 errtype: field.ErrorTypeRequired, 3256 errfield: "path", 3257 }, { 3258 name: "missing endpointname and path", 3259 gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""}, 3260 errtype: field.ErrorTypeRequired, 3261 errfield: "endpoints", 3262 }, 3263 } 3264 3265 for i, tc := range testCases { 3266 errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field")) 3267 3268 if len(errs) > 0 && tc.errtype == "" { 3269 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) 3270 } else if len(errs) == 0 && tc.errtype != "" { 3271 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) 3272 } else if len(errs) >= 1 { 3273 if errs[0].Type != tc.errtype { 3274 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) 3275 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { 3276 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) 3277 } 3278 } 3279 } 3280 } 3281 3282 func TestValidateGlusterfsPersistentVolumeSource(t *testing.T) { 3283 var epNs *string 3284 namespace := "" 3285 epNs = &namespace 3286 3287 testCases := []struct { 3288 name string 3289 gfs *core.GlusterfsPersistentVolumeSource 3290 errtype field.ErrorType 3291 errfield string 3292 }{{ 3293 name: "missing endpointname", 3294 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"}, 3295 errtype: field.ErrorTypeRequired, 3296 errfield: "endpoints", 3297 }, { 3298 name: "missing path", 3299 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""}, 3300 errtype: field.ErrorTypeRequired, 3301 errfield: "path", 3302 }, { 3303 name: "non null endpointnamespace with empty string", 3304 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs}, 3305 errtype: field.ErrorTypeInvalid, 3306 errfield: "endpointsNamespace", 3307 }, { 3308 name: "missing endpointname and path", 3309 gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""}, 3310 errtype: field.ErrorTypeRequired, 3311 errfield: "endpoints", 3312 }, 3313 } 3314 3315 for i, tc := range testCases { 3316 errs := validateGlusterfsPersistentVolumeSource(tc.gfs, field.NewPath("field")) 3317 3318 if len(errs) > 0 && tc.errtype == "" { 3319 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) 3320 } else if len(errs) == 0 && tc.errtype != "" { 3321 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) 3322 } else if len(errs) >= 1 { 3323 if errs[0].Type != tc.errtype { 3324 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) 3325 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { 3326 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) 3327 } 3328 } 3329 } 3330 } 3331 3332 func TestValidateCSIVolumeSource(t *testing.T) { 3333 testCases := []struct { 3334 name string 3335 csi *core.CSIVolumeSource 3336 errtype field.ErrorType 3337 errfield string 3338 }{{ 3339 name: "all required fields ok", 3340 csi: &core.CSIVolumeSource{Driver: "test-driver"}, 3341 }, { 3342 name: "missing driver name", 3343 csi: &core.CSIVolumeSource{Driver: ""}, 3344 errtype: field.ErrorTypeRequired, 3345 errfield: "driver", 3346 }, { 3347 name: "driver name: ok no punctuations", 3348 csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd"}, 3349 }, { 3350 name: "driver name: ok dot only", 3351 csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage.csi.flex"}, 3352 }, { 3353 name: "driver name: ok dash only", 3354 csi: &core.CSIVolumeSource{Driver: "io-kubernetes-storage-csi-flex"}, 3355 }, { 3356 name: "driver name: invalid underscore", 3357 csi: &core.CSIVolumeSource{Driver: "io_kubernetes_storage_csi_flex"}, 3358 errtype: field.ErrorTypeInvalid, 3359 errfield: "driver", 3360 }, { 3361 name: "driver name: invalid dot underscores", 3362 csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage_csi.flex"}, 3363 errtype: field.ErrorTypeInvalid, 3364 errfield: "driver", 3365 }, { 3366 name: "driver name: ok beginning with number", 3367 csi: &core.CSIVolumeSource{Driver: "2io.kubernetes.storage-csi.flex"}, 3368 }, { 3369 name: "driver name: ok ending with number", 3370 csi: &core.CSIVolumeSource{Driver: "io.kubernetes.storage-csi.flex2"}, 3371 }, { 3372 name: "driver name: invalid dot dash underscores", 3373 csi: &core.CSIVolumeSource{Driver: "io.kubernetes-storage.csi_flex"}, 3374 errtype: field.ErrorTypeInvalid, 3375 errfield: "driver", 3376 }, 3377 3378 { 3379 name: "driver name: ok length 1", 3380 csi: &core.CSIVolumeSource{Driver: "a"}, 3381 }, { 3382 name: "driver name: invalid length > 63", 3383 csi: &core.CSIVolumeSource{Driver: strings.Repeat("g", 65)}, 3384 errtype: field.ErrorTypeTooLong, 3385 errfield: "driver", 3386 }, { 3387 name: "driver name: invalid start char", 3388 csi: &core.CSIVolumeSource{Driver: "_comgooglestoragecsigcepd"}, 3389 errtype: field.ErrorTypeInvalid, 3390 errfield: "driver", 3391 }, { 3392 name: "driver name: invalid end char", 3393 csi: &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd/"}, 3394 errtype: field.ErrorTypeInvalid, 3395 errfield: "driver", 3396 }, { 3397 name: "driver name: invalid separators", 3398 csi: &core.CSIVolumeSource{Driver: "com/google/storage/csi~gcepd"}, 3399 errtype: field.ErrorTypeInvalid, 3400 errfield: "driver", 3401 }, { 3402 name: "valid nodePublishSecretRef", 3403 csi: &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: "foobar"}}, 3404 }, { 3405 name: "nodePublishSecretRef: invalid name missing", 3406 csi: &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: ""}}, 3407 errtype: field.ErrorTypeRequired, 3408 errfield: "nodePublishSecretRef.name", 3409 }, 3410 } 3411 3412 for i, tc := range testCases { 3413 errs := validateCSIVolumeSource(tc.csi, field.NewPath("field")) 3414 3415 if len(errs) > 0 && tc.errtype == "" { 3416 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) 3417 } else if len(errs) == 0 && tc.errtype != "" { 3418 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) 3419 } else if len(errs) >= 1 { 3420 if errs[0].Type != tc.errtype { 3421 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) 3422 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { 3423 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) 3424 } 3425 } 3426 } 3427 } 3428 3429 func TestValidateCSIPersistentVolumeSource(t *testing.T) { 3430 testCases := []struct { 3431 name string 3432 csi *core.CSIPersistentVolumeSource 3433 errtype field.ErrorType 3434 errfield string 3435 }{{ 3436 name: "all required fields ok", 3437 csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, 3438 }, { 3439 name: "with default values ok", 3440 csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"}, 3441 }, { 3442 name: "missing driver name", 3443 csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"}, 3444 errtype: field.ErrorTypeRequired, 3445 errfield: "driver", 3446 }, { 3447 name: "missing volume handle", 3448 csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"}, 3449 errtype: field.ErrorTypeRequired, 3450 errfield: "volumeHandle", 3451 }, { 3452 name: "driver name: ok no punctuations", 3453 csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"}, 3454 }, { 3455 name: "driver name: ok dot only", 3456 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"}, 3457 }, { 3458 name: "driver name: ok dash only", 3459 csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"}, 3460 }, { 3461 name: "driver name: invalid underscore", 3462 csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"}, 3463 errtype: field.ErrorTypeInvalid, 3464 errfield: "driver", 3465 }, { 3466 name: "driver name: invalid dot underscores", 3467 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"}, 3468 errtype: field.ErrorTypeInvalid, 3469 errfield: "driver", 3470 }, { 3471 name: "driver name: ok beginning with number", 3472 csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"}, 3473 }, { 3474 name: "driver name: ok ending with number", 3475 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"}, 3476 }, { 3477 name: "driver name: invalid dot dash underscores", 3478 csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"}, 3479 errtype: field.ErrorTypeInvalid, 3480 errfield: "driver", 3481 }, { 3482 name: "driver name: invalid length 0", 3483 csi: &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"}, 3484 errtype: field.ErrorTypeRequired, 3485 errfield: "driver", 3486 }, { 3487 name: "driver name: ok length 1", 3488 csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"}, 3489 }, { 3490 name: "driver name: invalid length > 63", 3491 csi: &core.CSIPersistentVolumeSource{Driver: strings.Repeat("g", 65), VolumeHandle: "test-123"}, 3492 errtype: field.ErrorTypeTooLong, 3493 errfield: "driver", 3494 }, { 3495 name: "driver name: invalid start char", 3496 csi: &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"}, 3497 errtype: field.ErrorTypeInvalid, 3498 errfield: "driver", 3499 }, { 3500 name: "driver name: invalid end char", 3501 csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"}, 3502 errtype: field.ErrorTypeInvalid, 3503 errfield: "driver", 3504 }, { 3505 name: "driver name: invalid separators", 3506 csi: &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"}, 3507 errtype: field.ErrorTypeInvalid, 3508 errfield: "driver", 3509 }, { 3510 name: "controllerExpandSecretRef: invalid name missing", 3511 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}}, 3512 errtype: field.ErrorTypeRequired, 3513 errfield: "controllerExpandSecretRef.name", 3514 }, { 3515 name: "controllerExpandSecretRef: invalid namespace missing", 3516 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}}, 3517 errtype: field.ErrorTypeRequired, 3518 errfield: "controllerExpandSecretRef.namespace", 3519 }, { 3520 name: "valid controllerExpandSecretRef", 3521 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, 3522 }, { 3523 name: "controllerPublishSecretRef: invalid name missing", 3524 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Namespace: "default"}}, 3525 errtype: field.ErrorTypeRequired, 3526 errfield: "controllerPublishSecretRef.name", 3527 }, { 3528 name: "controllerPublishSecretRef: invalid namespace missing", 3529 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar"}}, 3530 errtype: field.ErrorTypeRequired, 3531 errfield: "controllerPublishSecretRef.namespace", 3532 }, { 3533 name: "valid controllerPublishSecretRef", 3534 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, 3535 }, { 3536 name: "valid nodePublishSecretRef", 3537 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, 3538 }, { 3539 name: "nodePublishSecretRef: invalid name missing", 3540 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Namespace: "foobar"}}, 3541 errtype: field.ErrorTypeRequired, 3542 errfield: "nodePublishSecretRef.name", 3543 }, { 3544 name: "nodePublishSecretRef: invalid namespace missing", 3545 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar"}}, 3546 errtype: field.ErrorTypeRequired, 3547 errfield: "nodePublishSecretRef.namespace", 3548 }, { 3549 name: "nodeExpandSecretRef: invalid name missing", 3550 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Namespace: "default"}}, 3551 errtype: field.ErrorTypeRequired, 3552 errfield: "nodeExpandSecretRef.name", 3553 }, { 3554 name: "nodeExpandSecretRef: invalid namespace missing", 3555 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar"}}, 3556 errtype: field.ErrorTypeRequired, 3557 errfield: "nodeExpandSecretRef.namespace", 3558 }, { 3559 name: "valid nodeExpandSecretRef", 3560 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, 3561 }, { 3562 name: "Invalid nodePublishSecretRef", 3563 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}}, 3564 }, 3565 3566 // tests with allowDNSSubDomainSecretName flag on/off 3567 { 3568 name: "valid nodeExpandSecretRef", 3569 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}}, 3570 }, { 3571 name: "valid long nodeExpandSecretRef", 3572 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}}, 3573 }, { 3574 name: "Invalid nodeExpandSecretRef", 3575 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}}, 3576 errtype: field.ErrorTypeInvalid, 3577 errfield: "nodeExpandSecretRef.name", 3578 }, { 3579 name: "valid nodePublishSecretRef", 3580 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}}, 3581 }, { 3582 name: "valid long nodePublishSecretRef", 3583 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}}, 3584 }, { 3585 name: "Invalid nodePublishSecretRef", 3586 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}}, 3587 errtype: field.ErrorTypeInvalid, 3588 errfield: "nodePublishSecretRef.name", 3589 }, { 3590 name: "valid ControllerExpandSecretRef", 3591 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}}, 3592 }, { 3593 name: "valid long ControllerExpandSecretRef", 3594 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}}, 3595 }, { 3596 name: "Invalid ControllerExpandSecretRef", 3597 csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}}, 3598 errtype: field.ErrorTypeInvalid, 3599 errfield: "controllerExpandSecretRef.name", 3600 }, 3601 } 3602 3603 for i, tc := range testCases { 3604 errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field")) 3605 3606 if len(errs) > 0 && tc.errtype == "" { 3607 t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) 3608 } else if len(errs) == 0 && tc.errtype != "" { 3609 t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) 3610 } else if len(errs) >= 1 { 3611 if errs[0].Type != tc.errtype { 3612 t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) 3613 } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { 3614 t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) 3615 } 3616 } 3617 } 3618 } 3619 3620 // This test is a little too top-to-bottom. Ideally we would test each volume 3621 // type on its own, but we want to also make sure that the logic works through 3622 // the one-of wrapper, so we just do it all in one place. 3623 func TestValidateVolumes(t *testing.T) { 3624 validInitiatorName := "iqn.2015-02.example.com:init" 3625 invalidInitiatorName := "2015-02.example.com:init" 3626 3627 type verr struct { 3628 etype field.ErrorType 3629 field string 3630 detail string 3631 } 3632 3633 testCases := []struct { 3634 name string 3635 vol core.Volume 3636 errs []verr 3637 opts PodValidationOptions 3638 }{ 3639 // EmptyDir and basic volume names 3640 { 3641 name: "valid alpha name", 3642 vol: core.Volume{ 3643 Name: "empty", 3644 VolumeSource: core.VolumeSource{ 3645 EmptyDir: &core.EmptyDirVolumeSource{}, 3646 }, 3647 }, 3648 }, { 3649 name: "valid num name", 3650 vol: core.Volume{ 3651 Name: "123", 3652 VolumeSource: core.VolumeSource{ 3653 EmptyDir: &core.EmptyDirVolumeSource{}, 3654 }, 3655 }, 3656 }, { 3657 name: "valid alphanum name", 3658 vol: core.Volume{ 3659 Name: "empty-123", 3660 VolumeSource: core.VolumeSource{ 3661 EmptyDir: &core.EmptyDirVolumeSource{}, 3662 }, 3663 }, 3664 }, { 3665 name: "valid numalpha name", 3666 vol: core.Volume{ 3667 Name: "123-empty", 3668 VolumeSource: core.VolumeSource{ 3669 EmptyDir: &core.EmptyDirVolumeSource{}, 3670 }, 3671 }, 3672 }, { 3673 name: "zero-length name", 3674 vol: core.Volume{ 3675 Name: "", 3676 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, 3677 }, 3678 errs: []verr{{ 3679 etype: field.ErrorTypeRequired, 3680 field: "name", 3681 }}, 3682 }, { 3683 name: "name > 63 characters", 3684 vol: core.Volume{ 3685 Name: strings.Repeat("a", 64), 3686 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, 3687 }, 3688 errs: []verr{{ 3689 etype: field.ErrorTypeInvalid, 3690 field: "name", 3691 detail: "must be no more than", 3692 }}, 3693 }, { 3694 name: "name has dots", 3695 vol: core.Volume{ 3696 Name: "a.b.c", 3697 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, 3698 }, 3699 errs: []verr{{ 3700 etype: field.ErrorTypeInvalid, 3701 field: "name", 3702 detail: "must not contain dots", 3703 }}, 3704 }, { 3705 name: "name not a DNS label", 3706 vol: core.Volume{ 3707 Name: "Not a DNS label!", 3708 VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}, 3709 }, 3710 errs: []verr{{ 3711 etype: field.ErrorTypeInvalid, 3712 field: "name", 3713 detail: dnsLabelErrMsg, 3714 }}, 3715 }, 3716 // More than one source field specified. 3717 { 3718 name: "more than one source", 3719 vol: core.Volume{ 3720 Name: "dups", 3721 VolumeSource: core.VolumeSource{ 3722 EmptyDir: &core.EmptyDirVolumeSource{}, 3723 HostPath: &core.HostPathVolumeSource{ 3724 Path: "/mnt/path", 3725 Type: newHostPathType(string(core.HostPathDirectory)), 3726 }, 3727 }, 3728 }, 3729 errs: []verr{{ 3730 etype: field.ErrorTypeForbidden, 3731 field: "hostPath", 3732 detail: "may not specify more than 1 volume", 3733 }}, 3734 }, 3735 // HostPath Default 3736 { 3737 name: "default HostPath", 3738 vol: core.Volume{ 3739 Name: "hostpath", 3740 VolumeSource: core.VolumeSource{ 3741 HostPath: &core.HostPathVolumeSource{ 3742 Path: "/mnt/path", 3743 Type: newHostPathType(string(core.HostPathDirectory)), 3744 }, 3745 }, 3746 }, 3747 }, 3748 // HostPath Supported 3749 { 3750 name: "valid HostPath", 3751 vol: core.Volume{ 3752 Name: "hostpath", 3753 VolumeSource: core.VolumeSource{ 3754 HostPath: &core.HostPathVolumeSource{ 3755 Path: "/mnt/path", 3756 Type: newHostPathType(string(core.HostPathSocket)), 3757 }, 3758 }, 3759 }, 3760 }, 3761 // HostPath Invalid 3762 { 3763 name: "invalid HostPath", 3764 vol: core.Volume{ 3765 Name: "hostpath", 3766 VolumeSource: core.VolumeSource{ 3767 HostPath: &core.HostPathVolumeSource{ 3768 Path: "/mnt/path", 3769 Type: newHostPathType("invalid"), 3770 }, 3771 }, 3772 }, 3773 errs: []verr{{ 3774 etype: field.ErrorTypeNotSupported, 3775 field: "type", 3776 }}, 3777 }, { 3778 name: "invalid HostPath backsteps", 3779 vol: core.Volume{ 3780 Name: "hostpath", 3781 VolumeSource: core.VolumeSource{ 3782 HostPath: &core.HostPathVolumeSource{ 3783 Path: "/mnt/path/..", 3784 Type: newHostPathType(string(core.HostPathDirectory)), 3785 }, 3786 }, 3787 }, 3788 errs: []verr{{ 3789 etype: field.ErrorTypeInvalid, 3790 field: "path", 3791 detail: "must not contain '..'", 3792 }}, 3793 }, 3794 // GcePersistentDisk 3795 { 3796 name: "valid GcePersistentDisk", 3797 vol: core.Volume{ 3798 Name: "gce-pd", 3799 VolumeSource: core.VolumeSource{ 3800 GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ 3801 PDName: "my-PD", 3802 FSType: "ext4", 3803 Partition: 1, 3804 ReadOnly: false, 3805 }, 3806 }, 3807 }, 3808 }, 3809 // AWSElasticBlockStore 3810 { 3811 name: "valid AWSElasticBlockStore", 3812 vol: core.Volume{ 3813 Name: "aws-ebs", 3814 VolumeSource: core.VolumeSource{ 3815 AWSElasticBlockStore: &core.AWSElasticBlockStoreVolumeSource{ 3816 VolumeID: "my-PD", 3817 FSType: "ext4", 3818 Partition: 1, 3819 ReadOnly: false, 3820 }, 3821 }, 3822 }, 3823 }, 3824 // GitRepo 3825 { 3826 name: "valid GitRepo", 3827 vol: core.Volume{ 3828 Name: "git-repo", 3829 VolumeSource: core.VolumeSource{ 3830 GitRepo: &core.GitRepoVolumeSource{ 3831 Repository: "my-repo", 3832 Revision: "hashstring", 3833 Directory: "target", 3834 }, 3835 }, 3836 }, 3837 }, { 3838 name: "valid GitRepo in .", 3839 vol: core.Volume{ 3840 Name: "git-repo-dot", 3841 VolumeSource: core.VolumeSource{ 3842 GitRepo: &core.GitRepoVolumeSource{ 3843 Repository: "my-repo", 3844 Directory: ".", 3845 }, 3846 }, 3847 }, 3848 }, { 3849 name: "valid GitRepo with .. in name", 3850 vol: core.Volume{ 3851 Name: "git-repo-dot-dot-foo", 3852 VolumeSource: core.VolumeSource{ 3853 GitRepo: &core.GitRepoVolumeSource{ 3854 Repository: "my-repo", 3855 Directory: "..foo", 3856 }, 3857 }, 3858 }, 3859 }, { 3860 name: "GitRepo starts with ../", 3861 vol: core.Volume{ 3862 Name: "gitrepo", 3863 VolumeSource: core.VolumeSource{ 3864 GitRepo: &core.GitRepoVolumeSource{ 3865 Repository: "foo", 3866 Directory: "../dots/bar", 3867 }, 3868 }, 3869 }, 3870 errs: []verr{{ 3871 etype: field.ErrorTypeInvalid, 3872 field: "gitRepo.directory", 3873 detail: `must not contain '..'`, 3874 }}, 3875 }, { 3876 name: "GitRepo contains ..", 3877 vol: core.Volume{ 3878 Name: "gitrepo", 3879 VolumeSource: core.VolumeSource{ 3880 GitRepo: &core.GitRepoVolumeSource{ 3881 Repository: "foo", 3882 Directory: "dots/../bar", 3883 }, 3884 }, 3885 }, 3886 errs: []verr{{ 3887 etype: field.ErrorTypeInvalid, 3888 field: "gitRepo.directory", 3889 detail: `must not contain '..'`, 3890 }}, 3891 }, { 3892 name: "GitRepo absolute target", 3893 vol: core.Volume{ 3894 Name: "gitrepo", 3895 VolumeSource: core.VolumeSource{ 3896 GitRepo: &core.GitRepoVolumeSource{ 3897 Repository: "foo", 3898 Directory: "/abstarget", 3899 }, 3900 }, 3901 }, 3902 errs: []verr{{ 3903 etype: field.ErrorTypeInvalid, 3904 field: "gitRepo.directory", 3905 }}, 3906 }, 3907 // ISCSI 3908 { 3909 name: "valid ISCSI", 3910 vol: core.Volume{ 3911 Name: "iscsi", 3912 VolumeSource: core.VolumeSource{ 3913 ISCSI: &core.ISCSIVolumeSource{ 3914 TargetPortal: "127.0.0.1", 3915 IQN: "iqn.2015-02.example.com:test", 3916 Lun: 1, 3917 FSType: "ext4", 3918 ReadOnly: false, 3919 }, 3920 }, 3921 }, 3922 }, { 3923 name: "valid IQN: eui format", 3924 vol: core.Volume{ 3925 Name: "iscsi", 3926 VolumeSource: core.VolumeSource{ 3927 ISCSI: &core.ISCSIVolumeSource{ 3928 TargetPortal: "127.0.0.1", 3929 IQN: "eui.0123456789ABCDEF", 3930 Lun: 1, 3931 FSType: "ext4", 3932 ReadOnly: false, 3933 }, 3934 }, 3935 }, 3936 }, { 3937 name: "valid IQN: naa format", 3938 vol: core.Volume{ 3939 Name: "iscsi", 3940 VolumeSource: core.VolumeSource{ 3941 ISCSI: &core.ISCSIVolumeSource{ 3942 TargetPortal: "127.0.0.1", 3943 IQN: "naa.62004567BA64678D0123456789ABCDEF", 3944 Lun: 1, 3945 FSType: "ext4", 3946 ReadOnly: false, 3947 }, 3948 }, 3949 }, 3950 }, { 3951 name: "empty portal", 3952 vol: core.Volume{ 3953 Name: "iscsi", 3954 VolumeSource: core.VolumeSource{ 3955 ISCSI: &core.ISCSIVolumeSource{ 3956 TargetPortal: "", 3957 IQN: "iqn.2015-02.example.com:test", 3958 Lun: 1, 3959 FSType: "ext4", 3960 ReadOnly: false, 3961 }, 3962 }, 3963 }, 3964 errs: []verr{{ 3965 etype: field.ErrorTypeRequired, 3966 field: "iscsi.targetPortal", 3967 }}, 3968 }, { 3969 name: "empty iqn", 3970 vol: core.Volume{ 3971 Name: "iscsi", 3972 VolumeSource: core.VolumeSource{ 3973 ISCSI: &core.ISCSIVolumeSource{ 3974 TargetPortal: "127.0.0.1", 3975 IQN: "", 3976 Lun: 1, 3977 FSType: "ext4", 3978 ReadOnly: false, 3979 }, 3980 }, 3981 }, 3982 errs: []verr{{ 3983 etype: field.ErrorTypeRequired, 3984 field: "iscsi.iqn", 3985 }}, 3986 }, { 3987 name: "invalid IQN: iqn format", 3988 vol: core.Volume{ 3989 Name: "iscsi", 3990 VolumeSource: core.VolumeSource{ 3991 ISCSI: &core.ISCSIVolumeSource{ 3992 TargetPortal: "127.0.0.1", 3993 IQN: "iqn.2015-02.example.com:test;ls;", 3994 Lun: 1, 3995 FSType: "ext4", 3996 ReadOnly: false, 3997 }, 3998 }, 3999 }, 4000 errs: []verr{{ 4001 etype: field.ErrorTypeInvalid, 4002 field: "iscsi.iqn", 4003 }}, 4004 }, { 4005 name: "invalid IQN: eui format", 4006 vol: core.Volume{ 4007 Name: "iscsi", 4008 VolumeSource: core.VolumeSource{ 4009 ISCSI: &core.ISCSIVolumeSource{ 4010 TargetPortal: "127.0.0.1", 4011 IQN: "eui.0123456789ABCDEFGHIJ", 4012 Lun: 1, 4013 FSType: "ext4", 4014 ReadOnly: false, 4015 }, 4016 }, 4017 }, 4018 errs: []verr{{ 4019 etype: field.ErrorTypeInvalid, 4020 field: "iscsi.iqn", 4021 }}, 4022 }, { 4023 name: "invalid IQN: naa format", 4024 vol: core.Volume{ 4025 Name: "iscsi", 4026 VolumeSource: core.VolumeSource{ 4027 ISCSI: &core.ISCSIVolumeSource{ 4028 TargetPortal: "127.0.0.1", 4029 IQN: "naa.62004567BA_4-78D.123456789ABCDEF", 4030 Lun: 1, 4031 FSType: "ext4", 4032 ReadOnly: false, 4033 }, 4034 }, 4035 }, 4036 errs: []verr{{ 4037 etype: field.ErrorTypeInvalid, 4038 field: "iscsi.iqn", 4039 }}, 4040 }, { 4041 name: "valid initiatorName", 4042 vol: core.Volume{ 4043 Name: "iscsi", 4044 VolumeSource: core.VolumeSource{ 4045 ISCSI: &core.ISCSIVolumeSource{ 4046 TargetPortal: "127.0.0.1", 4047 IQN: "iqn.2015-02.example.com:test", 4048 Lun: 1, 4049 InitiatorName: &validInitiatorName, 4050 FSType: "ext4", 4051 ReadOnly: false, 4052 }, 4053 }, 4054 }, 4055 }, { 4056 name: "invalid initiatorName", 4057 vol: core.Volume{ 4058 Name: "iscsi", 4059 VolumeSource: core.VolumeSource{ 4060 ISCSI: &core.ISCSIVolumeSource{ 4061 TargetPortal: "127.0.0.1", 4062 IQN: "iqn.2015-02.example.com:test", 4063 Lun: 1, 4064 InitiatorName: &invalidInitiatorName, 4065 FSType: "ext4", 4066 ReadOnly: false, 4067 }, 4068 }, 4069 }, 4070 errs: []verr{{ 4071 etype: field.ErrorTypeInvalid, 4072 field: "iscsi.initiatorname", 4073 }}, 4074 }, { 4075 name: "empty secret", 4076 vol: core.Volume{ 4077 Name: "iscsi", 4078 VolumeSource: core.VolumeSource{ 4079 ISCSI: &core.ISCSIVolumeSource{ 4080 TargetPortal: "127.0.0.1", 4081 IQN: "iqn.2015-02.example.com:test", 4082 Lun: 1, 4083 FSType: "ext4", 4084 ReadOnly: false, 4085 DiscoveryCHAPAuth: true, 4086 }, 4087 }, 4088 }, 4089 errs: []verr{{ 4090 etype: field.ErrorTypeRequired, 4091 field: "iscsi.secretRef", 4092 }}, 4093 }, { 4094 name: "empty secret", 4095 vol: core.Volume{ 4096 Name: "iscsi", 4097 VolumeSource: core.VolumeSource{ 4098 ISCSI: &core.ISCSIVolumeSource{ 4099 TargetPortal: "127.0.0.1", 4100 IQN: "iqn.2015-02.example.com:test", 4101 Lun: 1, 4102 FSType: "ext4", 4103 ReadOnly: false, 4104 SessionCHAPAuth: true, 4105 }, 4106 }, 4107 }, 4108 errs: []verr{{ 4109 etype: field.ErrorTypeRequired, 4110 field: "iscsi.secretRef", 4111 }}, 4112 }, 4113 // Secret 4114 { 4115 name: "valid Secret", 4116 vol: core.Volume{ 4117 Name: "secret", 4118 VolumeSource: core.VolumeSource{ 4119 Secret: &core.SecretVolumeSource{ 4120 SecretName: "my-secret", 4121 }, 4122 }, 4123 }, 4124 }, { 4125 name: "valid Secret with defaultMode", 4126 vol: core.Volume{ 4127 Name: "secret", 4128 VolumeSource: core.VolumeSource{ 4129 Secret: &core.SecretVolumeSource{ 4130 SecretName: "my-secret", 4131 DefaultMode: utilpointer.Int32(0644), 4132 }, 4133 }, 4134 }, 4135 }, { 4136 name: "valid Secret with projection and mode", 4137 vol: core.Volume{ 4138 Name: "secret", 4139 VolumeSource: core.VolumeSource{ 4140 Secret: &core.SecretVolumeSource{ 4141 SecretName: "my-secret", 4142 Items: []core.KeyToPath{{ 4143 Key: "key", 4144 Path: "filename", 4145 Mode: utilpointer.Int32(0644), 4146 }}, 4147 }, 4148 }, 4149 }, 4150 }, { 4151 name: "valid Secret with subdir projection", 4152 vol: core.Volume{ 4153 Name: "secret", 4154 VolumeSource: core.VolumeSource{ 4155 Secret: &core.SecretVolumeSource{ 4156 SecretName: "my-secret", 4157 Items: []core.KeyToPath{{ 4158 Key: "key", 4159 Path: "dir/filename", 4160 }}, 4161 }, 4162 }, 4163 }, 4164 }, { 4165 name: "secret with missing path", 4166 vol: core.Volume{ 4167 Name: "secret", 4168 VolumeSource: core.VolumeSource{ 4169 Secret: &core.SecretVolumeSource{ 4170 SecretName: "s", 4171 Items: []core.KeyToPath{{Key: "key", Path: ""}}, 4172 }, 4173 }, 4174 }, 4175 errs: []verr{{ 4176 etype: field.ErrorTypeRequired, 4177 field: "secret.items[0].path", 4178 }}, 4179 }, { 4180 name: "secret with leading ..", 4181 vol: core.Volume{ 4182 Name: "secret", 4183 VolumeSource: core.VolumeSource{ 4184 Secret: &core.SecretVolumeSource{ 4185 SecretName: "s", 4186 Items: []core.KeyToPath{{Key: "key", Path: "../foo"}}, 4187 }, 4188 }, 4189 }, 4190 errs: []verr{{ 4191 etype: field.ErrorTypeInvalid, 4192 field: "secret.items[0].path", 4193 }}, 4194 }, { 4195 name: "secret with .. inside", 4196 vol: core.Volume{ 4197 Name: "secret", 4198 VolumeSource: core.VolumeSource{ 4199 Secret: &core.SecretVolumeSource{ 4200 SecretName: "s", 4201 Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}}, 4202 }, 4203 }, 4204 }, 4205 errs: []verr{{ 4206 etype: field.ErrorTypeInvalid, 4207 field: "secret.items[0].path", 4208 }}, 4209 }, { 4210 name: "secret with invalid positive defaultMode", 4211 vol: core.Volume{ 4212 Name: "secret", 4213 VolumeSource: core.VolumeSource{ 4214 Secret: &core.SecretVolumeSource{ 4215 SecretName: "s", 4216 DefaultMode: utilpointer.Int32(01000), 4217 }, 4218 }, 4219 }, 4220 errs: []verr{{ 4221 etype: field.ErrorTypeInvalid, 4222 field: "secret.defaultMode", 4223 }}, 4224 }, { 4225 name: "secret with invalid negative defaultMode", 4226 vol: core.Volume{ 4227 Name: "secret", 4228 VolumeSource: core.VolumeSource{ 4229 Secret: &core.SecretVolumeSource{ 4230 SecretName: "s", 4231 DefaultMode: utilpointer.Int32(-1), 4232 }, 4233 }, 4234 }, 4235 errs: []verr{{ 4236 etype: field.ErrorTypeInvalid, 4237 field: "secret.defaultMode", 4238 }}, 4239 }, 4240 // ConfigMap 4241 { 4242 name: "valid ConfigMap", 4243 vol: core.Volume{ 4244 Name: "cfgmap", 4245 VolumeSource: core.VolumeSource{ 4246 ConfigMap: &core.ConfigMapVolumeSource{ 4247 LocalObjectReference: core.LocalObjectReference{ 4248 Name: "my-cfgmap", 4249 }, 4250 }, 4251 }, 4252 }, 4253 }, { 4254 name: "valid ConfigMap with defaultMode", 4255 vol: core.Volume{ 4256 Name: "cfgmap", 4257 VolumeSource: core.VolumeSource{ 4258 ConfigMap: &core.ConfigMapVolumeSource{ 4259 LocalObjectReference: core.LocalObjectReference{ 4260 Name: "my-cfgmap", 4261 }, 4262 DefaultMode: utilpointer.Int32(0644), 4263 }, 4264 }, 4265 }, 4266 }, { 4267 name: "valid ConfigMap with projection and mode", 4268 vol: core.Volume{ 4269 Name: "cfgmap", 4270 VolumeSource: core.VolumeSource{ 4271 ConfigMap: &core.ConfigMapVolumeSource{ 4272 LocalObjectReference: core.LocalObjectReference{ 4273 Name: "my-cfgmap"}, 4274 Items: []core.KeyToPath{{ 4275 Key: "key", 4276 Path: "filename", 4277 Mode: utilpointer.Int32(0644), 4278 }}, 4279 }, 4280 }, 4281 }, 4282 }, { 4283 name: "valid ConfigMap with subdir projection", 4284 vol: core.Volume{ 4285 Name: "cfgmap", 4286 VolumeSource: core.VolumeSource{ 4287 ConfigMap: &core.ConfigMapVolumeSource{ 4288 LocalObjectReference: core.LocalObjectReference{ 4289 Name: "my-cfgmap"}, 4290 Items: []core.KeyToPath{{ 4291 Key: "key", 4292 Path: "dir/filename", 4293 }}, 4294 }, 4295 }, 4296 }, 4297 }, { 4298 name: "configmap with missing path", 4299 vol: core.Volume{ 4300 Name: "cfgmap", 4301 VolumeSource: core.VolumeSource{ 4302 ConfigMap: &core.ConfigMapVolumeSource{ 4303 LocalObjectReference: core.LocalObjectReference{Name: "c"}, 4304 Items: []core.KeyToPath{{Key: "key", Path: ""}}, 4305 }, 4306 }, 4307 }, 4308 errs: []verr{{ 4309 etype: field.ErrorTypeRequired, 4310 field: "configMap.items[0].path", 4311 }}, 4312 }, { 4313 name: "configmap with leading ..", 4314 vol: core.Volume{ 4315 Name: "cfgmap", 4316 VolumeSource: core.VolumeSource{ 4317 ConfigMap: &core.ConfigMapVolumeSource{ 4318 LocalObjectReference: core.LocalObjectReference{Name: "c"}, 4319 Items: []core.KeyToPath{{Key: "key", Path: "../foo"}}, 4320 }, 4321 }, 4322 }, 4323 errs: []verr{{ 4324 etype: field.ErrorTypeInvalid, 4325 field: "configMap.items[0].path", 4326 }}, 4327 }, { 4328 name: "configmap with .. inside", 4329 vol: core.Volume{ 4330 Name: "cfgmap", 4331 VolumeSource: core.VolumeSource{ 4332 ConfigMap: &core.ConfigMapVolumeSource{ 4333 LocalObjectReference: core.LocalObjectReference{Name: "c"}, 4334 Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}}, 4335 }, 4336 }, 4337 }, 4338 errs: []verr{{ 4339 etype: field.ErrorTypeInvalid, 4340 field: "configMap.items[0].path", 4341 }}, 4342 }, { 4343 name: "configmap with invalid positive defaultMode", 4344 vol: core.Volume{ 4345 Name: "cfgmap", 4346 VolumeSource: core.VolumeSource{ 4347 ConfigMap: &core.ConfigMapVolumeSource{ 4348 LocalObjectReference: core.LocalObjectReference{Name: "c"}, 4349 DefaultMode: utilpointer.Int32(01000), 4350 }, 4351 }, 4352 }, 4353 errs: []verr{{ 4354 etype: field.ErrorTypeInvalid, 4355 field: "configMap.defaultMode", 4356 }}, 4357 }, { 4358 name: "configmap with invalid negative defaultMode", 4359 vol: core.Volume{ 4360 Name: "cfgmap", 4361 VolumeSource: core.VolumeSource{ 4362 ConfigMap: &core.ConfigMapVolumeSource{ 4363 LocalObjectReference: core.LocalObjectReference{Name: "c"}, 4364 DefaultMode: utilpointer.Int32(-1), 4365 }, 4366 }, 4367 }, 4368 errs: []verr{{ 4369 etype: field.ErrorTypeInvalid, 4370 field: "configMap.defaultMode", 4371 }}, 4372 }, 4373 // Glusterfs 4374 { 4375 name: "valid Glusterfs", 4376 vol: core.Volume{ 4377 Name: "glusterfs", 4378 VolumeSource: core.VolumeSource{ 4379 Glusterfs: &core.GlusterfsVolumeSource{ 4380 EndpointsName: "host1", 4381 Path: "path", 4382 ReadOnly: false, 4383 }, 4384 }, 4385 }, 4386 }, { 4387 name: "empty hosts", 4388 vol: core.Volume{ 4389 Name: "glusterfs", 4390 VolumeSource: core.VolumeSource{ 4391 Glusterfs: &core.GlusterfsVolumeSource{ 4392 EndpointsName: "", 4393 Path: "path", 4394 ReadOnly: false, 4395 }, 4396 }, 4397 }, 4398 errs: []verr{{ 4399 etype: field.ErrorTypeRequired, 4400 field: "glusterfs.endpoints", 4401 }}, 4402 }, { 4403 name: "empty path", 4404 vol: core.Volume{ 4405 Name: "glusterfs", 4406 VolumeSource: core.VolumeSource{ 4407 Glusterfs: &core.GlusterfsVolumeSource{ 4408 EndpointsName: "host", 4409 Path: "", 4410 ReadOnly: false, 4411 }, 4412 }, 4413 }, 4414 errs: []verr{{ 4415 etype: field.ErrorTypeRequired, 4416 field: "glusterfs.path", 4417 }}, 4418 }, 4419 // Flocker 4420 { 4421 name: "valid Flocker -- datasetUUID", 4422 vol: core.Volume{ 4423 Name: "flocker", 4424 VolumeSource: core.VolumeSource{ 4425 Flocker: &core.FlockerVolumeSource{ 4426 DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4", 4427 }, 4428 }, 4429 }, 4430 }, { 4431 name: "valid Flocker -- datasetName", 4432 vol: core.Volume{ 4433 Name: "flocker", 4434 VolumeSource: core.VolumeSource{ 4435 Flocker: &core.FlockerVolumeSource{ 4436 DatasetName: "datasetName", 4437 }, 4438 }, 4439 }, 4440 }, { 4441 name: "both empty", 4442 vol: core.Volume{ 4443 Name: "flocker", 4444 VolumeSource: core.VolumeSource{ 4445 Flocker: &core.FlockerVolumeSource{ 4446 DatasetName: "", 4447 }, 4448 }, 4449 }, 4450 errs: []verr{{ 4451 etype: field.ErrorTypeRequired, 4452 field: "flocker", 4453 }}, 4454 }, { 4455 name: "both specified", 4456 vol: core.Volume{ 4457 Name: "flocker", 4458 VolumeSource: core.VolumeSource{ 4459 Flocker: &core.FlockerVolumeSource{ 4460 DatasetName: "datasetName", 4461 DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4", 4462 }, 4463 }, 4464 }, 4465 errs: []verr{{ 4466 etype: field.ErrorTypeInvalid, 4467 field: "flocker", 4468 }}, 4469 }, { 4470 name: "slash in flocker datasetName", 4471 vol: core.Volume{ 4472 Name: "flocker", 4473 VolumeSource: core.VolumeSource{ 4474 Flocker: &core.FlockerVolumeSource{ 4475 DatasetName: "foo/bar", 4476 }, 4477 }, 4478 }, 4479 errs: []verr{{ 4480 etype: field.ErrorTypeInvalid, 4481 field: "flocker.datasetName", 4482 detail: "must not contain '/'", 4483 }}, 4484 }, 4485 // RBD 4486 { 4487 name: "valid RBD", 4488 vol: core.Volume{ 4489 Name: "rbd", 4490 VolumeSource: core.VolumeSource{ 4491 RBD: &core.RBDVolumeSource{ 4492 CephMonitors: []string{"foo"}, 4493 RBDImage: "bar", 4494 FSType: "ext4", 4495 }, 4496 }, 4497 }, 4498 }, { 4499 name: "empty rbd monitors", 4500 vol: core.Volume{ 4501 Name: "rbd", 4502 VolumeSource: core.VolumeSource{ 4503 RBD: &core.RBDVolumeSource{ 4504 CephMonitors: []string{}, 4505 RBDImage: "bar", 4506 FSType: "ext4", 4507 }, 4508 }, 4509 }, 4510 errs: []verr{{ 4511 etype: field.ErrorTypeRequired, 4512 field: "rbd.monitors", 4513 }}, 4514 }, { 4515 name: "empty image", 4516 vol: core.Volume{ 4517 Name: "rbd", 4518 VolumeSource: core.VolumeSource{ 4519 RBD: &core.RBDVolumeSource{ 4520 CephMonitors: []string{"foo"}, 4521 RBDImage: "", 4522 FSType: "ext4", 4523 }, 4524 }, 4525 }, 4526 errs: []verr{{ 4527 etype: field.ErrorTypeRequired, 4528 field: "rbd.image", 4529 }}, 4530 }, 4531 // Cinder 4532 { 4533 name: "valid Cinder", 4534 vol: core.Volume{ 4535 Name: "cinder", 4536 VolumeSource: core.VolumeSource{ 4537 Cinder: &core.CinderVolumeSource{ 4538 VolumeID: "29ea5088-4f60-4757-962e-dba678767887", 4539 FSType: "ext4", 4540 ReadOnly: false, 4541 }, 4542 }, 4543 }, 4544 }, 4545 // CephFS 4546 { 4547 name: "valid CephFS", 4548 vol: core.Volume{ 4549 Name: "cephfs", 4550 VolumeSource: core.VolumeSource{ 4551 CephFS: &core.CephFSVolumeSource{ 4552 Monitors: []string{"foo"}, 4553 }, 4554 }, 4555 }, 4556 }, { 4557 name: "empty cephfs monitors", 4558 vol: core.Volume{ 4559 Name: "cephfs", 4560 VolumeSource: core.VolumeSource{ 4561 CephFS: &core.CephFSVolumeSource{ 4562 Monitors: []string{}, 4563 }, 4564 }, 4565 }, 4566 errs: []verr{{ 4567 etype: field.ErrorTypeRequired, 4568 field: "cephfs.monitors", 4569 }}, 4570 }, 4571 // DownwardAPI 4572 { 4573 name: "valid DownwardAPI", 4574 vol: core.Volume{ 4575 Name: "downwardapi", 4576 VolumeSource: core.VolumeSource{ 4577 DownwardAPI: &core.DownwardAPIVolumeSource{ 4578 Items: []core.DownwardAPIVolumeFile{{ 4579 Path: "labels", 4580 FieldRef: &core.ObjectFieldSelector{ 4581 APIVersion: "v1", 4582 FieldPath: "metadata.labels", 4583 }, 4584 }, { 4585 Path: "labels with subscript", 4586 FieldRef: &core.ObjectFieldSelector{ 4587 APIVersion: "v1", 4588 FieldPath: "metadata.labels['key']", 4589 }, 4590 }, { 4591 Path: "labels with complex subscript", 4592 FieldRef: &core.ObjectFieldSelector{ 4593 APIVersion: "v1", 4594 FieldPath: "metadata.labels['test.example.com/key']", 4595 }, 4596 }, { 4597 Path: "annotations", 4598 FieldRef: &core.ObjectFieldSelector{ 4599 APIVersion: "v1", 4600 FieldPath: "metadata.annotations", 4601 }, 4602 }, { 4603 Path: "annotations with subscript", 4604 FieldRef: &core.ObjectFieldSelector{ 4605 APIVersion: "v1", 4606 FieldPath: "metadata.annotations['key']", 4607 }, 4608 }, { 4609 Path: "annotations with complex subscript", 4610 FieldRef: &core.ObjectFieldSelector{ 4611 APIVersion: "v1", 4612 FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']", 4613 }, 4614 }, { 4615 Path: "namespace", 4616 FieldRef: &core.ObjectFieldSelector{ 4617 APIVersion: "v1", 4618 FieldPath: "metadata.namespace", 4619 }, 4620 }, { 4621 Path: "name", 4622 FieldRef: &core.ObjectFieldSelector{ 4623 APIVersion: "v1", 4624 FieldPath: "metadata.name", 4625 }, 4626 }, { 4627 Path: "path/with/subdirs", 4628 FieldRef: &core.ObjectFieldSelector{ 4629 APIVersion: "v1", 4630 FieldPath: "metadata.labels", 4631 }, 4632 }, { 4633 Path: "path/./withdot", 4634 FieldRef: &core.ObjectFieldSelector{ 4635 APIVersion: "v1", 4636 FieldPath: "metadata.labels", 4637 }, 4638 }, { 4639 Path: "path/with/embedded..dotdot", 4640 FieldRef: &core.ObjectFieldSelector{ 4641 APIVersion: "v1", 4642 FieldPath: "metadata.labels", 4643 }, 4644 }, { 4645 Path: "path/with/leading/..dotdot", 4646 FieldRef: &core.ObjectFieldSelector{ 4647 APIVersion: "v1", 4648 FieldPath: "metadata.labels", 4649 }, 4650 }, { 4651 Path: "cpu_limit", 4652 ResourceFieldRef: &core.ResourceFieldSelector{ 4653 ContainerName: "test-container", 4654 Resource: "limits.cpu", 4655 }, 4656 }, { 4657 Path: "cpu_request", 4658 ResourceFieldRef: &core.ResourceFieldSelector{ 4659 ContainerName: "test-container", 4660 Resource: "requests.cpu", 4661 }, 4662 }, { 4663 Path: "memory_limit", 4664 ResourceFieldRef: &core.ResourceFieldSelector{ 4665 ContainerName: "test-container", 4666 Resource: "limits.memory", 4667 }, 4668 }, { 4669 Path: "memory_request", 4670 ResourceFieldRef: &core.ResourceFieldSelector{ 4671 ContainerName: "test-container", 4672 Resource: "requests.memory", 4673 }, 4674 }}, 4675 }, 4676 }, 4677 }, 4678 }, { 4679 name: "hugepages-downwardAPI-enabled", 4680 vol: core.Volume{ 4681 Name: "downwardapi", 4682 VolumeSource: core.VolumeSource{ 4683 DownwardAPI: &core.DownwardAPIVolumeSource{ 4684 Items: []core.DownwardAPIVolumeFile{{ 4685 Path: "hugepages_request", 4686 ResourceFieldRef: &core.ResourceFieldSelector{ 4687 ContainerName: "test-container", 4688 Resource: "requests.hugepages-2Mi", 4689 }, 4690 }, { 4691 Path: "hugepages_limit", 4692 ResourceFieldRef: &core.ResourceFieldSelector{ 4693 ContainerName: "test-container", 4694 Resource: "limits.hugepages-2Mi", 4695 }, 4696 }}, 4697 }, 4698 }, 4699 }, 4700 }, { 4701 name: "downapi valid defaultMode", 4702 vol: core.Volume{ 4703 Name: "downapi", 4704 VolumeSource: core.VolumeSource{ 4705 DownwardAPI: &core.DownwardAPIVolumeSource{ 4706 DefaultMode: utilpointer.Int32(0644), 4707 }, 4708 }, 4709 }, 4710 }, { 4711 name: "downapi valid item mode", 4712 vol: core.Volume{ 4713 Name: "downapi", 4714 VolumeSource: core.VolumeSource{ 4715 DownwardAPI: &core.DownwardAPIVolumeSource{ 4716 Items: []core.DownwardAPIVolumeFile{{ 4717 Mode: utilpointer.Int32(0644), 4718 Path: "path", 4719 FieldRef: &core.ObjectFieldSelector{ 4720 APIVersion: "v1", 4721 FieldPath: "metadata.labels", 4722 }, 4723 }}, 4724 }, 4725 }, 4726 }, 4727 }, { 4728 name: "downapi invalid positive item mode", 4729 vol: core.Volume{ 4730 Name: "downapi", 4731 VolumeSource: core.VolumeSource{ 4732 DownwardAPI: &core.DownwardAPIVolumeSource{ 4733 Items: []core.DownwardAPIVolumeFile{{ 4734 Mode: utilpointer.Int32(01000), 4735 Path: "path", 4736 FieldRef: &core.ObjectFieldSelector{ 4737 APIVersion: "v1", 4738 FieldPath: "metadata.labels", 4739 }, 4740 }}, 4741 }, 4742 }, 4743 }, 4744 errs: []verr{{ 4745 etype: field.ErrorTypeInvalid, 4746 field: "downwardAPI.mode", 4747 }}, 4748 }, { 4749 name: "downapi invalid negative item mode", 4750 vol: core.Volume{ 4751 Name: "downapi", 4752 VolumeSource: core.VolumeSource{ 4753 DownwardAPI: &core.DownwardAPIVolumeSource{ 4754 Items: []core.DownwardAPIVolumeFile{{ 4755 Mode: utilpointer.Int32(-1), 4756 Path: "path", 4757 FieldRef: &core.ObjectFieldSelector{ 4758 APIVersion: "v1", 4759 FieldPath: "metadata.labels", 4760 }, 4761 }}, 4762 }, 4763 }, 4764 }, 4765 errs: []verr{{ 4766 etype: field.ErrorTypeInvalid, 4767 field: "downwardAPI.mode", 4768 }}, 4769 }, { 4770 name: "downapi empty metatada path", 4771 vol: core.Volume{ 4772 Name: "downapi", 4773 VolumeSource: core.VolumeSource{ 4774 DownwardAPI: &core.DownwardAPIVolumeSource{ 4775 Items: []core.DownwardAPIVolumeFile{{ 4776 Path: "", 4777 FieldRef: &core.ObjectFieldSelector{ 4778 APIVersion: "v1", 4779 FieldPath: "metadata.labels", 4780 }, 4781 }}, 4782 }, 4783 }, 4784 }, 4785 errs: []verr{{ 4786 etype: field.ErrorTypeRequired, 4787 field: "downwardAPI.path", 4788 }}, 4789 }, { 4790 name: "downapi absolute path", 4791 vol: core.Volume{ 4792 Name: "downapi", 4793 VolumeSource: core.VolumeSource{ 4794 DownwardAPI: &core.DownwardAPIVolumeSource{ 4795 Items: []core.DownwardAPIVolumeFile{{ 4796 Path: "/absolutepath", 4797 FieldRef: &core.ObjectFieldSelector{ 4798 APIVersion: "v1", 4799 FieldPath: "metadata.labels", 4800 }, 4801 }}, 4802 }, 4803 }, 4804 }, 4805 errs: []verr{{ 4806 etype: field.ErrorTypeInvalid, 4807 field: "downwardAPI.path", 4808 }}, 4809 }, { 4810 name: "downapi dot dot path", 4811 vol: core.Volume{ 4812 Name: "downapi", 4813 VolumeSource: core.VolumeSource{ 4814 DownwardAPI: &core.DownwardAPIVolumeSource{ 4815 Items: []core.DownwardAPIVolumeFile{{ 4816 Path: "../../passwd", 4817 FieldRef: &core.ObjectFieldSelector{ 4818 APIVersion: "v1", 4819 FieldPath: "metadata.labels", 4820 }, 4821 }}, 4822 }, 4823 }, 4824 }, 4825 errs: []verr{{ 4826 etype: field.ErrorTypeInvalid, 4827 field: "downwardAPI.path", 4828 detail: `must not contain '..'`, 4829 }}, 4830 }, { 4831 name: "downapi dot dot file name", 4832 vol: core.Volume{ 4833 Name: "downapi", 4834 VolumeSource: core.VolumeSource{ 4835 DownwardAPI: &core.DownwardAPIVolumeSource{ 4836 Items: []core.DownwardAPIVolumeFile{{ 4837 Path: "..badFileName", 4838 FieldRef: &core.ObjectFieldSelector{ 4839 APIVersion: "v1", 4840 FieldPath: "metadata.labels", 4841 }, 4842 }}, 4843 }, 4844 }, 4845 }, 4846 errs: []verr{{ 4847 etype: field.ErrorTypeInvalid, 4848 field: "downwardAPI.path", 4849 detail: `must not start with '..'`, 4850 }}, 4851 }, { 4852 name: "downapi dot dot first level dirent", 4853 vol: core.Volume{ 4854 Name: "downapi", 4855 VolumeSource: core.VolumeSource{ 4856 DownwardAPI: &core.DownwardAPIVolumeSource{ 4857 Items: []core.DownwardAPIVolumeFile{{ 4858 Path: "..badDirName/goodFileName", 4859 FieldRef: &core.ObjectFieldSelector{ 4860 APIVersion: "v1", 4861 FieldPath: "metadata.labels", 4862 }, 4863 }}, 4864 }, 4865 }, 4866 }, 4867 errs: []verr{{ 4868 etype: field.ErrorTypeInvalid, 4869 field: "downwardAPI.path", 4870 detail: `must not start with '..'`, 4871 }}, 4872 }, { 4873 name: "downapi fieldRef and ResourceFieldRef together", 4874 vol: core.Volume{ 4875 Name: "downapi", 4876 VolumeSource: core.VolumeSource{ 4877 DownwardAPI: &core.DownwardAPIVolumeSource{ 4878 Items: []core.DownwardAPIVolumeFile{{ 4879 Path: "test", 4880 FieldRef: &core.ObjectFieldSelector{ 4881 APIVersion: "v1", 4882 FieldPath: "metadata.labels", 4883 }, 4884 ResourceFieldRef: &core.ResourceFieldSelector{ 4885 ContainerName: "test-container", 4886 Resource: "requests.memory", 4887 }, 4888 }}, 4889 }, 4890 }, 4891 }, 4892 errs: []verr{{ 4893 etype: field.ErrorTypeInvalid, 4894 field: "downwardAPI", 4895 detail: "fieldRef and resourceFieldRef can not be specified simultaneously", 4896 }}, 4897 }, { 4898 name: "downapi invalid positive defaultMode", 4899 vol: core.Volume{ 4900 Name: "downapi", 4901 VolumeSource: core.VolumeSource{ 4902 DownwardAPI: &core.DownwardAPIVolumeSource{ 4903 DefaultMode: utilpointer.Int32(01000), 4904 }, 4905 }, 4906 }, 4907 errs: []verr{{ 4908 etype: field.ErrorTypeInvalid, 4909 field: "downwardAPI.defaultMode", 4910 }}, 4911 }, { 4912 name: "downapi invalid negative defaultMode", 4913 vol: core.Volume{ 4914 Name: "downapi", 4915 VolumeSource: core.VolumeSource{ 4916 DownwardAPI: &core.DownwardAPIVolumeSource{ 4917 DefaultMode: utilpointer.Int32(-1), 4918 }, 4919 }, 4920 }, 4921 errs: []verr{{ 4922 etype: field.ErrorTypeInvalid, 4923 field: "downwardAPI.defaultMode", 4924 }}, 4925 }, 4926 // FC 4927 { 4928 name: "FC valid targetWWNs and lun", 4929 vol: core.Volume{ 4930 Name: "fc", 4931 VolumeSource: core.VolumeSource{ 4932 FC: &core.FCVolumeSource{ 4933 TargetWWNs: []string{"some_wwn"}, 4934 Lun: utilpointer.Int32(1), 4935 FSType: "ext4", 4936 ReadOnly: false, 4937 }, 4938 }, 4939 }, 4940 }, { 4941 name: "FC valid wwids", 4942 vol: core.Volume{ 4943 Name: "fc", 4944 VolumeSource: core.VolumeSource{ 4945 FC: &core.FCVolumeSource{ 4946 WWIDs: []string{"some_wwid"}, 4947 FSType: "ext4", 4948 ReadOnly: false, 4949 }, 4950 }, 4951 }, 4952 }, { 4953 name: "FC empty targetWWNs and wwids", 4954 vol: core.Volume{ 4955 Name: "fc", 4956 VolumeSource: core.VolumeSource{ 4957 FC: &core.FCVolumeSource{ 4958 TargetWWNs: []string{}, 4959 Lun: utilpointer.Int32(1), 4960 WWIDs: []string{}, 4961 FSType: "ext4", 4962 ReadOnly: false, 4963 }, 4964 }, 4965 }, 4966 errs: []verr{{ 4967 etype: field.ErrorTypeRequired, 4968 field: "fc.targetWWNs", 4969 detail: "must specify either targetWWNs or wwids", 4970 }}, 4971 }, { 4972 name: "FC invalid: both targetWWNs and wwids simultaneously", 4973 vol: core.Volume{ 4974 Name: "fc", 4975 VolumeSource: core.VolumeSource{ 4976 FC: &core.FCVolumeSource{ 4977 TargetWWNs: []string{"some_wwn"}, 4978 Lun: utilpointer.Int32(1), 4979 WWIDs: []string{"some_wwid"}, 4980 FSType: "ext4", 4981 ReadOnly: false, 4982 }, 4983 }, 4984 }, 4985 errs: []verr{{ 4986 etype: field.ErrorTypeInvalid, 4987 field: "fc.targetWWNs", 4988 detail: "targetWWNs and wwids can not be specified simultaneously", 4989 }}, 4990 }, { 4991 name: "FC valid targetWWNs and empty lun", 4992 vol: core.Volume{ 4993 Name: "fc", 4994 VolumeSource: core.VolumeSource{ 4995 FC: &core.FCVolumeSource{ 4996 TargetWWNs: []string{"wwn"}, 4997 Lun: nil, 4998 FSType: "ext4", 4999 ReadOnly: false, 5000 }, 5001 }, 5002 }, 5003 errs: []verr{{ 5004 etype: field.ErrorTypeRequired, 5005 field: "fc.lun", 5006 detail: "lun is required if targetWWNs is specified", 5007 }}, 5008 }, { 5009 name: "FC valid targetWWNs and invalid lun", 5010 vol: core.Volume{ 5011 Name: "fc", 5012 VolumeSource: core.VolumeSource{ 5013 FC: &core.FCVolumeSource{ 5014 TargetWWNs: []string{"wwn"}, 5015 Lun: utilpointer.Int32(256), 5016 FSType: "ext4", 5017 ReadOnly: false, 5018 }, 5019 }, 5020 }, 5021 errs: []verr{{ 5022 etype: field.ErrorTypeInvalid, 5023 field: "fc.lun", 5024 detail: validation.InclusiveRangeError(0, 255), 5025 }}, 5026 }, 5027 // FlexVolume 5028 { 5029 name: "valid FlexVolume", 5030 vol: core.Volume{ 5031 Name: "flex-volume", 5032 VolumeSource: core.VolumeSource{ 5033 FlexVolume: &core.FlexVolumeSource{ 5034 Driver: "kubernetes.io/blue", 5035 FSType: "ext4", 5036 }, 5037 }, 5038 }, 5039 }, 5040 // AzureFile 5041 { 5042 name: "valid AzureFile", 5043 vol: core.Volume{ 5044 Name: "azure-file", 5045 VolumeSource: core.VolumeSource{ 5046 AzureFile: &core.AzureFileVolumeSource{ 5047 SecretName: "key", 5048 ShareName: "share", 5049 ReadOnly: false, 5050 }, 5051 }, 5052 }, 5053 }, { 5054 name: "AzureFile empty secret", 5055 vol: core.Volume{ 5056 Name: "azure-file", 5057 VolumeSource: core.VolumeSource{ 5058 AzureFile: &core.AzureFileVolumeSource{ 5059 SecretName: "", 5060 ShareName: "share", 5061 ReadOnly: false, 5062 }, 5063 }, 5064 }, 5065 errs: []verr{{ 5066 etype: field.ErrorTypeRequired, 5067 field: "azureFile.secretName", 5068 }}, 5069 }, { 5070 name: "AzureFile empty share", 5071 vol: core.Volume{ 5072 Name: "azure-file", 5073 VolumeSource: core.VolumeSource{ 5074 AzureFile: &core.AzureFileVolumeSource{ 5075 SecretName: "name", 5076 ShareName: "", 5077 ReadOnly: false, 5078 }, 5079 }, 5080 }, 5081 errs: []verr{{ 5082 etype: field.ErrorTypeRequired, 5083 field: "azureFile.shareName", 5084 }}, 5085 }, 5086 // Quobyte 5087 { 5088 name: "valid Quobyte", 5089 vol: core.Volume{ 5090 Name: "quobyte", 5091 VolumeSource: core.VolumeSource{ 5092 Quobyte: &core.QuobyteVolumeSource{ 5093 Registry: "registry:7861", 5094 Volume: "volume", 5095 ReadOnly: false, 5096 User: "root", 5097 Group: "root", 5098 Tenant: "ThisIsSomeTenantUUID", 5099 }, 5100 }, 5101 }, 5102 }, { 5103 name: "empty registry quobyte", 5104 vol: core.Volume{ 5105 Name: "quobyte", 5106 VolumeSource: core.VolumeSource{ 5107 Quobyte: &core.QuobyteVolumeSource{ 5108 Volume: "/test", 5109 Tenant: "ThisIsSomeTenantUUID", 5110 }, 5111 }, 5112 }, 5113 errs: []verr{{ 5114 etype: field.ErrorTypeRequired, 5115 field: "quobyte.registry", 5116 }}, 5117 }, { 5118 name: "wrong format registry quobyte", 5119 vol: core.Volume{ 5120 Name: "quobyte", 5121 VolumeSource: core.VolumeSource{ 5122 Quobyte: &core.QuobyteVolumeSource{ 5123 Registry: "registry7861", 5124 Volume: "/test", 5125 Tenant: "ThisIsSomeTenantUUID", 5126 }, 5127 }, 5128 }, 5129 errs: []verr{{ 5130 etype: field.ErrorTypeInvalid, 5131 field: "quobyte.registry", 5132 }}, 5133 }, { 5134 name: "wrong format multiple registries quobyte", 5135 vol: core.Volume{ 5136 Name: "quobyte", 5137 VolumeSource: core.VolumeSource{ 5138 Quobyte: &core.QuobyteVolumeSource{ 5139 Registry: "registry:7861,reg2", 5140 Volume: "/test", 5141 Tenant: "ThisIsSomeTenantUUID", 5142 }, 5143 }, 5144 }, 5145 errs: []verr{{ 5146 etype: field.ErrorTypeInvalid, 5147 field: "quobyte.registry", 5148 }}, 5149 }, { 5150 name: "empty volume quobyte", 5151 vol: core.Volume{ 5152 Name: "quobyte", 5153 VolumeSource: core.VolumeSource{ 5154 Quobyte: &core.QuobyteVolumeSource{ 5155 Registry: "registry:7861", 5156 Tenant: "ThisIsSomeTenantUUID", 5157 }, 5158 }, 5159 }, 5160 errs: []verr{{ 5161 etype: field.ErrorTypeRequired, 5162 field: "quobyte.volume", 5163 }}, 5164 }, { 5165 name: "empty tenant quobyte", 5166 vol: core.Volume{ 5167 Name: "quobyte", 5168 VolumeSource: core.VolumeSource{ 5169 Quobyte: &core.QuobyteVolumeSource{ 5170 Registry: "registry:7861", 5171 Volume: "/test", 5172 Tenant: "", 5173 }, 5174 }, 5175 }, 5176 }, { 5177 name: "too long tenant quobyte", 5178 vol: core.Volume{ 5179 Name: "quobyte", 5180 VolumeSource: core.VolumeSource{ 5181 Quobyte: &core.QuobyteVolumeSource{ 5182 Registry: "registry:7861", 5183 Volume: "/test", 5184 Tenant: "this is too long to be a valid uuid so this test has to fail on the maximum length validation of the tenant.", 5185 }, 5186 }, 5187 }, 5188 errs: []verr{{ 5189 etype: field.ErrorTypeRequired, 5190 field: "quobyte.tenant", 5191 }}, 5192 }, 5193 // AzureDisk 5194 { 5195 name: "valid AzureDisk", 5196 vol: core.Volume{ 5197 Name: "azure-disk", 5198 VolumeSource: core.VolumeSource{ 5199 AzureDisk: &core.AzureDiskVolumeSource{ 5200 DiskName: "foo", 5201 DataDiskURI: "https://blob/vhds/bar.vhd", 5202 }, 5203 }, 5204 }, 5205 }, { 5206 name: "AzureDisk empty disk name", 5207 vol: core.Volume{ 5208 Name: "azure-disk", 5209 VolumeSource: core.VolumeSource{ 5210 AzureDisk: &core.AzureDiskVolumeSource{ 5211 DiskName: "", 5212 DataDiskURI: "https://blob/vhds/bar.vhd", 5213 }, 5214 }, 5215 }, 5216 errs: []verr{{ 5217 etype: field.ErrorTypeRequired, 5218 field: "azureDisk.diskName", 5219 }}, 5220 }, { 5221 name: "AzureDisk empty disk uri", 5222 vol: core.Volume{ 5223 Name: "azure-disk", 5224 VolumeSource: core.VolumeSource{ 5225 AzureDisk: &core.AzureDiskVolumeSource{ 5226 DiskName: "foo", 5227 DataDiskURI: "", 5228 }, 5229 }, 5230 }, 5231 errs: []verr{{ 5232 etype: field.ErrorTypeRequired, 5233 field: "azureDisk.diskURI", 5234 }}, 5235 }, 5236 // ScaleIO 5237 { 5238 name: "valid scaleio volume", 5239 vol: core.Volume{ 5240 Name: "scaleio-volume", 5241 VolumeSource: core.VolumeSource{ 5242 ScaleIO: &core.ScaleIOVolumeSource{ 5243 Gateway: "http://abcd/efg", 5244 System: "test-system", 5245 VolumeName: "test-vol-1", 5246 }, 5247 }, 5248 }, 5249 }, { 5250 name: "ScaleIO with empty name", 5251 vol: core.Volume{ 5252 Name: "scaleio-volume", 5253 VolumeSource: core.VolumeSource{ 5254 ScaleIO: &core.ScaleIOVolumeSource{ 5255 Gateway: "http://abcd/efg", 5256 System: "test-system", 5257 VolumeName: "", 5258 }, 5259 }, 5260 }, 5261 errs: []verr{{ 5262 etype: field.ErrorTypeRequired, 5263 field: "scaleIO.volumeName", 5264 }}, 5265 }, { 5266 name: "ScaleIO with empty gateway", 5267 vol: core.Volume{ 5268 Name: "scaleio-volume", 5269 VolumeSource: core.VolumeSource{ 5270 ScaleIO: &core.ScaleIOVolumeSource{ 5271 Gateway: "", 5272 System: "test-system", 5273 VolumeName: "test-vol-1", 5274 }, 5275 }, 5276 }, 5277 errs: []verr{{ 5278 etype: field.ErrorTypeRequired, 5279 field: "scaleIO.gateway", 5280 }}, 5281 }, { 5282 name: "ScaleIO with empty system", 5283 vol: core.Volume{ 5284 Name: "scaleio-volume", 5285 VolumeSource: core.VolumeSource{ 5286 ScaleIO: &core.ScaleIOVolumeSource{ 5287 Gateway: "http://agc/efg/gateway", 5288 System: "", 5289 VolumeName: "test-vol-1", 5290 }, 5291 }, 5292 }, 5293 errs: []verr{{ 5294 etype: field.ErrorTypeRequired, 5295 field: "scaleIO.system", 5296 }}, 5297 }, 5298 // ProjectedVolumeSource 5299 { 5300 name: "ProjectedVolumeSource more than one projection in a source", 5301 vol: core.Volume{ 5302 Name: "projected-volume", 5303 VolumeSource: core.VolumeSource{ 5304 Projected: &core.ProjectedVolumeSource{ 5305 Sources: []core.VolumeProjection{{ 5306 Secret: &core.SecretProjection{ 5307 LocalObjectReference: core.LocalObjectReference{ 5308 Name: "foo", 5309 }, 5310 }, 5311 }, { 5312 Secret: &core.SecretProjection{ 5313 LocalObjectReference: core.LocalObjectReference{ 5314 Name: "foo", 5315 }, 5316 }, 5317 DownwardAPI: &core.DownwardAPIProjection{}, 5318 }}, 5319 }, 5320 }, 5321 }, 5322 errs: []verr{{ 5323 etype: field.ErrorTypeForbidden, 5324 field: "projected.sources[1]", 5325 }}, 5326 }, { 5327 name: "ProjectedVolumeSource more than one projection in a source", 5328 vol: core.Volume{ 5329 Name: "projected-volume", 5330 VolumeSource: core.VolumeSource{ 5331 Projected: &core.ProjectedVolumeSource{ 5332 Sources: []core.VolumeProjection{{ 5333 Secret: &core.SecretProjection{}, 5334 }, { 5335 Secret: &core.SecretProjection{}, 5336 DownwardAPI: &core.DownwardAPIProjection{}, 5337 }}, 5338 }, 5339 }, 5340 }, 5341 errs: []verr{{ 5342 etype: field.ErrorTypeRequired, 5343 field: "projected.sources[0].secret.name", 5344 }, { 5345 etype: field.ErrorTypeRequired, 5346 field: "projected.sources[1].secret.name", 5347 }, { 5348 etype: field.ErrorTypeForbidden, 5349 field: "projected.sources[1]", 5350 }}, 5351 }, 5352 } 5353 5354 for _, tc := range testCases { 5355 t.Run(tc.name, func(t *testing.T) { 5356 names, errs := ValidateVolumes([]core.Volume{tc.vol}, nil, field.NewPath("field"), tc.opts) 5357 if len(errs) != len(tc.errs) { 5358 t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs) 5359 } 5360 if len(errs) == 0 && (len(names) > 1 || !IsMatchedVolume(tc.vol.Name, names)) { 5361 t.Errorf("wrong names result: %v", names) 5362 } 5363 for i, err := range errs { 5364 expErr := tc.errs[i] 5365 if err.Type != expErr.etype { 5366 t.Errorf("unexpected error type:\n\twant: %q\n\t got: %q", expErr.etype, err.Type) 5367 } 5368 if !strings.HasSuffix(err.Field, "."+expErr.field) { 5369 t.Errorf("unexpected error field:\n\twant: %q\n\t got: %q", expErr.field, err.Field) 5370 } 5371 if !strings.Contains(err.Detail, expErr.detail) { 5372 t.Errorf("unexpected error detail:\n\twant: %q\n\t got: %q", expErr.detail, err.Detail) 5373 } 5374 } 5375 }) 5376 } 5377 5378 dupsCase := []core.Volume{ 5379 {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 5380 {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 5381 } 5382 _, errs := ValidateVolumes(dupsCase, nil, field.NewPath("field"), PodValidationOptions{}) 5383 if len(errs) == 0 { 5384 t.Errorf("expected error") 5385 } else if len(errs) != 1 { 5386 t.Errorf("expected 1 error, got %d: %v", len(errs), errs) 5387 } else if errs[0].Type != field.ErrorTypeDuplicate { 5388 t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type) 5389 } 5390 5391 // Validate HugePages medium type for EmptyDir 5392 hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}} 5393 5394 // Enable HugePages 5395 if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working", nil, PodValidationOptions{}); len(errs) != 0 { 5396 t.Errorf("Unexpected error when HugePages feature is enabled.") 5397 } 5398 5399 } 5400 5401 func TestValidateReadOnlyPersistentDisks(t *testing.T) { 5402 cases := []struct { 5403 name string 5404 volumes []core.Volume 5405 oldVolume []core.Volume 5406 gateValue bool 5407 expectError bool 5408 }{{ 5409 name: "gate on, read-only disk, nil old", 5410 gateValue: true, 5411 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5412 oldVolume: []core.Volume(nil), 5413 expectError: false, 5414 }, { 5415 name: "gate off, read-only disk, nil old", 5416 gateValue: false, 5417 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5418 oldVolume: []core.Volume(nil), 5419 expectError: false, 5420 }, { 5421 name: "gate on, read-write, nil old", 5422 gateValue: true, 5423 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5424 oldVolume: []core.Volume(nil), 5425 expectError: false, 5426 }, { 5427 name: "gate off, read-write, nil old", 5428 gateValue: false, 5429 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5430 oldVolume: []core.Volume(nil), 5431 expectError: true, 5432 }, { 5433 name: "gate on, new read-only and old read-write", 5434 gateValue: true, 5435 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5436 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5437 expectError: false, 5438 }, { 5439 name: "gate off, new read-only and old read-write", 5440 gateValue: false, 5441 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5442 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5443 expectError: false, 5444 }, { 5445 name: "gate on, new read-write and old read-write", 5446 gateValue: true, 5447 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5448 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5449 expectError: false, 5450 }, { 5451 name: "gate off, new read-write and old read-write", 5452 gateValue: false, 5453 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5454 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5455 expectError: false, 5456 }, { 5457 name: "gate on, new read-only and old read-only", 5458 gateValue: true, 5459 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5460 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5461 expectError: false, 5462 }, { 5463 name: "gate off, new read-only and old read-only", 5464 gateValue: false, 5465 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5466 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5467 expectError: false, 5468 }, { 5469 name: "gate on, new read-write and old read-only", 5470 gateValue: true, 5471 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5472 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5473 expectError: false, 5474 }, { 5475 name: "gate off, new read-write and old read-only", 5476 gateValue: false, 5477 volumes: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}}, 5478 oldVolume: []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}}, 5479 expectError: true, 5480 }, 5481 } 5482 for _, testCase := range cases { 5483 t.Run(testCase.name, func(t *testing.T) { 5484 fidPath := field.NewPath("testField") 5485 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, testCase.gateValue)() 5486 errs := ValidateReadOnlyPersistentDisks(testCase.volumes, testCase.oldVolume, fidPath) 5487 if !testCase.expectError && len(errs) != 0 { 5488 t.Errorf("expected success, got:%v", errs) 5489 } 5490 }) 5491 } 5492 } 5493 5494 func TestHugePagesIsolation(t *testing.T) { 5495 testCases := map[string]struct { 5496 pod *core.Pod 5497 expectError bool 5498 }{ 5499 "Valid: request hugepages-2Mi": { 5500 pod: &core.Pod{ 5501 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 5502 Spec: core.PodSpec{ 5503 Containers: []core.Container{{ 5504 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 5505 Resources: core.ResourceRequirements{ 5506 Requests: core.ResourceList{ 5507 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5508 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5509 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5510 }, 5511 Limits: core.ResourceList{ 5512 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5513 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5514 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5515 }, 5516 }, 5517 }}, 5518 RestartPolicy: core.RestartPolicyAlways, 5519 DNSPolicy: core.DNSClusterFirst, 5520 }, 5521 }, 5522 }, 5523 "Valid: request more than one hugepages size": { 5524 pod: &core.Pod{ 5525 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"}, 5526 Spec: core.PodSpec{ 5527 Containers: []core.Container{{ 5528 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 5529 Resources: core.ResourceRequirements{ 5530 Requests: core.ResourceList{ 5531 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5532 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5533 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5534 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), 5535 }, 5536 Limits: core.ResourceList{ 5537 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5538 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5539 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5540 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), 5541 }, 5542 }, 5543 }}, 5544 RestartPolicy: core.RestartPolicyAlways, 5545 DNSPolicy: core.DNSClusterFirst, 5546 }, 5547 }, 5548 expectError: false, 5549 }, 5550 "Valid: request hugepages-1Gi, limit hugepages-2Mi and hugepages-1Gi": { 5551 pod: &core.Pod{ 5552 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"}, 5553 Spec: core.PodSpec{ 5554 Containers: []core.Container{{ 5555 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 5556 Resources: core.ResourceRequirements{ 5557 Requests: core.ResourceList{ 5558 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5559 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5560 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5561 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), 5562 }, 5563 Limits: core.ResourceList{ 5564 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5565 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5566 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5567 core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), 5568 }, 5569 }, 5570 }}, 5571 RestartPolicy: core.RestartPolicyAlways, 5572 DNSPolicy: core.DNSClusterFirst, 5573 }, 5574 }, 5575 }, 5576 "Invalid: not requesting cpu and memory": { 5577 pod: &core.Pod{ 5578 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"}, 5579 Spec: core.PodSpec{ 5580 Containers: []core.Container{{ 5581 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 5582 Resources: core.ResourceRequirements{ 5583 Requests: core.ResourceList{ 5584 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5585 }, 5586 Limits: core.ResourceList{ 5587 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5588 }, 5589 }, 5590 }}, 5591 RestartPolicy: core.RestartPolicyAlways, 5592 DNSPolicy: core.DNSClusterFirst, 5593 }, 5594 }, 5595 expectError: true, 5596 }, 5597 "Invalid: request 1Gi hugepages-2Mi but limit 2Gi": { 5598 pod: &core.Pod{ 5599 ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"}, 5600 Spec: core.PodSpec{ 5601 Containers: []core.Container{{ 5602 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 5603 Resources: core.ResourceRequirements{ 5604 Requests: core.ResourceList{ 5605 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5606 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5607 core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"), 5608 }, 5609 Limits: core.ResourceList{ 5610 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 5611 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 5612 core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"), 5613 }, 5614 }, 5615 }}, 5616 RestartPolicy: core.RestartPolicyAlways, 5617 DNSPolicy: core.DNSClusterFirst, 5618 }, 5619 }, 5620 expectError: true, 5621 }, 5622 } 5623 for tcName, tc := range testCases { 5624 t.Run(tcName, func(t *testing.T) { 5625 errs := ValidatePodCreate(tc.pod, PodValidationOptions{}) 5626 if tc.expectError && len(errs) == 0 { 5627 t.Errorf("Unexpected success") 5628 } 5629 if !tc.expectError && len(errs) != 0 { 5630 t.Errorf("Unexpected error(s): %v", errs) 5631 } 5632 }) 5633 } 5634 } 5635 5636 func TestPVCVolumeMode(t *testing.T) { 5637 block := core.PersistentVolumeBlock 5638 file := core.PersistentVolumeFilesystem 5639 fake := core.PersistentVolumeMode("fake") 5640 empty := core.PersistentVolumeMode("") 5641 5642 // Success Cases 5643 successCasesPVC := map[string]*core.PersistentVolumeClaim{ 5644 "valid block value": createTestVolModePVC(&block), 5645 "valid filesystem value": createTestVolModePVC(&file), 5646 "valid nil value": createTestVolModePVC(nil), 5647 } 5648 for k, v := range successCasesPVC { 5649 opts := ValidationOptionsForPersistentVolumeClaim(v, nil) 5650 if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) != 0 { 5651 t.Errorf("expected success for %s", k) 5652 } 5653 } 5654 5655 // Error Cases 5656 errorCasesPVC := map[string]*core.PersistentVolumeClaim{ 5657 "invalid value": createTestVolModePVC(&fake), 5658 "empty value": createTestVolModePVC(&empty), 5659 } 5660 for k, v := range errorCasesPVC { 5661 opts := ValidationOptionsForPersistentVolumeClaim(v, nil) 5662 if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) == 0 { 5663 t.Errorf("expected failure for %s", k) 5664 } 5665 } 5666 } 5667 5668 func TestPVVolumeMode(t *testing.T) { 5669 block := core.PersistentVolumeBlock 5670 file := core.PersistentVolumeFilesystem 5671 fake := core.PersistentVolumeMode("fake") 5672 empty := core.PersistentVolumeMode("") 5673 5674 // Success Cases 5675 successCasesPV := map[string]*core.PersistentVolume{ 5676 "valid block value": createTestVolModePV(&block), 5677 "valid filesystem value": createTestVolModePV(&file), 5678 "valid nil value": createTestVolModePV(nil), 5679 } 5680 for k, v := range successCasesPV { 5681 opts := ValidationOptionsForPersistentVolume(v, nil) 5682 if errs := ValidatePersistentVolume(v, opts); len(errs) != 0 { 5683 t.Errorf("expected success for %s", k) 5684 } 5685 } 5686 5687 // Error Cases 5688 errorCasesPV := map[string]*core.PersistentVolume{ 5689 "invalid value": createTestVolModePV(&fake), 5690 "empty value": createTestVolModePV(&empty), 5691 } 5692 for k, v := range errorCasesPV { 5693 opts := ValidationOptionsForPersistentVolume(v, nil) 5694 if errs := ValidatePersistentVolume(v, opts); len(errs) == 0 { 5695 t.Errorf("expected failure for %s", k) 5696 } 5697 } 5698 } 5699 5700 func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim { 5701 validName := "valid-storage-class" 5702 5703 pvc := core.PersistentVolumeClaim{ 5704 ObjectMeta: metav1.ObjectMeta{ 5705 Name: "foo", 5706 Namespace: "default", 5707 }, 5708 Spec: core.PersistentVolumeClaimSpec{ 5709 Resources: core.VolumeResourceRequirements{ 5710 Requests: core.ResourceList{ 5711 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 5712 }, 5713 }, 5714 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 5715 StorageClassName: &validName, 5716 VolumeMode: vmode, 5717 }, 5718 } 5719 return &pvc 5720 } 5721 5722 func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume { 5723 5724 // PersistentVolume with VolumeMode set (valid and invalid) 5725 pv := core.PersistentVolume{ 5726 ObjectMeta: metav1.ObjectMeta{ 5727 Name: "foo", 5728 Namespace: "", 5729 }, 5730 Spec: core.PersistentVolumeSpec{ 5731 Capacity: core.ResourceList{ 5732 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 5733 }, 5734 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 5735 PersistentVolumeSource: core.PersistentVolumeSource{ 5736 HostPath: &core.HostPathVolumeSource{ 5737 Path: "/foo", 5738 Type: newHostPathType(string(core.HostPathDirectory)), 5739 }, 5740 }, 5741 StorageClassName: "test-storage-class", 5742 VolumeMode: vmode, 5743 }, 5744 } 5745 return &pv 5746 } 5747 5748 func createTestPV() *core.PersistentVolume { 5749 5750 // PersistentVolume with VolumeMode set (valid and invalid) 5751 pv := core.PersistentVolume{ 5752 ObjectMeta: metav1.ObjectMeta{ 5753 Name: "foo", 5754 Namespace: "", 5755 }, 5756 Spec: core.PersistentVolumeSpec{ 5757 Capacity: core.ResourceList{ 5758 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 5759 }, 5760 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 5761 PersistentVolumeSource: core.PersistentVolumeSource{ 5762 HostPath: &core.HostPathVolumeSource{ 5763 Path: "/foo", 5764 Type: newHostPathType(string(core.HostPathDirectory)), 5765 }, 5766 }, 5767 StorageClassName: "test-storage-class", 5768 }, 5769 } 5770 return &pv 5771 } 5772 5773 func TestAlphaLocalStorageCapacityIsolation(t *testing.T) { 5774 5775 testCases := []core.VolumeSource{ 5776 {EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}}, 5777 } 5778 5779 for _, tc := range testCases { 5780 if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil, PodValidationOptions{}); len(errs) != 0 { 5781 t.Errorf("expected success: %v", errs) 5782 } 5783 } 5784 5785 containerLimitCase := core.ResourceRequirements{ 5786 Limits: core.ResourceList{ 5787 core.ResourceEphemeralStorage: *resource.NewMilliQuantity( 5788 int64(40000), 5789 resource.BinarySI), 5790 }, 5791 } 5792 if errs := ValidateResourceRequirements(&containerLimitCase, nil, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 { 5793 t.Errorf("expected success: %v", errs) 5794 } 5795 } 5796 5797 func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) { 5798 spec := core.ResourceQuotaSpec{ 5799 Hard: core.ResourceList{ 5800 core.ResourceCPU: resource.MustParse("100"), 5801 core.ResourceMemory: resource.MustParse("10000"), 5802 core.ResourceRequestsCPU: resource.MustParse("100"), 5803 core.ResourceRequestsMemory: resource.MustParse("10000"), 5804 core.ResourceLimitsCPU: resource.MustParse("100"), 5805 core.ResourceLimitsMemory: resource.MustParse("10000"), 5806 core.ResourcePods: resource.MustParse("10"), 5807 core.ResourceServices: resource.MustParse("0"), 5808 core.ResourceReplicationControllers: resource.MustParse("10"), 5809 core.ResourceQuotas: resource.MustParse("10"), 5810 core.ResourceConfigMaps: resource.MustParse("10"), 5811 core.ResourceSecrets: resource.MustParse("10"), 5812 core.ResourceEphemeralStorage: resource.MustParse("10000"), 5813 core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"), 5814 core.ResourceLimitsEphemeralStorage: resource.MustParse("10000"), 5815 }, 5816 } 5817 resourceQuota := &core.ResourceQuota{ 5818 ObjectMeta: metav1.ObjectMeta{ 5819 Name: "abc", 5820 Namespace: "foo", 5821 }, 5822 Spec: spec, 5823 } 5824 5825 if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 { 5826 t.Errorf("expected success: %v", errs) 5827 } 5828 } 5829 5830 func TestValidatePorts(t *testing.T) { 5831 successCase := []core.ContainerPort{ 5832 {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, 5833 {Name: "easy", ContainerPort: 82, Protocol: "TCP"}, 5834 {Name: "as", ContainerPort: 83, Protocol: "UDP"}, 5835 {Name: "do-re-me", ContainerPort: 84, Protocol: "SCTP"}, 5836 {ContainerPort: 85, Protocol: "TCP"}, 5837 } 5838 if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 { 5839 t.Errorf("expected success: %v", errs) 5840 } 5841 5842 nonCanonicalCase := []core.ContainerPort{ 5843 {ContainerPort: 80, Protocol: "TCP"}, 5844 } 5845 if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 { 5846 t.Errorf("expected success: %v", errs) 5847 } 5848 5849 errorCases := map[string]struct { 5850 P []core.ContainerPort 5851 T field.ErrorType 5852 F string 5853 D string 5854 }{ 5855 "name > 15 characters": { 5856 []core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}}, 5857 field.ErrorTypeInvalid, 5858 "name", "15", 5859 }, 5860 "name contains invalid characters": { 5861 []core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}}, 5862 field.ErrorTypeInvalid, 5863 "name", "alpha-numeric", 5864 }, 5865 "name is a number": { 5866 []core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}}, 5867 field.ErrorTypeInvalid, 5868 "name", "at least one letter", 5869 }, 5870 "name not unique": { 5871 []core.ContainerPort{ 5872 {Name: "abc", ContainerPort: 80, Protocol: "TCP"}, 5873 {Name: "abc", ContainerPort: 81, Protocol: "TCP"}, 5874 }, 5875 field.ErrorTypeDuplicate, 5876 "[1].name", "", 5877 }, 5878 "zero container port": { 5879 []core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}}, 5880 field.ErrorTypeRequired, 5881 "containerPort", "", 5882 }, 5883 "invalid container port": { 5884 []core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}}, 5885 field.ErrorTypeInvalid, 5886 "containerPort", "between", 5887 }, 5888 "invalid host port": { 5889 []core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, 5890 field.ErrorTypeInvalid, 5891 "hostPort", "between", 5892 }, 5893 "invalid protocol case": { 5894 []core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}}, 5895 field.ErrorTypeNotSupported, 5896 "protocol", `supported values: "SCTP", "TCP", "UDP"`, 5897 }, 5898 "invalid protocol": { 5899 []core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}}, 5900 field.ErrorTypeNotSupported, 5901 "protocol", `supported values: "SCTP", "TCP", "UDP"`, 5902 }, 5903 "protocol required": { 5904 []core.ContainerPort{{Name: "abc", ContainerPort: 80}}, 5905 field.ErrorTypeRequired, 5906 "protocol", "", 5907 }, 5908 } 5909 for k, v := range errorCases { 5910 errs := validateContainerPorts(v.P, field.NewPath("field")) 5911 if len(errs) == 0 { 5912 t.Errorf("expected failure for %s", k) 5913 } 5914 for i := range errs { 5915 if errs[i].Type != v.T { 5916 t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type) 5917 } 5918 if !strings.Contains(errs[i].Field, v.F) { 5919 t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field) 5920 } 5921 if !strings.Contains(errs[i].Detail, v.D) { 5922 t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail) 5923 } 5924 } 5925 } 5926 } 5927 5928 func TestLocalStorageEnvWithFeatureGate(t *testing.T) { 5929 testCases := []core.EnvVar{{ 5930 Name: "ephemeral-storage-limits", 5931 ValueFrom: &core.EnvVarSource{ 5932 ResourceFieldRef: &core.ResourceFieldSelector{ 5933 ContainerName: "test-container", 5934 Resource: "limits.ephemeral-storage", 5935 }, 5936 }, 5937 }, { 5938 Name: "ephemeral-storage-requests", 5939 ValueFrom: &core.EnvVarSource{ 5940 ResourceFieldRef: &core.ResourceFieldSelector{ 5941 ContainerName: "test-container", 5942 Resource: "requests.ephemeral-storage", 5943 }, 5944 }, 5945 }, 5946 } 5947 for _, testCase := range testCases { 5948 if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { 5949 t.Errorf("expected success, got: %v", errs) 5950 } 5951 } 5952 } 5953 5954 func TestHugePagesEnv(t *testing.T) { 5955 testCases := []core.EnvVar{{ 5956 Name: "hugepages-limits", 5957 ValueFrom: &core.EnvVarSource{ 5958 ResourceFieldRef: &core.ResourceFieldSelector{ 5959 ContainerName: "test-container", 5960 Resource: "limits.hugepages-2Mi", 5961 }, 5962 }, 5963 }, { 5964 Name: "hugepages-requests", 5965 ValueFrom: &core.EnvVarSource{ 5966 ResourceFieldRef: &core.ResourceFieldSelector{ 5967 ContainerName: "test-container", 5968 Resource: "requests.hugepages-2Mi", 5969 }, 5970 }, 5971 }, 5972 } 5973 // enable gate 5974 for _, testCase := range testCases { 5975 t.Run(testCase.Name, func(t *testing.T) { 5976 opts := PodValidationOptions{} 5977 if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), opts); len(errs) != 0 { 5978 t.Errorf("expected success, got: %v", errs) 5979 } 5980 }) 5981 } 5982 } 5983 5984 func TestValidateEnv(t *testing.T) { 5985 successCase := []core.EnvVar{ 5986 {Name: "abc", Value: "value"}, 5987 {Name: "ABC", Value: "value"}, 5988 {Name: "AbC_123", Value: "value"}, 5989 {Name: "abc", Value: ""}, 5990 {Name: "a.b.c", Value: "value"}, 5991 {Name: "a-b-c", Value: "value"}, { 5992 Name: "abc", 5993 ValueFrom: &core.EnvVarSource{ 5994 FieldRef: &core.ObjectFieldSelector{ 5995 APIVersion: "v1", 5996 FieldPath: "metadata.annotations['key']", 5997 }, 5998 }, 5999 }, { 6000 Name: "abc", 6001 ValueFrom: &core.EnvVarSource{ 6002 FieldRef: &core.ObjectFieldSelector{ 6003 APIVersion: "v1", 6004 FieldPath: "metadata.labels['key']", 6005 }, 6006 }, 6007 }, { 6008 Name: "abc", 6009 ValueFrom: &core.EnvVarSource{ 6010 FieldRef: &core.ObjectFieldSelector{ 6011 APIVersion: "v1", 6012 FieldPath: "metadata.name", 6013 }, 6014 }, 6015 }, { 6016 Name: "abc", 6017 ValueFrom: &core.EnvVarSource{ 6018 FieldRef: &core.ObjectFieldSelector{ 6019 APIVersion: "v1", 6020 FieldPath: "metadata.namespace", 6021 }, 6022 }, 6023 }, { 6024 Name: "abc", 6025 ValueFrom: &core.EnvVarSource{ 6026 FieldRef: &core.ObjectFieldSelector{ 6027 APIVersion: "v1", 6028 FieldPath: "metadata.uid", 6029 }, 6030 }, 6031 }, { 6032 Name: "abc", 6033 ValueFrom: &core.EnvVarSource{ 6034 FieldRef: &core.ObjectFieldSelector{ 6035 APIVersion: "v1", 6036 FieldPath: "spec.nodeName", 6037 }, 6038 }, 6039 }, { 6040 Name: "abc", 6041 ValueFrom: &core.EnvVarSource{ 6042 FieldRef: &core.ObjectFieldSelector{ 6043 APIVersion: "v1", 6044 FieldPath: "spec.serviceAccountName", 6045 }, 6046 }, 6047 }, { 6048 Name: "abc", 6049 ValueFrom: &core.EnvVarSource{ 6050 FieldRef: &core.ObjectFieldSelector{ 6051 APIVersion: "v1", 6052 FieldPath: "status.hostIP", 6053 }, 6054 }, 6055 }, { 6056 Name: "abc", 6057 ValueFrom: &core.EnvVarSource{ 6058 FieldRef: &core.ObjectFieldSelector{ 6059 APIVersion: "v1", 6060 FieldPath: "status.podIP", 6061 }, 6062 }, 6063 }, { 6064 Name: "abc", 6065 ValueFrom: &core.EnvVarSource{ 6066 FieldRef: &core.ObjectFieldSelector{ 6067 APIVersion: "v1", 6068 FieldPath: "status.podIPs", 6069 }, 6070 }, 6071 }, { 6072 Name: "secret_value", 6073 ValueFrom: &core.EnvVarSource{ 6074 SecretKeyRef: &core.SecretKeySelector{ 6075 LocalObjectReference: core.LocalObjectReference{ 6076 Name: "some-secret", 6077 }, 6078 Key: "secret-key", 6079 }, 6080 }, 6081 }, { 6082 Name: "ENV_VAR_1", 6083 ValueFrom: &core.EnvVarSource{ 6084 ConfigMapKeyRef: &core.ConfigMapKeySelector{ 6085 LocalObjectReference: core.LocalObjectReference{ 6086 Name: "some-config-map", 6087 }, 6088 Key: "some-key", 6089 }, 6090 }, 6091 }, 6092 } 6093 if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { 6094 t.Errorf("expected success, got: %v", errs) 6095 } 6096 6097 errorCases := []struct { 6098 name string 6099 envs []core.EnvVar 6100 expectedError string 6101 }{{ 6102 name: "zero-length name", 6103 envs: []core.EnvVar{{Name: ""}}, 6104 expectedError: "[0].name: Required value", 6105 }, { 6106 name: "illegal character", 6107 envs: []core.EnvVar{{Name: "a!b"}}, 6108 expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg, 6109 }, { 6110 name: "dot only", 6111 envs: []core.EnvVar{{Name: "."}}, 6112 expectedError: `[0].name: Invalid value: ".": must not be`, 6113 }, { 6114 name: "double dots only", 6115 envs: []core.EnvVar{{Name: ".."}}, 6116 expectedError: `[0].name: Invalid value: "..": must not be`, 6117 }, { 6118 name: "leading double dots", 6119 envs: []core.EnvVar{{Name: "..abc"}}, 6120 expectedError: `[0].name: Invalid value: "..abc": must not start with`, 6121 }, { 6122 name: "value and valueFrom specified", 6123 envs: []core.EnvVar{{ 6124 Name: "abc", 6125 Value: "foo", 6126 ValueFrom: &core.EnvVarSource{ 6127 FieldRef: &core.ObjectFieldSelector{ 6128 APIVersion: "v1", 6129 FieldPath: "metadata.name", 6130 }, 6131 }, 6132 }}, 6133 expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty", 6134 }, { 6135 name: "valueFrom without a source", 6136 envs: []core.EnvVar{{ 6137 Name: "abc", 6138 ValueFrom: &core.EnvVarSource{}, 6139 }}, 6140 expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`", 6141 }, { 6142 name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", 6143 envs: []core.EnvVar{{ 6144 Name: "abc", 6145 ValueFrom: &core.EnvVarSource{ 6146 FieldRef: &core.ObjectFieldSelector{ 6147 APIVersion: "v1", 6148 FieldPath: "metadata.name", 6149 }, 6150 SecretKeyRef: &core.SecretKeySelector{ 6151 LocalObjectReference: core.LocalObjectReference{ 6152 Name: "a-secret", 6153 }, 6154 Key: "a-key", 6155 }, 6156 }, 6157 }}, 6158 expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time", 6159 }, { 6160 name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set", 6161 envs: []core.EnvVar{{ 6162 Name: "some_var_name", 6163 ValueFrom: &core.EnvVarSource{ 6164 FieldRef: &core.ObjectFieldSelector{ 6165 APIVersion: "v1", 6166 FieldPath: "metadata.name", 6167 }, 6168 ConfigMapKeyRef: &core.ConfigMapKeySelector{ 6169 LocalObjectReference: core.LocalObjectReference{ 6170 Name: "some-config-map", 6171 }, 6172 Key: "some-key", 6173 }, 6174 }, 6175 }}, 6176 expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, 6177 }, { 6178 name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified", 6179 envs: []core.EnvVar{{ 6180 Name: "abc", 6181 ValueFrom: &core.EnvVarSource{ 6182 FieldRef: &core.ObjectFieldSelector{ 6183 APIVersion: "v1", 6184 FieldPath: "metadata.name", 6185 }, 6186 SecretKeyRef: &core.SecretKeySelector{ 6187 LocalObjectReference: core.LocalObjectReference{ 6188 Name: "a-secret", 6189 }, 6190 Key: "a-key", 6191 }, 6192 ConfigMapKeyRef: &core.ConfigMapKeySelector{ 6193 LocalObjectReference: core.LocalObjectReference{ 6194 Name: "some-config-map", 6195 }, 6196 Key: "some-key", 6197 }, 6198 }, 6199 }}, 6200 expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`, 6201 }, { 6202 name: "valueFrom.secretKeyRef.name invalid", 6203 envs: []core.EnvVar{{ 6204 Name: "abc", 6205 ValueFrom: &core.EnvVarSource{ 6206 SecretKeyRef: &core.SecretKeySelector{ 6207 LocalObjectReference: core.LocalObjectReference{ 6208 Name: "$%^&*#", 6209 }, 6210 Key: "a-key", 6211 }, 6212 }, 6213 }}, 6214 }, { 6215 name: "valueFrom.configMapKeyRef.name invalid", 6216 envs: []core.EnvVar{{ 6217 Name: "abc", 6218 ValueFrom: &core.EnvVarSource{ 6219 ConfigMapKeyRef: &core.ConfigMapKeySelector{ 6220 LocalObjectReference: core.LocalObjectReference{ 6221 Name: "$%^&*#", 6222 }, 6223 Key: "some-key", 6224 }, 6225 }, 6226 }}, 6227 }, { 6228 name: "missing FieldPath on ObjectFieldSelector", 6229 envs: []core.EnvVar{{ 6230 Name: "abc", 6231 ValueFrom: &core.EnvVarSource{ 6232 FieldRef: &core.ObjectFieldSelector{ 6233 APIVersion: "v1", 6234 }, 6235 }, 6236 }}, 6237 expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`, 6238 }, { 6239 name: "missing APIVersion on ObjectFieldSelector", 6240 envs: []core.EnvVar{{ 6241 Name: "abc", 6242 ValueFrom: &core.EnvVarSource{ 6243 FieldRef: &core.ObjectFieldSelector{ 6244 FieldPath: "metadata.name", 6245 }, 6246 }, 6247 }}, 6248 expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`, 6249 }, { 6250 name: "invalid fieldPath", 6251 envs: []core.EnvVar{{ 6252 Name: "abc", 6253 ValueFrom: &core.EnvVarSource{ 6254 FieldRef: &core.ObjectFieldSelector{ 6255 FieldPath: "metadata.whoops", 6256 APIVersion: "v1", 6257 }, 6258 }, 6259 }}, 6260 expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`, 6261 }, { 6262 name: "metadata.name with subscript", 6263 envs: []core.EnvVar{{ 6264 Name: "labels", 6265 ValueFrom: &core.EnvVarSource{ 6266 FieldRef: &core.ObjectFieldSelector{ 6267 FieldPath: "metadata.name['key']", 6268 APIVersion: "v1", 6269 }, 6270 }, 6271 }}, 6272 expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`, 6273 }, { 6274 name: "metadata.labels without subscript", 6275 envs: []core.EnvVar{{ 6276 Name: "labels", 6277 ValueFrom: &core.EnvVarSource{ 6278 FieldRef: &core.ObjectFieldSelector{ 6279 FieldPath: "metadata.labels", 6280 APIVersion: "v1", 6281 }, 6282 }, 6283 }}, 6284 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"`, 6285 }, { 6286 name: "metadata.annotations without subscript", 6287 envs: []core.EnvVar{{ 6288 Name: "abc", 6289 ValueFrom: &core.EnvVarSource{ 6290 FieldRef: &core.ObjectFieldSelector{ 6291 FieldPath: "metadata.annotations", 6292 APIVersion: "v1", 6293 }, 6294 }, 6295 }}, 6296 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"`, 6297 }, { 6298 name: "metadata.annotations with invalid key", 6299 envs: []core.EnvVar{{ 6300 Name: "abc", 6301 ValueFrom: &core.EnvVarSource{ 6302 FieldRef: &core.ObjectFieldSelector{ 6303 FieldPath: "metadata.annotations['invalid~key']", 6304 APIVersion: "v1", 6305 }, 6306 }, 6307 }}, 6308 expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`, 6309 }, { 6310 name: "metadata.labels with invalid key", 6311 envs: []core.EnvVar{{ 6312 Name: "abc", 6313 ValueFrom: &core.EnvVarSource{ 6314 FieldRef: &core.ObjectFieldSelector{ 6315 FieldPath: "metadata.labels['Www.k8s.io/test']", 6316 APIVersion: "v1", 6317 }, 6318 }, 6319 }}, 6320 expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`, 6321 }, { 6322 name: "unsupported fieldPath", 6323 envs: []core.EnvVar{{ 6324 Name: "abc", 6325 ValueFrom: &core.EnvVarSource{ 6326 FieldRef: &core.ObjectFieldSelector{ 6327 FieldPath: "status.phase", 6328 APIVersion: "v1", 6329 }, 6330 }, 6331 }}, 6332 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"`, 6333 }, 6334 } 6335 for _, tc := range errorCases { 6336 if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 { 6337 t.Errorf("expected failure for %s", tc.name) 6338 } else { 6339 for i := range errs { 6340 str := errs[i].Error() 6341 if str != "" && !strings.Contains(str, tc.expectedError) { 6342 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str) 6343 } 6344 } 6345 } 6346 } 6347 } 6348 6349 func TestValidateEnvFrom(t *testing.T) { 6350 successCase := []core.EnvFromSource{{ 6351 ConfigMapRef: &core.ConfigMapEnvSource{ 6352 LocalObjectReference: core.LocalObjectReference{Name: "abc"}, 6353 }, 6354 }, { 6355 Prefix: "pre_", 6356 ConfigMapRef: &core.ConfigMapEnvSource{ 6357 LocalObjectReference: core.LocalObjectReference{Name: "abc"}, 6358 }, 6359 }, { 6360 Prefix: "a.b", 6361 ConfigMapRef: &core.ConfigMapEnvSource{ 6362 LocalObjectReference: core.LocalObjectReference{Name: "abc"}, 6363 }, 6364 }, { 6365 SecretRef: &core.SecretEnvSource{ 6366 LocalObjectReference: core.LocalObjectReference{Name: "abc"}, 6367 }, 6368 }, { 6369 Prefix: "pre_", 6370 SecretRef: &core.SecretEnvSource{ 6371 LocalObjectReference: core.LocalObjectReference{Name: "abc"}, 6372 }, 6373 }, { 6374 Prefix: "a.b", 6375 SecretRef: &core.SecretEnvSource{ 6376 LocalObjectReference: core.LocalObjectReference{Name: "abc"}, 6377 }, 6378 }, 6379 } 6380 if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 { 6381 t.Errorf("expected success: %v", errs) 6382 } 6383 6384 errorCases := []struct { 6385 name string 6386 envs []core.EnvFromSource 6387 expectedError string 6388 }{{ 6389 name: "zero-length name", 6390 envs: []core.EnvFromSource{{ 6391 ConfigMapRef: &core.ConfigMapEnvSource{ 6392 LocalObjectReference: core.LocalObjectReference{Name: ""}}, 6393 }}, 6394 expectedError: "field[0].configMapRef.name: Required value", 6395 }, { 6396 name: "invalid name", 6397 envs: []core.EnvFromSource{{ 6398 ConfigMapRef: &core.ConfigMapEnvSource{ 6399 LocalObjectReference: core.LocalObjectReference{Name: "$"}}, 6400 }}, 6401 expectedError: "field[0].configMapRef.name: Invalid value", 6402 }, { 6403 name: "invalid prefix", 6404 envs: []core.EnvFromSource{{ 6405 Prefix: "a!b", 6406 ConfigMapRef: &core.ConfigMapEnvSource{ 6407 LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, 6408 }}, 6409 expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, 6410 }, { 6411 name: "zero-length name", 6412 envs: []core.EnvFromSource{{ 6413 SecretRef: &core.SecretEnvSource{ 6414 LocalObjectReference: core.LocalObjectReference{Name: ""}}, 6415 }}, 6416 expectedError: "field[0].secretRef.name: Required value", 6417 }, { 6418 name: "invalid name", 6419 envs: []core.EnvFromSource{{ 6420 SecretRef: &core.SecretEnvSource{ 6421 LocalObjectReference: core.LocalObjectReference{Name: "&"}}, 6422 }}, 6423 expectedError: "field[0].secretRef.name: Invalid value", 6424 }, { 6425 name: "invalid prefix", 6426 envs: []core.EnvFromSource{{ 6427 Prefix: "a!b", 6428 SecretRef: &core.SecretEnvSource{ 6429 LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, 6430 }}, 6431 expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg, 6432 }, { 6433 name: "no refs", 6434 envs: []core.EnvFromSource{ 6435 {}, 6436 }, 6437 expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`", 6438 }, { 6439 name: "multiple refs", 6440 envs: []core.EnvFromSource{{ 6441 SecretRef: &core.SecretEnvSource{ 6442 LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, 6443 ConfigMapRef: &core.ConfigMapEnvSource{ 6444 LocalObjectReference: core.LocalObjectReference{Name: "abc"}}, 6445 }}, 6446 expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time", 6447 }, { 6448 name: "invalid secret ref name", 6449 envs: []core.EnvFromSource{{ 6450 SecretRef: &core.SecretEnvSource{ 6451 LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, 6452 }}, 6453 expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, 6454 }, { 6455 name: "invalid config ref name", 6456 envs: []core.EnvFromSource{{ 6457 ConfigMapRef: &core.ConfigMapEnvSource{ 6458 LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}}, 6459 }}, 6460 expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, 6461 }, 6462 } 6463 for _, tc := range errorCases { 6464 if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 { 6465 t.Errorf("expected failure for %s", tc.name) 6466 } else { 6467 for i := range errs { 6468 str := errs[i].Error() 6469 if str != "" && !strings.Contains(str, tc.expectedError) { 6470 t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str) 6471 } 6472 } 6473 } 6474 } 6475 } 6476 6477 func TestValidateVolumeMounts(t *testing.T) { 6478 volumes := []core.Volume{ 6479 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}}, 6480 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, 6481 {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, 6482 {Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{ 6483 Spec: core.PersistentVolumeClaimSpec{ 6484 AccessModes: []core.PersistentVolumeAccessMode{ 6485 core.ReadWriteOnce, 6486 }, 6487 Resources: core.VolumeResourceRequirements{ 6488 Requests: core.ResourceList{ 6489 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 6490 }, 6491 }, 6492 }, 6493 }}}}, 6494 } 6495 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{}) 6496 if len(v1err) > 0 { 6497 t.Errorf("Invalid test volume - expected success %v", v1err) 6498 return 6499 } 6500 container := core.Container{ 6501 SecurityContext: nil, 6502 } 6503 propagation := core.MountPropagationBidirectional 6504 6505 successCase := []core.VolumeMount{ 6506 {Name: "abc", MountPath: "/foo"}, 6507 {Name: "123", MountPath: "/bar"}, 6508 {Name: "abc-123", MountPath: "/baz"}, 6509 {Name: "abc-123", MountPath: "/baa", SubPath: ""}, 6510 {Name: "abc-123", MountPath: "/bab", SubPath: "baz"}, 6511 {Name: "abc-123", MountPath: "d:", SubPath: ""}, 6512 {Name: "abc-123", MountPath: "F:", SubPath: ""}, 6513 {Name: "abc-123", MountPath: "G:\\mount", SubPath: ""}, 6514 {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"}, 6515 {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"}, 6516 {Name: "ephemeral", MountPath: "/foobar"}, 6517 } 6518 goodVolumeDevices := []core.VolumeDevice{ 6519 {Name: "xyz", DevicePath: "/foofoo"}, 6520 {Name: "uvw", DevicePath: "/foofoo/share/test"}, 6521 } 6522 if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 { 6523 t.Errorf("expected success: %v", errs) 6524 } 6525 6526 errorCases := map[string][]core.VolumeMount{ 6527 "empty name": {{Name: "", MountPath: "/foo"}}, 6528 "name not found": {{Name: "", MountPath: "/foo"}}, 6529 "empty mountpath": {{Name: "abc", MountPath: ""}}, 6530 "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}}, 6531 "absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}}, 6532 "subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}}, 6533 "subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}}, 6534 "subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}}, 6535 "disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}}, 6536 "name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}}, 6537 "mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}}, 6538 "both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}}, 6539 } 6540 badVolumeDevice := []core.VolumeDevice{ 6541 {Name: "xyz", DevicePath: "/mnt/exists"}, 6542 } 6543 6544 for k, v := range errorCases { 6545 if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 { 6546 t.Errorf("expected failure for %s", k) 6547 } 6548 } 6549 } 6550 6551 func TestValidateSubpathMutuallyExclusive(t *testing.T) { 6552 volumes := []core.Volume{ 6553 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}}, 6554 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, 6555 {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, 6556 } 6557 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{}) 6558 if len(v1err) > 0 { 6559 t.Errorf("Invalid test volume - expected success %v", v1err) 6560 return 6561 } 6562 6563 container := core.Container{ 6564 SecurityContext: nil, 6565 } 6566 6567 goodVolumeDevices := []core.VolumeDevice{ 6568 {Name: "xyz", DevicePath: "/foofoo"}, 6569 {Name: "uvw", DevicePath: "/foofoo/share/test"}, 6570 } 6571 6572 cases := map[string]struct { 6573 mounts []core.VolumeMount 6574 expectError bool 6575 }{ 6576 "subpath and subpathexpr not specified": { 6577 []core.VolumeMount{{ 6578 Name: "abc-123", 6579 MountPath: "/bab", 6580 }}, 6581 false, 6582 }, 6583 "subpath expr specified": { 6584 []core.VolumeMount{{ 6585 Name: "abc-123", 6586 MountPath: "/bab", 6587 SubPathExpr: "$(POD_NAME)", 6588 }}, 6589 false, 6590 }, 6591 "subpath specified": { 6592 []core.VolumeMount{{ 6593 Name: "abc-123", 6594 MountPath: "/bab", 6595 SubPath: "baz", 6596 }}, 6597 false, 6598 }, 6599 "subpath and subpathexpr specified": { 6600 []core.VolumeMount{{ 6601 Name: "abc-123", 6602 MountPath: "/bab", 6603 SubPath: "baz", 6604 SubPathExpr: "$(POD_NAME)", 6605 }}, 6606 true, 6607 }, 6608 } 6609 6610 for name, test := range cases { 6611 errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")) 6612 6613 if len(errs) != 0 && !test.expectError { 6614 t.Errorf("test %v failed: %+v", name, errs) 6615 } 6616 6617 if len(errs) == 0 && test.expectError { 6618 t.Errorf("test %v failed, expected error", name) 6619 } 6620 } 6621 } 6622 6623 func TestValidateDisabledSubpathExpr(t *testing.T) { 6624 6625 volumes := []core.Volume{ 6626 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}}, 6627 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, 6628 {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, 6629 } 6630 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{}) 6631 if len(v1err) > 0 { 6632 t.Errorf("Invalid test volume - expected success %v", v1err) 6633 return 6634 } 6635 6636 container := core.Container{ 6637 SecurityContext: nil, 6638 } 6639 6640 goodVolumeDevices := []core.VolumeDevice{ 6641 {Name: "xyz", DevicePath: "/foofoo"}, 6642 {Name: "uvw", DevicePath: "/foofoo/share/test"}, 6643 } 6644 6645 cases := map[string]struct { 6646 mounts []core.VolumeMount 6647 expectError bool 6648 }{ 6649 "subpath expr not specified": { 6650 []core.VolumeMount{{ 6651 Name: "abc-123", 6652 MountPath: "/bab", 6653 }}, 6654 false, 6655 }, 6656 "subpath expr specified": { 6657 []core.VolumeMount{{ 6658 Name: "abc-123", 6659 MountPath: "/bab", 6660 SubPathExpr: "$(POD_NAME)", 6661 }}, 6662 false, 6663 }, 6664 } 6665 6666 for name, test := range cases { 6667 errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")) 6668 6669 if len(errs) != 0 && !test.expectError { 6670 t.Errorf("test %v failed: %+v", name, errs) 6671 } 6672 6673 if len(errs) == 0 && test.expectError { 6674 t.Errorf("test %v failed, expected error", name) 6675 } 6676 } 6677 } 6678 6679 func TestValidateMountPropagation(t *testing.T) { 6680 bTrue := true 6681 bFalse := false 6682 privilegedContainer := &core.Container{ 6683 SecurityContext: &core.SecurityContext{ 6684 Privileged: &bTrue, 6685 }, 6686 } 6687 nonPrivilegedContainer := &core.Container{ 6688 SecurityContext: &core.SecurityContext{ 6689 Privileged: &bFalse, 6690 }, 6691 } 6692 defaultContainer := &core.Container{} 6693 6694 propagationBidirectional := core.MountPropagationBidirectional 6695 propagationHostToContainer := core.MountPropagationHostToContainer 6696 propagationNone := core.MountPropagationNone 6697 propagationInvalid := core.MountPropagationMode("invalid") 6698 6699 tests := []struct { 6700 mount core.VolumeMount 6701 container *core.Container 6702 expectError bool 6703 }{{ 6704 // implicitly non-privileged container + no propagation 6705 core.VolumeMount{Name: "foo", MountPath: "/foo"}, 6706 defaultContainer, 6707 false, 6708 }, { 6709 // implicitly non-privileged container + HostToContainer 6710 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, 6711 defaultContainer, 6712 false, 6713 }, { 6714 // non-privileged container + None 6715 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone}, 6716 defaultContainer, 6717 false, 6718 }, { 6719 // error: implicitly non-privileged container + Bidirectional 6720 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, 6721 defaultContainer, 6722 true, 6723 }, { 6724 // explicitly non-privileged container + no propagation 6725 core.VolumeMount{Name: "foo", MountPath: "/foo"}, 6726 nonPrivilegedContainer, 6727 false, 6728 }, { 6729 // explicitly non-privileged container + HostToContainer 6730 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, 6731 nonPrivilegedContainer, 6732 false, 6733 }, { 6734 // explicitly non-privileged container + HostToContainer 6735 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, 6736 nonPrivilegedContainer, 6737 true, 6738 }, { 6739 // privileged container + no propagation 6740 core.VolumeMount{Name: "foo", MountPath: "/foo"}, 6741 privilegedContainer, 6742 false, 6743 }, { 6744 // privileged container + HostToContainer 6745 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer}, 6746 privilegedContainer, 6747 false, 6748 }, { 6749 // privileged container + Bidirectional 6750 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, 6751 privilegedContainer, 6752 false, 6753 }, { 6754 // error: privileged container + invalid mount propagation 6755 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid}, 6756 privilegedContainer, 6757 true, 6758 }, { 6759 // no container + Bidirectional 6760 core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional}, 6761 nil, 6762 false, 6763 }, 6764 } 6765 6766 volumes := []core.Volume{ 6767 {Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, 6768 } 6769 vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{}) 6770 if len(v2err) > 0 { 6771 t.Errorf("Invalid test volume - expected success %v", v2err) 6772 return 6773 } 6774 for i, test := range tests { 6775 errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field")) 6776 if test.expectError && len(errs) == 0 { 6777 t.Errorf("test %d expected error, got none", i) 6778 } 6779 if !test.expectError && len(errs) != 0 { 6780 t.Errorf("test %d expected success, got error: %v", i, errs) 6781 } 6782 } 6783 } 6784 6785 func TestAlphaValidateVolumeDevices(t *testing.T) { 6786 volumes := []core.Volume{ 6787 {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}}, 6788 {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, 6789 {Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, 6790 {Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{ 6791 Spec: core.PersistentVolumeClaimSpec{ 6792 AccessModes: []core.PersistentVolumeAccessMode{ 6793 core.ReadWriteOnce, 6794 }, 6795 Resources: core.VolumeResourceRequirements{ 6796 Requests: core.ResourceList{ 6797 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 6798 }, 6799 }, 6800 }, 6801 }}}}, 6802 } 6803 6804 vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{}) 6805 if len(v1err) > 0 { 6806 t.Errorf("Invalid test volumes - expected success %v", v1err) 6807 return 6808 } 6809 6810 successCase := []core.VolumeDevice{ 6811 {Name: "abc", DevicePath: "/foo"}, 6812 {Name: "abc-123", DevicePath: "/usr/share/test"}, 6813 {Name: "ephemeral", DevicePath: "/disk"}, 6814 } 6815 goodVolumeMounts := []core.VolumeMount{ 6816 {Name: "xyz", MountPath: "/foofoo"}, 6817 {Name: "ghi", MountPath: "/foo/usr/share/test"}, 6818 } 6819 6820 errorCases := map[string][]core.VolumeDevice{ 6821 "empty name": {{Name: "", DevicePath: "/foo"}}, 6822 "duplicate name": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}}, 6823 "name not found": {{Name: "not-found", DevicePath: "/usr/share/test"}}, 6824 "name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}}, 6825 "empty devicepath": {{Name: "abc", DevicePath: ""}}, 6826 "relative devicepath": {{Name: "abc-123", DevicePath: "baz"}}, 6827 "duplicate devicepath": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}}, 6828 "no backsteps": {{Name: "def", DevicePath: "/baz/../"}}, 6829 "name exists in volumemounts": {{Name: "abc", DevicePath: "/baz/../"}}, 6830 "path exists in volumemounts": {{Name: "xyz", DevicePath: "/this/path/exists"}}, 6831 "both exist in volumemounts": {{Name: "abc", DevicePath: "/this/path/exists"}}, 6832 } 6833 badVolumeMounts := []core.VolumeMount{ 6834 {Name: "abc", MountPath: "/foo"}, 6835 {Name: "abc-123", MountPath: "/this/path/exists"}, 6836 } 6837 6838 // Success Cases: 6839 // Validate normal success cases - only PVC volumeSource or generic ephemeral volume 6840 if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 { 6841 t.Errorf("expected success: %v", errs) 6842 } 6843 6844 // Error Cases: 6845 // Validate normal error cases - only PVC volumeSource 6846 for k, v := range errorCases { 6847 if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 { 6848 t.Errorf("expected failure for %s", k) 6849 } 6850 } 6851 } 6852 6853 func TestValidateProbe(t *testing.T) { 6854 handler := core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}} 6855 // These fields must be positive. 6856 positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"} 6857 successCases := []*core.Probe{nil} 6858 for _, field := range positiveFields { 6859 probe := &core.Probe{ProbeHandler: handler} 6860 reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10) 6861 successCases = append(successCases, probe) 6862 } 6863 6864 for _, p := range successCases { 6865 if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) != 0 { 6866 t.Errorf("expected success: %v", errs) 6867 } 6868 } 6869 6870 errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}} 6871 for _, field := range positiveFields { 6872 probe := &core.Probe{ProbeHandler: handler} 6873 reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10) 6874 errorCases = append(errorCases, probe) 6875 } 6876 for _, p := range errorCases { 6877 if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) == 0 { 6878 t.Errorf("expected failure for %v", p) 6879 } 6880 } 6881 } 6882 6883 func Test_validateProbe(t *testing.T) { 6884 fldPath := field.NewPath("test") 6885 type args struct { 6886 probe *core.Probe 6887 fldPath *field.Path 6888 } 6889 tests := []struct { 6890 name string 6891 args args 6892 want field.ErrorList 6893 }{{ 6894 args: args{ 6895 probe: &core.Probe{}, 6896 fldPath: fldPath, 6897 }, 6898 want: field.ErrorList{field.Required(fldPath, "must specify a handler type")}, 6899 }, { 6900 args: args{ 6901 probe: &core.Probe{ 6902 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6903 }, 6904 fldPath: fldPath, 6905 }, 6906 want: field.ErrorList{}, 6907 }, { 6908 args: args{ 6909 probe: &core.Probe{ 6910 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6911 InitialDelaySeconds: -1, 6912 }, 6913 fldPath: fldPath, 6914 }, 6915 want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")}, 6916 }, { 6917 args: args{ 6918 probe: &core.Probe{ 6919 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6920 TimeoutSeconds: -1, 6921 }, 6922 fldPath: fldPath, 6923 }, 6924 want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")}, 6925 }, { 6926 args: args{ 6927 probe: &core.Probe{ 6928 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6929 PeriodSeconds: -1, 6930 }, 6931 fldPath: fldPath, 6932 }, 6933 want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")}, 6934 }, { 6935 args: args{ 6936 probe: &core.Probe{ 6937 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6938 SuccessThreshold: -1, 6939 }, 6940 fldPath: fldPath, 6941 }, 6942 want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")}, 6943 }, { 6944 args: args{ 6945 probe: &core.Probe{ 6946 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6947 FailureThreshold: -1, 6948 }, 6949 fldPath: fldPath, 6950 }, 6951 want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")}, 6952 }, { 6953 args: args{ 6954 probe: &core.Probe{ 6955 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6956 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 6957 }, 6958 fldPath: fldPath, 6959 }, 6960 want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")}, 6961 }, { 6962 args: args{ 6963 probe: &core.Probe{ 6964 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6965 TerminationGracePeriodSeconds: utilpointer.Int64(0), 6966 }, 6967 fldPath: fldPath, 6968 }, 6969 want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")}, 6970 }, { 6971 args: args{ 6972 probe: &core.Probe{ 6973 ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}, 6974 TerminationGracePeriodSeconds: utilpointer.Int64(1), 6975 }, 6976 fldPath: fldPath, 6977 }, 6978 want: field.ErrorList{}, 6979 }, 6980 } 6981 for _, tt := range tests { 6982 t.Run(tt.name, func(t *testing.T) { 6983 got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath) 6984 if len(got) != len(tt.want) { 6985 t.Errorf("validateProbe() = %v, want %v", got, tt.want) 6986 return 6987 } 6988 for i := range got { 6989 if got[i].Type != tt.want[i].Type || 6990 got[i].Field != tt.want[i].Field { 6991 t.Errorf("validateProbe()[%d] = %v, want %v", i, got[i], tt.want[i]) 6992 } 6993 } 6994 }) 6995 } 6996 } 6997 6998 func TestValidateHandler(t *testing.T) { 6999 successCases := []core.ProbeHandler{ 7000 {Exec: &core.ExecAction{Command: []string{"echo"}}}, 7001 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt32(1), Host: "", Scheme: "HTTP"}}, 7002 {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65535), Host: "host", Scheme: "HTTP"}}, 7003 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}}, 7004 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}}, 7005 {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"}}}}, 7006 } 7007 for _, h := range successCases { 7008 if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) != 0 { 7009 t.Errorf("expected success: %v", errs) 7010 } 7011 } 7012 7013 errorCases := []core.ProbeHandler{ 7014 {}, 7015 {Exec: &core.ExecAction{Command: []string{}}}, 7016 {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt32(0), Host: ""}}, 7017 {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65536), Host: "host"}}, 7018 {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}}, 7019 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}}, 7020 {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}}, 7021 } 7022 for _, h := range errorCases { 7023 if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) == 0 { 7024 t.Errorf("expected failure for %#v", h) 7025 } 7026 } 7027 } 7028 7029 func TestValidatePullPolicy(t *testing.T) { 7030 type T struct { 7031 Container core.Container 7032 ExpectedPolicy core.PullPolicy 7033 } 7034 testCases := map[string]T{ 7035 "NotPresent1": { 7036 core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7037 core.PullIfNotPresent, 7038 }, 7039 "NotPresent2": { 7040 core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7041 core.PullIfNotPresent, 7042 }, 7043 "Always1": { 7044 core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"}, 7045 core.PullAlways, 7046 }, 7047 "Always2": { 7048 core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"}, 7049 core.PullAlways, 7050 }, 7051 "Never1": { 7052 core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"}, 7053 core.PullNever, 7054 }, 7055 "Never2": { 7056 core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"}, 7057 core.PullNever, 7058 }, 7059 } 7060 for k, v := range testCases { 7061 ctr := &v.Container 7062 errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field")) 7063 if len(errs) != 0 { 7064 t.Errorf("case[%s] expected success, got %#v", k, errs) 7065 } 7066 if ctr.ImagePullPolicy != v.ExpectedPolicy { 7067 t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy) 7068 } 7069 } 7070 } 7071 7072 func TestValidateResizePolicy(t *testing.T) { 7073 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)() 7074 tSupportedResizeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory)) 7075 tSupportedResizePolicies := sets.NewString(string(core.NotRequired), string(core.RestartContainer)) 7076 type T struct { 7077 PolicyList []core.ContainerResizePolicy 7078 ExpectError bool 7079 Errors field.ErrorList 7080 PodRestartPolicy core.RestartPolicy 7081 } 7082 7083 testCases := map[string]T{ 7084 "ValidCPUandMemoryPolicies": { 7085 PolicyList: []core.ContainerResizePolicy{ 7086 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7087 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7088 }, 7089 ExpectError: false, 7090 Errors: nil, 7091 PodRestartPolicy: "Always", 7092 }, 7093 "ValidCPUPolicy": { 7094 PolicyList: []core.ContainerResizePolicy{ 7095 {ResourceName: "cpu", RestartPolicy: "RestartContainer"}, 7096 }, 7097 ExpectError: false, 7098 Errors: nil, 7099 PodRestartPolicy: "Always", 7100 }, 7101 "ValidMemoryPolicy": { 7102 PolicyList: []core.ContainerResizePolicy{ 7103 {ResourceName: "memory", RestartPolicy: "NotRequired"}, 7104 }, 7105 ExpectError: false, 7106 Errors: nil, 7107 PodRestartPolicy: "Always", 7108 }, 7109 "NoPolicy": { 7110 PolicyList: []core.ContainerResizePolicy{}, 7111 ExpectError: false, 7112 Errors: nil, 7113 PodRestartPolicy: "Always", 7114 }, 7115 "ValidCPUandInvalidMemoryPolicy": { 7116 PolicyList: []core.ContainerResizePolicy{ 7117 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7118 {ResourceName: "memory", RestartPolicy: "Restarrrt"}, 7119 }, 7120 ExpectError: true, 7121 Errors: field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("Restarrrt"), tSupportedResizePolicies.List())}, 7122 PodRestartPolicy: "Always", 7123 }, 7124 "ValidMemoryandInvalidCPUPolicy": { 7125 PolicyList: []core.ContainerResizePolicy{ 7126 {ResourceName: "cpu", RestartPolicy: "RestartNotRequirrred"}, 7127 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7128 }, 7129 ExpectError: true, 7130 Errors: field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartNotRequirrred"), tSupportedResizePolicies.List())}, 7131 PodRestartPolicy: "Always", 7132 }, 7133 "InvalidResourceNameValidPolicy": { 7134 PolicyList: []core.ContainerResizePolicy{ 7135 {ResourceName: "cpuuu", RestartPolicy: "NotRequired"}, 7136 }, 7137 ExpectError: true, 7138 Errors: field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceName("cpuuu"), tSupportedResizeResources.List())}, 7139 PodRestartPolicy: "Always", 7140 }, 7141 "ValidResourceNameMissingPolicy": { 7142 PolicyList: []core.ContainerResizePolicy{ 7143 {ResourceName: "memory", RestartPolicy: ""}, 7144 }, 7145 ExpectError: true, 7146 Errors: field.ErrorList{field.Required(field.NewPath("field"), "")}, 7147 PodRestartPolicy: "Always", 7148 }, 7149 "RepeatedPolicies": { 7150 PolicyList: []core.ContainerResizePolicy{ 7151 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7152 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7153 {ResourceName: "cpu", RestartPolicy: "RestartContainer"}, 7154 }, 7155 ExpectError: true, 7156 Errors: field.ErrorList{field.Duplicate(field.NewPath("field").Index(2), core.ResourceCPU)}, 7157 PodRestartPolicy: "Always", 7158 }, 7159 "InvalidCPUPolicyWithPodRestartPolicy": { 7160 PolicyList: []core.ContainerResizePolicy{ 7161 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7162 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7163 }, 7164 ExpectError: true, 7165 Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")}, 7166 PodRestartPolicy: "Never", 7167 }, 7168 "InvalidMemoryPolicyWithPodRestartPolicy": { 7169 PolicyList: []core.ContainerResizePolicy{ 7170 {ResourceName: "cpu", RestartPolicy: "RestartContainer"}, 7171 {ResourceName: "memory", RestartPolicy: "NotRequired"}, 7172 }, 7173 ExpectError: true, 7174 Errors: field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")}, 7175 PodRestartPolicy: "Never", 7176 }, 7177 "InvalidMemoryCPUPolicyWithPodRestartPolicy": { 7178 PolicyList: []core.ContainerResizePolicy{ 7179 {ResourceName: "cpu", RestartPolicy: "RestartContainer"}, 7180 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7181 }, 7182 ExpectError: true, 7183 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'")}, 7184 PodRestartPolicy: "Never", 7185 }, 7186 "ValidMemoryCPUPolicyWithPodRestartPolicy": { 7187 PolicyList: []core.ContainerResizePolicy{ 7188 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7189 {ResourceName: "memory", RestartPolicy: "NotRequired"}, 7190 }, 7191 ExpectError: false, 7192 Errors: nil, 7193 PodRestartPolicy: "Never", 7194 }, 7195 } 7196 for k, v := range testCases { 7197 errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy) 7198 if !v.ExpectError && len(errs) > 0 { 7199 t.Errorf("Testcase %s - expected success, got error: %+v", k, errs) 7200 } 7201 if v.ExpectError { 7202 if len(errs) == 0 { 7203 t.Errorf("Testcase %s - expected error, got success", k) 7204 } 7205 delta := cmp.Diff(errs, v.Errors) 7206 if delta != "" { 7207 t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta) 7208 } 7209 } 7210 } 7211 } 7212 7213 func getResourceLimits(cpu, memory string) core.ResourceList { 7214 res := core.ResourceList{} 7215 res[core.ResourceCPU] = resource.MustParse(cpu) 7216 res[core.ResourceMemory] = resource.MustParse(memory) 7217 return res 7218 } 7219 7220 func getResources(cpu, memory, storage string) core.ResourceList { 7221 res := core.ResourceList{} 7222 if cpu != "" { 7223 res[core.ResourceCPU] = resource.MustParse(cpu) 7224 } 7225 if memory != "" { 7226 res[core.ResourceMemory] = resource.MustParse(memory) 7227 } 7228 if storage != "" { 7229 res[core.ResourceEphemeralStorage] = resource.MustParse(storage) 7230 } 7231 return res 7232 } 7233 7234 func TestValidateEphemeralContainers(t *testing.T) { 7235 containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}} 7236 initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}} 7237 vols := map[string]core.VolumeSource{ 7238 "blk": {PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}}, 7239 "vol": {EmptyDir: &core.EmptyDirVolumeSource{}}, 7240 } 7241 7242 // Success Cases 7243 for title, ephemeralContainers := range map[string][]core.EphemeralContainer{ 7244 "Empty Ephemeral Containers": {}, 7245 "Single Container": { 7246 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7247 }, 7248 "Multiple Containers": { 7249 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7250 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7251 }, 7252 "Single Container with Target": {{ 7253 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7254 TargetContainerName: "ctr", 7255 }}, 7256 "All allowed fields": {{ 7257 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7258 7259 Name: "debug", 7260 Image: "image", 7261 Command: []string{"bash"}, 7262 Args: []string{"bash"}, 7263 WorkingDir: "/", 7264 EnvFrom: []core.EnvFromSource{{ 7265 ConfigMapRef: &core.ConfigMapEnvSource{ 7266 LocalObjectReference: core.LocalObjectReference{Name: "dummy"}, 7267 Optional: &[]bool{true}[0], 7268 }, 7269 }}, 7270 Env: []core.EnvVar{ 7271 {Name: "TEST", Value: "TRUE"}, 7272 }, 7273 VolumeMounts: []core.VolumeMount{ 7274 {Name: "vol", MountPath: "/vol"}, 7275 }, 7276 VolumeDevices: []core.VolumeDevice{ 7277 {Name: "blk", DevicePath: "/dev/block"}, 7278 }, 7279 TerminationMessagePath: "/dev/termination-log", 7280 TerminationMessagePolicy: "File", 7281 ImagePullPolicy: "IfNotPresent", 7282 SecurityContext: &core.SecurityContext{ 7283 Capabilities: &core.Capabilities{ 7284 Add: []core.Capability{"SYS_ADMIN"}, 7285 }, 7286 }, 7287 Stdin: true, 7288 StdinOnce: true, 7289 TTY: true, 7290 }, 7291 }}, 7292 } { 7293 var PodRestartPolicy core.RestartPolicy 7294 PodRestartPolicy = "Never" 7295 if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 { 7296 t.Errorf("expected success for '%s' but got errors: %v", title, errs) 7297 } 7298 7299 PodRestartPolicy = "Always" 7300 if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 { 7301 t.Errorf("expected success for '%s' but got errors: %v", title, errs) 7302 } 7303 7304 PodRestartPolicy = "OnFailure" 7305 if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 { 7306 t.Errorf("expected success for '%s' but got errors: %v", title, errs) 7307 } 7308 } 7309 7310 // Failure Cases 7311 tcs := []struct { 7312 title, line string 7313 ephemeralContainers []core.EphemeralContainer 7314 expectedErrors field.ErrorList 7315 }{{ 7316 "Name Collision with Container.Containers", 7317 line(), 7318 []core.EphemeralContainer{ 7319 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7320 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7321 }, 7322 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}}, 7323 }, { 7324 "Name Collision with Container.InitContainers", 7325 line(), 7326 []core.EphemeralContainer{ 7327 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7328 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7329 }, 7330 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}}, 7331 }, { 7332 "Name Collision with EphemeralContainers", 7333 line(), 7334 []core.EphemeralContainer{ 7335 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7336 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7337 }, 7338 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}}, 7339 }, { 7340 "empty Container", 7341 line(), 7342 []core.EphemeralContainer{ 7343 {EphemeralContainerCommon: core.EphemeralContainerCommon{}}, 7344 }, 7345 field.ErrorList{ 7346 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}, 7347 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"}, 7348 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"}, 7349 {Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"}, 7350 }, 7351 }, { 7352 "empty Container Name", 7353 line(), 7354 []core.EphemeralContainer{ 7355 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7356 }, 7357 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}}, 7358 }, { 7359 "whitespace padded image name", 7360 line(), 7361 []core.EphemeralContainer{ 7362 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7363 }, 7364 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}}, 7365 }, { 7366 "invalid image pull policy", 7367 line(), 7368 []core.EphemeralContainer{ 7369 {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}}, 7370 }, 7371 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}}, 7372 }, { 7373 "TargetContainerName doesn't exist", 7374 line(), 7375 []core.EphemeralContainer{{ 7376 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7377 TargetContainerName: "bogus", 7378 }}, 7379 field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}}, 7380 }, { 7381 "Targets an ephemeral container", 7382 line(), 7383 []core.EphemeralContainer{{ 7384 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7385 }, { 7386 EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7387 TargetContainerName: "debug", 7388 }}, 7389 field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}}, 7390 }, { 7391 "Container uses disallowed field: Lifecycle", 7392 line(), 7393 []core.EphemeralContainer{{ 7394 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7395 Name: "debug", 7396 Image: "image", 7397 ImagePullPolicy: "IfNotPresent", 7398 TerminationMessagePolicy: "File", 7399 Lifecycle: &core.Lifecycle{ 7400 PreStop: &core.LifecycleHandler{ 7401 Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, 7402 }, 7403 }, 7404 }, 7405 }}, 7406 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}}, 7407 }, { 7408 "Container uses disallowed field: LivenessProbe", 7409 line(), 7410 []core.EphemeralContainer{{ 7411 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7412 Name: "debug", 7413 Image: "image", 7414 ImagePullPolicy: "IfNotPresent", 7415 TerminationMessagePolicy: "File", 7416 LivenessProbe: &core.Probe{ 7417 ProbeHandler: core.ProbeHandler{ 7418 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 7419 }, 7420 SuccessThreshold: 1, 7421 }, 7422 }, 7423 }}, 7424 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}}, 7425 }, { 7426 "Container uses disallowed field: Ports", 7427 line(), 7428 []core.EphemeralContainer{{ 7429 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7430 Name: "debug", 7431 Image: "image", 7432 ImagePullPolicy: "IfNotPresent", 7433 TerminationMessagePolicy: "File", 7434 Ports: []core.ContainerPort{ 7435 {Protocol: "TCP", ContainerPort: 80}, 7436 }, 7437 }, 7438 }}, 7439 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}}, 7440 }, { 7441 "Container uses disallowed field: ReadinessProbe", 7442 line(), 7443 []core.EphemeralContainer{{ 7444 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7445 Name: "debug", 7446 Image: "image", 7447 ImagePullPolicy: "IfNotPresent", 7448 TerminationMessagePolicy: "File", 7449 ReadinessProbe: &core.Probe{ 7450 ProbeHandler: core.ProbeHandler{ 7451 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 7452 }, 7453 }, 7454 }, 7455 }}, 7456 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}}, 7457 }, { 7458 "Container uses disallowed field: StartupProbe", 7459 line(), 7460 []core.EphemeralContainer{{ 7461 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7462 Name: "debug", 7463 Image: "image", 7464 ImagePullPolicy: "IfNotPresent", 7465 TerminationMessagePolicy: "File", 7466 StartupProbe: &core.Probe{ 7467 ProbeHandler: core.ProbeHandler{ 7468 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 7469 }, 7470 SuccessThreshold: 1, 7471 }, 7472 }, 7473 }}, 7474 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}}, 7475 }, { 7476 "Container uses disallowed field: Resources", 7477 line(), 7478 []core.EphemeralContainer{{ 7479 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7480 Name: "debug", 7481 Image: "image", 7482 ImagePullPolicy: "IfNotPresent", 7483 TerminationMessagePolicy: "File", 7484 Resources: core.ResourceRequirements{ 7485 Limits: core.ResourceList{ 7486 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7487 }, 7488 }, 7489 }, 7490 }}, 7491 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}}, 7492 }, { 7493 "Container uses disallowed field: VolumeMount.SubPath", 7494 line(), 7495 []core.EphemeralContainer{{ 7496 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7497 Name: "debug", 7498 Image: "image", 7499 ImagePullPolicy: "IfNotPresent", 7500 TerminationMessagePolicy: "File", 7501 VolumeMounts: []core.VolumeMount{ 7502 {Name: "vol", MountPath: "/vol"}, 7503 {Name: "vol", MountPath: "/volsub", SubPath: "foo"}, 7504 }, 7505 }, 7506 }}, 7507 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}}, 7508 }, { 7509 "Container uses disallowed field: VolumeMount.SubPathExpr", 7510 line(), 7511 []core.EphemeralContainer{{ 7512 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7513 Name: "debug", 7514 Image: "image", 7515 ImagePullPolicy: "IfNotPresent", 7516 TerminationMessagePolicy: "File", 7517 VolumeMounts: []core.VolumeMount{ 7518 {Name: "vol", MountPath: "/vol"}, 7519 {Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"}, 7520 }, 7521 }, 7522 }}, 7523 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}}, 7524 }, { 7525 "Disallowed field with other errors should only return a single Forbidden", 7526 line(), 7527 []core.EphemeralContainer{{ 7528 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7529 Name: "debug", 7530 Image: "image", 7531 ImagePullPolicy: "IfNotPresent", 7532 TerminationMessagePolicy: "File", 7533 Lifecycle: &core.Lifecycle{ 7534 PreStop: &core.LifecycleHandler{ 7535 Exec: &core.ExecAction{Command: []string{}}, 7536 }, 7537 }, 7538 }, 7539 }}, 7540 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}}, 7541 }, { 7542 "Container uses disallowed field: ResizePolicy", 7543 line(), 7544 []core.EphemeralContainer{{ 7545 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7546 Name: "resources-resize-policy", 7547 Image: "image", 7548 ImagePullPolicy: "IfNotPresent", 7549 TerminationMessagePolicy: "File", 7550 ResizePolicy: []core.ContainerResizePolicy{ 7551 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7552 }, 7553 }, 7554 }}, 7555 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}}, 7556 }, { 7557 "Forbidden RestartPolicy: Always", 7558 line(), 7559 []core.EphemeralContainer{{ 7560 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7561 Name: "foo", 7562 Image: "image", 7563 ImagePullPolicy: "IfNotPresent", 7564 TerminationMessagePolicy: "File", 7565 RestartPolicy: &containerRestartPolicyAlways, 7566 }, 7567 }}, 7568 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, 7569 }, { 7570 "Forbidden RestartPolicy: OnFailure", 7571 line(), 7572 []core.EphemeralContainer{{ 7573 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7574 Name: "foo", 7575 Image: "image", 7576 ImagePullPolicy: "IfNotPresent", 7577 TerminationMessagePolicy: "File", 7578 RestartPolicy: &containerRestartPolicyOnFailure, 7579 }, 7580 }}, 7581 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, 7582 }, { 7583 "Forbidden RestartPolicy: Never", 7584 line(), 7585 []core.EphemeralContainer{{ 7586 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7587 Name: "foo", 7588 Image: "image", 7589 ImagePullPolicy: "IfNotPresent", 7590 TerminationMessagePolicy: "File", 7591 RestartPolicy: &containerRestartPolicyNever, 7592 }, 7593 }}, 7594 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, 7595 }, { 7596 "Forbidden RestartPolicy: invalid", 7597 line(), 7598 []core.EphemeralContainer{{ 7599 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7600 Name: "foo", 7601 Image: "image", 7602 ImagePullPolicy: "IfNotPresent", 7603 TerminationMessagePolicy: "File", 7604 RestartPolicy: &containerRestartPolicyInvalid, 7605 }, 7606 }}, 7607 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, 7608 }, { 7609 "Forbidden RestartPolicy: empty", 7610 line(), 7611 []core.EphemeralContainer{{ 7612 EphemeralContainerCommon: core.EphemeralContainerCommon{ 7613 Name: "foo", 7614 Image: "image", 7615 ImagePullPolicy: "IfNotPresent", 7616 TerminationMessagePolicy: "File", 7617 RestartPolicy: &containerRestartPolicyEmpty, 7618 }, 7619 }}, 7620 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}}, 7621 }, 7622 } 7623 7624 var PodRestartPolicy core.RestartPolicy 7625 7626 for _, tc := range tcs { 7627 t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) { 7628 7629 PodRestartPolicy = "Never" 7630 errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy) 7631 if len(errs) == 0 { 7632 t.Fatal("expected error but received none") 7633 } 7634 7635 PodRestartPolicy = "Always" 7636 errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy) 7637 if len(errs) == 0 { 7638 t.Fatal("expected error but received none") 7639 } 7640 7641 PodRestartPolicy = "OnFailure" 7642 errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy) 7643 if len(errs) == 0 { 7644 t.Fatal("expected error but received none") 7645 } 7646 7647 if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" { 7648 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff) 7649 t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs)) 7650 } 7651 }) 7652 } 7653 } 7654 7655 func TestValidateWindowsPodSecurityContext(t *testing.T) { 7656 validWindowsSC := &core.PodSecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}} 7657 invalidWindowsSC := &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummyRole"}} 7658 cases := map[string]struct { 7659 podSec *core.PodSpec 7660 expectErr bool 7661 errorType field.ErrorType 7662 errorDetail string 7663 }{ 7664 "valid SC, windows, no error": { 7665 podSec: &core.PodSpec{SecurityContext: validWindowsSC}, 7666 expectErr: false, 7667 }, 7668 "invalid SC, windows, error": { 7669 podSec: &core.PodSpec{SecurityContext: invalidWindowsSC}, 7670 errorType: "FieldValueForbidden", 7671 errorDetail: "cannot be set for a windows pod", 7672 expectErr: true, 7673 }, 7674 } 7675 for k, v := range cases { 7676 t.Run(k, func(t *testing.T) { 7677 errs := validateWindows(v.podSec, field.NewPath("field")) 7678 if v.expectErr && len(errs) > 0 { 7679 if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { 7680 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) 7681 } 7682 } else if v.expectErr && len(errs) == 0 { 7683 t.Errorf("Unexpected success") 7684 } 7685 if !v.expectErr && len(errs) != 0 { 7686 t.Errorf("Unexpected error(s): %v", errs) 7687 } 7688 }) 7689 } 7690 } 7691 7692 func TestValidateLinuxPodSecurityContext(t *testing.T) { 7693 runAsUser := int64(1) 7694 validLinuxSC := &core.PodSecurityContext{ 7695 SELinuxOptions: &core.SELinuxOptions{ 7696 User: "user", 7697 Role: "role", 7698 Type: "type", 7699 Level: "level", 7700 }, 7701 RunAsUser: &runAsUser, 7702 } 7703 invalidLinuxSC := &core.PodSecurityContext{ 7704 WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")}, 7705 } 7706 7707 cases := map[string]struct { 7708 podSpec *core.PodSpec 7709 expectErr bool 7710 errorType field.ErrorType 7711 errorDetail string 7712 }{ 7713 "valid SC, linux, no error": { 7714 podSpec: &core.PodSpec{SecurityContext: validLinuxSC}, 7715 expectErr: false, 7716 }, 7717 "invalid SC, linux, error": { 7718 podSpec: &core.PodSpec{SecurityContext: invalidLinuxSC}, 7719 errorType: "FieldValueForbidden", 7720 errorDetail: "windows options cannot be set for a linux pod", 7721 expectErr: true, 7722 }, 7723 } 7724 for k, v := range cases { 7725 t.Run(k, func(t *testing.T) { 7726 errs := validateLinux(v.podSpec, field.NewPath("field")) 7727 if v.expectErr && len(errs) > 0 { 7728 if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { 7729 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) 7730 } 7731 } else if v.expectErr && len(errs) == 0 { 7732 t.Errorf("Unexpected success") 7733 } 7734 if !v.expectErr && len(errs) != 0 { 7735 t.Errorf("Unexpected error(s): %v", errs) 7736 } 7737 }) 7738 } 7739 } 7740 7741 func TestValidateContainers(t *testing.T) { 7742 volumeDevices := make(map[string]core.VolumeSource) 7743 capabilities.SetForTests(capabilities.Capabilities{ 7744 AllowPrivileged: true, 7745 }) 7746 7747 successCase := []core.Container{ 7748 {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7749 // backwards compatibility to ensure containers in pod template spec do not check for this 7750 {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7751 {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7752 {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7753 {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, { 7754 Name: "life-123", 7755 Image: "image", 7756 Lifecycle: &core.Lifecycle{ 7757 PreStop: &core.LifecycleHandler{ 7758 Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, 7759 }, 7760 }, 7761 ImagePullPolicy: "IfNotPresent", 7762 TerminationMessagePolicy: "File", 7763 }, { 7764 Name: "resources-test", 7765 Image: "image", 7766 Resources: core.ResourceRequirements{ 7767 Limits: core.ResourceList{ 7768 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7769 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7770 core.ResourceName("my.org/resource"): resource.MustParse("10"), 7771 }, 7772 }, 7773 ImagePullPolicy: "IfNotPresent", 7774 TerminationMessagePolicy: "File", 7775 }, { 7776 Name: "resources-test-with-request-and-limit", 7777 Image: "image", 7778 Resources: core.ResourceRequirements{ 7779 Requests: core.ResourceList{ 7780 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7781 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7782 }, 7783 Limits: core.ResourceList{ 7784 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7785 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7786 }, 7787 }, 7788 ImagePullPolicy: "IfNotPresent", 7789 TerminationMessagePolicy: "File", 7790 }, { 7791 Name: "resources-request-limit-simple", 7792 Image: "image", 7793 Resources: core.ResourceRequirements{ 7794 Requests: core.ResourceList{ 7795 core.ResourceName(core.ResourceCPU): resource.MustParse("8"), 7796 }, 7797 Limits: core.ResourceList{ 7798 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7799 }, 7800 }, 7801 ImagePullPolicy: "IfNotPresent", 7802 TerminationMessagePolicy: "File", 7803 }, { 7804 Name: "resources-request-limit-edge", 7805 Image: "image", 7806 Resources: core.ResourceRequirements{ 7807 Requests: core.ResourceList{ 7808 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7809 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7810 core.ResourceName("my.org/resource"): resource.MustParse("10"), 7811 }, 7812 Limits: core.ResourceList{ 7813 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7814 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7815 core.ResourceName("my.org/resource"): resource.MustParse("10"), 7816 }, 7817 }, 7818 ImagePullPolicy: "IfNotPresent", 7819 TerminationMessagePolicy: "File", 7820 }, { 7821 Name: "resources-request-limit-partials", 7822 Image: "image", 7823 Resources: core.ResourceRequirements{ 7824 Requests: core.ResourceList{ 7825 core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"), 7826 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7827 }, 7828 Limits: core.ResourceList{ 7829 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 7830 core.ResourceName("my.org/resource"): resource.MustParse("10"), 7831 }, 7832 }, 7833 ImagePullPolicy: "IfNotPresent", 7834 TerminationMessagePolicy: "File", 7835 }, { 7836 Name: "resources-request", 7837 Image: "image", 7838 Resources: core.ResourceRequirements{ 7839 Requests: core.ResourceList{ 7840 core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"), 7841 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 7842 }, 7843 }, 7844 ImagePullPolicy: "IfNotPresent", 7845 TerminationMessagePolicy: "File", 7846 }, { 7847 Name: "resources-resize-policy", 7848 Image: "image", 7849 ResizePolicy: []core.ContainerResizePolicy{ 7850 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7851 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7852 }, 7853 ImagePullPolicy: "IfNotPresent", 7854 TerminationMessagePolicy: "File", 7855 }, { 7856 Name: "same-host-port-different-protocol", 7857 Image: "image", 7858 Ports: []core.ContainerPort{ 7859 {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, 7860 {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, 7861 }, 7862 ImagePullPolicy: "IfNotPresent", 7863 TerminationMessagePolicy: "File", 7864 }, { 7865 Name: "fallback-to-logs-termination-message", 7866 Image: "image", 7867 ImagePullPolicy: "IfNotPresent", 7868 TerminationMessagePolicy: "FallbackToLogsOnError", 7869 }, { 7870 Name: "file-termination-message", 7871 Image: "image", 7872 ImagePullPolicy: "IfNotPresent", 7873 TerminationMessagePolicy: "File", 7874 }, { 7875 Name: "env-from-source", 7876 Image: "image", 7877 ImagePullPolicy: "IfNotPresent", 7878 TerminationMessagePolicy: "File", 7879 EnvFrom: []core.EnvFromSource{{ 7880 ConfigMapRef: &core.ConfigMapEnvSource{ 7881 LocalObjectReference: core.LocalObjectReference{ 7882 Name: "test", 7883 }, 7884 }, 7885 }}, 7886 }, 7887 {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, { 7888 Name: "live-123", 7889 Image: "image", 7890 LivenessProbe: &core.Probe{ 7891 ProbeHandler: core.ProbeHandler{ 7892 TCPSocket: &core.TCPSocketAction{ 7893 Port: intstr.FromInt32(80), 7894 }, 7895 }, 7896 SuccessThreshold: 1, 7897 }, 7898 ImagePullPolicy: "IfNotPresent", 7899 TerminationMessagePolicy: "File", 7900 }, { 7901 Name: "startup-123", 7902 Image: "image", 7903 StartupProbe: &core.Probe{ 7904 ProbeHandler: core.ProbeHandler{ 7905 TCPSocket: &core.TCPSocketAction{ 7906 Port: intstr.FromInt32(80), 7907 }, 7908 }, 7909 SuccessThreshold: 1, 7910 }, 7911 ImagePullPolicy: "IfNotPresent", 7912 TerminationMessagePolicy: "File", 7913 }, { 7914 Name: "resize-policy-cpu", 7915 Image: "image", 7916 ImagePullPolicy: "IfNotPresent", 7917 TerminationMessagePolicy: "File", 7918 ResizePolicy: []core.ContainerResizePolicy{ 7919 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 7920 }, 7921 }, { 7922 Name: "resize-policy-mem", 7923 Image: "image", 7924 ImagePullPolicy: "IfNotPresent", 7925 TerminationMessagePolicy: "File", 7926 ResizePolicy: []core.ContainerResizePolicy{ 7927 {ResourceName: "memory", RestartPolicy: "RestartContainer"}, 7928 }, 7929 }, { 7930 Name: "resize-policy-cpu-and-mem", 7931 Image: "image", 7932 ImagePullPolicy: "IfNotPresent", 7933 TerminationMessagePolicy: "File", 7934 ResizePolicy: []core.ContainerResizePolicy{ 7935 {ResourceName: "memory", RestartPolicy: "NotRequired"}, 7936 {ResourceName: "cpu", RestartPolicy: "RestartContainer"}, 7937 }, 7938 }, 7939 } 7940 7941 var PodRestartPolicy core.RestartPolicy = "Always" 7942 if errs := validateContainers(successCase, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 { 7943 t.Errorf("expected success: %v", errs) 7944 } 7945 7946 capabilities.SetForTests(capabilities.Capabilities{ 7947 AllowPrivileged: false, 7948 }) 7949 errorCases := []struct { 7950 title, line string 7951 containers []core.Container 7952 expectedErrors field.ErrorList 7953 }{{ 7954 "zero-length name", 7955 line(), 7956 []core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7957 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}}, 7958 }, { 7959 "zero-length-image", 7960 line(), 7961 []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7962 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}}, 7963 }, { 7964 "name > 63 characters", 7965 line(), 7966 []core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7967 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}}, 7968 }, { 7969 "name not a DNS label", 7970 line(), 7971 []core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7972 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}}, 7973 }, { 7974 "name not unique", 7975 line(), 7976 []core.Container{ 7977 {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7978 {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7979 }, 7980 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}}, 7981 }, { 7982 "zero-length image", 7983 line(), 7984 []core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 7985 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}}, 7986 }, { 7987 "host port not unique", 7988 line(), 7989 []core.Container{ 7990 {Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}}, 7991 ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7992 {Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}}, 7993 ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 7994 }, 7995 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}}, 7996 }, { 7997 "invalid env var name", 7998 line(), 7999 []core.Container{ 8000 {Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 8001 }, 8002 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}}, 8003 }, { 8004 "unknown volume name", 8005 line(), 8006 []core.Container{ 8007 {Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}}, 8008 ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 8009 }, 8010 field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}}, 8011 }, { 8012 "invalid lifecycle, no exec command.", 8013 line(), 8014 []core.Container{{ 8015 Name: "life-123", 8016 Image: "image", 8017 Lifecycle: &core.Lifecycle{ 8018 PreStop: &core.LifecycleHandler{ 8019 Exec: &core.ExecAction{}, 8020 }, 8021 }, 8022 ImagePullPolicy: "IfNotPresent", 8023 TerminationMessagePolicy: "File", 8024 }}, 8025 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}}, 8026 }, { 8027 "invalid lifecycle, no http path.", 8028 line(), 8029 []core.Container{{ 8030 Name: "life-123", 8031 Image: "image", 8032 Lifecycle: &core.Lifecycle{ 8033 PreStop: &core.LifecycleHandler{ 8034 HTTPGet: &core.HTTPGetAction{ 8035 Port: intstr.FromInt32(80), 8036 Scheme: "HTTP", 8037 }, 8038 }, 8039 }, 8040 ImagePullPolicy: "IfNotPresent", 8041 TerminationMessagePolicy: "File", 8042 }}, 8043 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}}, 8044 }, { 8045 "invalid lifecycle, no http port.", 8046 line(), 8047 []core.Container{{ 8048 Name: "life-123", 8049 Image: "image", 8050 Lifecycle: &core.Lifecycle{ 8051 PreStop: &core.LifecycleHandler{ 8052 HTTPGet: &core.HTTPGetAction{ 8053 Path: "/", 8054 Scheme: "HTTP", 8055 }, 8056 }, 8057 }, 8058 ImagePullPolicy: "IfNotPresent", 8059 TerminationMessagePolicy: "File", 8060 }}, 8061 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}}, 8062 }, { 8063 "invalid lifecycle, no http scheme.", 8064 line(), 8065 []core.Container{{ 8066 Name: "life-123", 8067 Image: "image", 8068 Lifecycle: &core.Lifecycle{ 8069 PreStop: &core.LifecycleHandler{ 8070 HTTPGet: &core.HTTPGetAction{ 8071 Path: "/", 8072 Port: intstr.FromInt32(80), 8073 }, 8074 }, 8075 }, 8076 ImagePullPolicy: "IfNotPresent", 8077 TerminationMessagePolicy: "File", 8078 }}, 8079 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}}, 8080 }, { 8081 "invalid lifecycle, no tcp socket port.", 8082 line(), 8083 []core.Container{{ 8084 Name: "life-123", 8085 Image: "image", 8086 Lifecycle: &core.Lifecycle{ 8087 PreStop: &core.LifecycleHandler{ 8088 TCPSocket: &core.TCPSocketAction{}, 8089 }, 8090 }, 8091 ImagePullPolicy: "IfNotPresent", 8092 TerminationMessagePolicy: "File", 8093 }}, 8094 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}}, 8095 }, { 8096 "invalid lifecycle, zero tcp socket port.", 8097 line(), 8098 []core.Container{{ 8099 Name: "life-123", 8100 Image: "image", 8101 Lifecycle: &core.Lifecycle{ 8102 PreStop: &core.LifecycleHandler{ 8103 TCPSocket: &core.TCPSocketAction{ 8104 Port: intstr.FromInt32(0), 8105 }, 8106 }, 8107 }, 8108 ImagePullPolicy: "IfNotPresent", 8109 TerminationMessagePolicy: "File", 8110 }}, 8111 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}}, 8112 }, { 8113 "invalid lifecycle, no action.", 8114 line(), 8115 []core.Container{{ 8116 Name: "life-123", 8117 Image: "image", 8118 Lifecycle: &core.Lifecycle{ 8119 PreStop: &core.LifecycleHandler{}, 8120 }, 8121 ImagePullPolicy: "IfNotPresent", 8122 TerminationMessagePolicy: "File", 8123 }}, 8124 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}}, 8125 }, { 8126 "invalid readiness probe, terminationGracePeriodSeconds set.", 8127 line(), 8128 []core.Container{{ 8129 Name: "life-123", 8130 Image: "image", 8131 ReadinessProbe: &core.Probe{ 8132 ProbeHandler: core.ProbeHandler{ 8133 TCPSocket: &core.TCPSocketAction{ 8134 Port: intstr.FromInt32(80), 8135 }, 8136 }, 8137 TerminationGracePeriodSeconds: utilpointer.Int64(10), 8138 }, 8139 ImagePullPolicy: "IfNotPresent", 8140 TerminationMessagePolicy: "File", 8141 }}, 8142 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}}, 8143 }, { 8144 "invalid liveness probe, no tcp socket port.", 8145 line(), 8146 []core.Container{{ 8147 Name: "live-123", 8148 Image: "image", 8149 LivenessProbe: &core.Probe{ 8150 ProbeHandler: core.ProbeHandler{ 8151 TCPSocket: &core.TCPSocketAction{}, 8152 }, 8153 SuccessThreshold: 1, 8154 }, 8155 ImagePullPolicy: "IfNotPresent", 8156 TerminationMessagePolicy: "File", 8157 }}, 8158 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}}, 8159 }, { 8160 "invalid liveness probe, no action.", 8161 line(), 8162 []core.Container{{ 8163 Name: "live-123", 8164 Image: "image", 8165 LivenessProbe: &core.Probe{ 8166 ProbeHandler: core.ProbeHandler{}, 8167 SuccessThreshold: 1, 8168 }, 8169 ImagePullPolicy: "IfNotPresent", 8170 TerminationMessagePolicy: "File", 8171 }}, 8172 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}}, 8173 }, { 8174 "invalid liveness probe, successThreshold != 1", 8175 line(), 8176 []core.Container{{ 8177 Name: "live-123", 8178 Image: "image", 8179 LivenessProbe: &core.Probe{ 8180 ProbeHandler: core.ProbeHandler{ 8181 TCPSocket: &core.TCPSocketAction{ 8182 Port: intstr.FromInt32(80), 8183 }, 8184 }, 8185 SuccessThreshold: 2, 8186 }, 8187 ImagePullPolicy: "IfNotPresent", 8188 TerminationMessagePolicy: "File", 8189 }}, 8190 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}}, 8191 }, { 8192 "invalid startup probe, successThreshold != 1", 8193 line(), 8194 []core.Container{{ 8195 Name: "startup-123", 8196 Image: "image", 8197 StartupProbe: &core.Probe{ 8198 ProbeHandler: core.ProbeHandler{ 8199 TCPSocket: &core.TCPSocketAction{ 8200 Port: intstr.FromInt32(80), 8201 }, 8202 }, 8203 SuccessThreshold: 2, 8204 }, 8205 ImagePullPolicy: "IfNotPresent", 8206 TerminationMessagePolicy: "File", 8207 }}, 8208 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}}, 8209 }, { 8210 "invalid liveness probe, negative numbers", 8211 line(), 8212 []core.Container{{ 8213 Name: "live-123", 8214 Image: "image", 8215 LivenessProbe: &core.Probe{ 8216 ProbeHandler: core.ProbeHandler{ 8217 TCPSocket: &core.TCPSocketAction{ 8218 Port: intstr.FromInt32(80), 8219 }, 8220 }, 8221 InitialDelaySeconds: -1, 8222 TimeoutSeconds: -1, 8223 PeriodSeconds: -1, 8224 SuccessThreshold: -1, 8225 FailureThreshold: -1, 8226 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 8227 }, 8228 ImagePullPolicy: "IfNotPresent", 8229 TerminationMessagePolicy: "File", 8230 }}, 8231 field.ErrorList{ 8232 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"}, 8233 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"}, 8234 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"}, 8235 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}, 8236 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"}, 8237 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"}, 8238 {Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}, 8239 }, 8240 }, { 8241 "invalid readiness probe, negative numbers", 8242 line(), 8243 []core.Container{{ 8244 Name: "ready-123", 8245 Image: "image", 8246 ReadinessProbe: &core.Probe{ 8247 ProbeHandler: core.ProbeHandler{ 8248 TCPSocket: &core.TCPSocketAction{ 8249 Port: intstr.FromInt32(80), 8250 }, 8251 }, 8252 InitialDelaySeconds: -1, 8253 TimeoutSeconds: -1, 8254 PeriodSeconds: -1, 8255 SuccessThreshold: -1, 8256 FailureThreshold: -1, 8257 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 8258 }, 8259 ImagePullPolicy: "IfNotPresent", 8260 TerminationMessagePolicy: "File", 8261 }}, 8262 field.ErrorList{ 8263 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"}, 8264 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"}, 8265 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"}, 8266 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"}, 8267 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"}, 8268 // terminationGracePeriodSeconds returns multiple validation errors here: 8269 // containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must be greater than 0 8270 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}, 8271 // containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must not be set for readinessProbes 8272 {Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}, 8273 }, 8274 }, { 8275 "invalid startup probe, negative numbers", 8276 line(), 8277 []core.Container{{ 8278 Name: "startup-123", 8279 Image: "image", 8280 StartupProbe: &core.Probe{ 8281 ProbeHandler: core.ProbeHandler{ 8282 TCPSocket: &core.TCPSocketAction{ 8283 Port: intstr.FromInt32(80), 8284 }, 8285 }, 8286 InitialDelaySeconds: -1, 8287 TimeoutSeconds: -1, 8288 PeriodSeconds: -1, 8289 SuccessThreshold: -1, 8290 FailureThreshold: -1, 8291 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 8292 }, 8293 ImagePullPolicy: "IfNotPresent", 8294 TerminationMessagePolicy: "File", 8295 }}, 8296 field.ErrorList{ 8297 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"}, 8298 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"}, 8299 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"}, 8300 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}, 8301 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"}, 8302 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"}, 8303 {Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}, 8304 }, 8305 }, { 8306 "invalid message termination policy", 8307 line(), 8308 []core.Container{{ 8309 Name: "life-123", 8310 Image: "image", 8311 ImagePullPolicy: "IfNotPresent", 8312 TerminationMessagePolicy: "Unknown", 8313 }}, 8314 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}}, 8315 }, { 8316 "empty message termination policy", 8317 line(), 8318 []core.Container{{ 8319 Name: "life-123", 8320 Image: "image", 8321 ImagePullPolicy: "IfNotPresent", 8322 TerminationMessagePolicy: "", 8323 }}, 8324 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}}, 8325 }, { 8326 "privilege disabled", 8327 line(), 8328 []core.Container{{ 8329 Name: "abc", 8330 Image: "image", 8331 SecurityContext: fakeValidSecurityContext(true), 8332 ImagePullPolicy: "IfNotPresent", 8333 TerminationMessagePolicy: "File", 8334 }}, 8335 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}}, 8336 }, { 8337 "invalid compute resource", 8338 line(), 8339 []core.Container{{ 8340 Name: "abc-123", 8341 Image: "image", 8342 Resources: core.ResourceRequirements{ 8343 Limits: core.ResourceList{ 8344 "disk": resource.MustParse("10G"), 8345 }, 8346 }, 8347 ImagePullPolicy: "IfNotPresent", 8348 TerminationMessagePolicy: "File", 8349 }}, 8350 field.ErrorList{ 8351 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"}, 8352 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"}, 8353 }, 8354 }, { 8355 "Resource CPU invalid", 8356 line(), 8357 []core.Container{{ 8358 Name: "abc-123", 8359 Image: "image", 8360 Resources: core.ResourceRequirements{ 8361 Limits: getResourceLimits("-10", "0"), 8362 }, 8363 ImagePullPolicy: "IfNotPresent", 8364 TerminationMessagePolicy: "File", 8365 }}, 8366 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}}, 8367 }, { 8368 "Resource Requests CPU invalid", 8369 line(), 8370 []core.Container{{ 8371 Name: "abc-123", 8372 Image: "image", 8373 Resources: core.ResourceRequirements{ 8374 Requests: getResourceLimits("-10", "0"), 8375 }, 8376 ImagePullPolicy: "IfNotPresent", 8377 TerminationMessagePolicy: "File", 8378 }}, 8379 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}}, 8380 }, { 8381 "Resource Memory invalid", 8382 line(), 8383 []core.Container{{ 8384 Name: "abc-123", 8385 Image: "image", 8386 Resources: core.ResourceRequirements{ 8387 Limits: getResourceLimits("0", "-10"), 8388 }, 8389 ImagePullPolicy: "IfNotPresent", 8390 TerminationMessagePolicy: "File", 8391 }}, 8392 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}}, 8393 }, { 8394 "Request limit simple invalid", 8395 line(), 8396 []core.Container{{ 8397 Name: "abc-123", 8398 Image: "image", 8399 Resources: core.ResourceRequirements{ 8400 Limits: getResourceLimits("5", "3"), 8401 Requests: getResourceLimits("6", "3"), 8402 }, 8403 ImagePullPolicy: "IfNotPresent", 8404 TerminationMessagePolicy: "File", 8405 }}, 8406 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, 8407 }, { 8408 "Invalid storage limit request", 8409 line(), 8410 []core.Container{{ 8411 Name: "abc-123", 8412 Image: "image", 8413 Resources: core.ResourceRequirements{ 8414 Limits: core.ResourceList{ 8415 core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI), 8416 }, 8417 }, 8418 ImagePullPolicy: "IfNotPresent", 8419 TerminationMessagePolicy: "File", 8420 }}, 8421 field.ErrorList{ 8422 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"}, 8423 {Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"}, 8424 }, 8425 }, { 8426 "CPU request limit multiple invalid", 8427 line(), 8428 []core.Container{{ 8429 Name: "abc-123", 8430 Image: "image", 8431 Resources: core.ResourceRequirements{ 8432 Limits: getResourceLimits("5", "3"), 8433 Requests: getResourceLimits("6", "3"), 8434 }, 8435 ImagePullPolicy: "IfNotPresent", 8436 TerminationMessagePolicy: "File", 8437 }}, 8438 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, 8439 }, { 8440 "Memory request limit multiple invalid", 8441 line(), 8442 []core.Container{{ 8443 Name: "abc-123", 8444 Image: "image", 8445 Resources: core.ResourceRequirements{ 8446 Limits: getResourceLimits("5", "3"), 8447 Requests: getResourceLimits("5", "4"), 8448 }, 8449 ImagePullPolicy: "IfNotPresent", 8450 TerminationMessagePolicy: "File", 8451 }}, 8452 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}}, 8453 }, { 8454 "Invalid env from", 8455 line(), 8456 []core.Container{{ 8457 Name: "env-from-source", 8458 Image: "image", 8459 ImagePullPolicy: "IfNotPresent", 8460 TerminationMessagePolicy: "File", 8461 EnvFrom: []core.EnvFromSource{{ 8462 ConfigMapRef: &core.ConfigMapEnvSource{ 8463 LocalObjectReference: core.LocalObjectReference{ 8464 Name: "$%^&*#", 8465 }, 8466 }, 8467 }}, 8468 }}, 8469 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}}, 8470 }, { 8471 "Unsupported resize policy for memory", 8472 line(), 8473 []core.Container{{ 8474 Name: "resize-policy-mem-invalid", 8475 Image: "image", 8476 ImagePullPolicy: "IfNotPresent", 8477 TerminationMessagePolicy: "File", 8478 ResizePolicy: []core.ContainerResizePolicy{ 8479 {ResourceName: "memory", RestartPolicy: "RestartContainerrrr"}, 8480 }, 8481 }}, 8482 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, 8483 }, { 8484 "Unsupported resize policy for CPU", 8485 line(), 8486 []core.Container{{ 8487 Name: "resize-policy-cpu-invalid", 8488 Image: "image", 8489 ImagePullPolicy: "IfNotPresent", 8490 TerminationMessagePolicy: "File", 8491 ResizePolicy: []core.ContainerResizePolicy{ 8492 {ResourceName: "cpu", RestartPolicy: "RestartNotRequired"}, 8493 }, 8494 }}, 8495 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}}, 8496 }, { 8497 "Forbidden RestartPolicy: Always", 8498 line(), 8499 []core.Container{{ 8500 Name: "foo", 8501 Image: "image", 8502 ImagePullPolicy: "IfNotPresent", 8503 TerminationMessagePolicy: "File", 8504 RestartPolicy: &containerRestartPolicyAlways, 8505 }}, 8506 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, 8507 }, { 8508 "Forbidden RestartPolicy: OnFailure", 8509 line(), 8510 []core.Container{{ 8511 Name: "foo", 8512 Image: "image", 8513 ImagePullPolicy: "IfNotPresent", 8514 TerminationMessagePolicy: "File", 8515 RestartPolicy: &containerRestartPolicyOnFailure, 8516 }}, 8517 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, 8518 }, { 8519 "Forbidden RestartPolicy: Never", 8520 line(), 8521 []core.Container{{ 8522 Name: "foo", 8523 Image: "image", 8524 ImagePullPolicy: "IfNotPresent", 8525 TerminationMessagePolicy: "File", 8526 RestartPolicy: &containerRestartPolicyNever, 8527 }}, 8528 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, 8529 }, { 8530 "Forbidden RestartPolicy: invalid", 8531 line(), 8532 []core.Container{{ 8533 Name: "foo", 8534 Image: "image", 8535 ImagePullPolicy: "IfNotPresent", 8536 TerminationMessagePolicy: "File", 8537 RestartPolicy: &containerRestartPolicyInvalid, 8538 }}, 8539 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, 8540 }, { 8541 "Forbidden RestartPolicy: empty", 8542 line(), 8543 []core.Container{{ 8544 Name: "foo", 8545 Image: "image", 8546 ImagePullPolicy: "IfNotPresent", 8547 TerminationMessagePolicy: "File", 8548 RestartPolicy: &containerRestartPolicyEmpty, 8549 }}, 8550 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}}, 8551 }, 8552 } 8553 8554 for _, tc := range errorCases { 8555 t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) { 8556 errs := validateContainers(tc.containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("containers"), PodValidationOptions{}, &PodRestartPolicy) 8557 if len(errs) == 0 { 8558 t.Fatal("expected error but received none") 8559 } 8560 8561 if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" { 8562 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff) 8563 t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs)) 8564 } 8565 }) 8566 } 8567 } 8568 8569 func TestValidateInitContainers(t *testing.T) { 8570 volumeDevices := make(map[string]core.VolumeSource) 8571 capabilities.SetForTests(capabilities.Capabilities{ 8572 AllowPrivileged: true, 8573 }) 8574 8575 containers := []core.Container{{ 8576 Name: "app", 8577 Image: "nginx", 8578 ImagePullPolicy: "IfNotPresent", 8579 TerminationMessagePolicy: "File", 8580 }, 8581 } 8582 8583 successCase := []core.Container{{ 8584 Name: "container-1-same-host-port-different-protocol", 8585 Image: "image", 8586 Ports: []core.ContainerPort{ 8587 {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, 8588 {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, 8589 }, 8590 ImagePullPolicy: "IfNotPresent", 8591 TerminationMessagePolicy: "File", 8592 }, { 8593 Name: "container-2-same-host-port-different-protocol", 8594 Image: "image", 8595 Ports: []core.ContainerPort{ 8596 {ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, 8597 {ContainerPort: 80, HostPort: 80, Protocol: "UDP"}, 8598 }, 8599 ImagePullPolicy: "IfNotPresent", 8600 TerminationMessagePolicy: "File", 8601 }, { 8602 Name: "container-3-restart-always-with-lifecycle-hook-and-probes", 8603 Image: "image", 8604 ImagePullPolicy: "IfNotPresent", 8605 TerminationMessagePolicy: "File", 8606 RestartPolicy: &containerRestartPolicyAlways, 8607 Lifecycle: &core.Lifecycle{ 8608 PostStart: &core.LifecycleHandler{ 8609 Exec: &core.ExecAction{ 8610 Command: []string{"echo", "post start"}, 8611 }, 8612 }, 8613 PreStop: &core.LifecycleHandler{ 8614 Exec: &core.ExecAction{ 8615 Command: []string{"echo", "pre stop"}, 8616 }, 8617 }, 8618 }, 8619 LivenessProbe: &core.Probe{ 8620 ProbeHandler: core.ProbeHandler{ 8621 TCPSocket: &core.TCPSocketAction{ 8622 Port: intstr.FromInt32(80), 8623 }, 8624 }, 8625 SuccessThreshold: 1, 8626 }, 8627 ReadinessProbe: &core.Probe{ 8628 ProbeHandler: core.ProbeHandler{ 8629 TCPSocket: &core.TCPSocketAction{ 8630 Port: intstr.FromInt32(80), 8631 }, 8632 }, 8633 }, 8634 StartupProbe: &core.Probe{ 8635 ProbeHandler: core.ProbeHandler{ 8636 TCPSocket: &core.TCPSocketAction{ 8637 Port: intstr.FromInt32(80), 8638 }, 8639 }, 8640 SuccessThreshold: 1, 8641 }, 8642 }, 8643 } 8644 var PodRestartPolicy core.RestartPolicy = "Never" 8645 if errs := validateInitContainers(successCase, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 { 8646 t.Errorf("expected success: %v", errs) 8647 } 8648 8649 capabilities.SetForTests(capabilities.Capabilities{ 8650 AllowPrivileged: false, 8651 }) 8652 errorCases := []struct { 8653 title, line string 8654 initContainers []core.Container 8655 expectedErrors field.ErrorList 8656 }{{ 8657 "empty name", 8658 line(), 8659 []core.Container{{ 8660 Name: "", 8661 Image: "image", 8662 ImagePullPolicy: "IfNotPresent", 8663 TerminationMessagePolicy: "File", 8664 }}, 8665 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}}, 8666 }, { 8667 "name collision with regular container", 8668 line(), 8669 []core.Container{{ 8670 Name: "app", 8671 Image: "image", 8672 ImagePullPolicy: "IfNotPresent", 8673 TerminationMessagePolicy: "File", 8674 }}, 8675 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}}, 8676 }, { 8677 "invalid termination message policy", 8678 line(), 8679 []core.Container{{ 8680 Name: "init", 8681 Image: "image", 8682 ImagePullPolicy: "IfNotPresent", 8683 TerminationMessagePolicy: "Unknown", 8684 }}, 8685 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}}, 8686 }, { 8687 "duplicate names", 8688 line(), 8689 []core.Container{{ 8690 Name: "init", 8691 Image: "image", 8692 ImagePullPolicy: "IfNotPresent", 8693 TerminationMessagePolicy: "File", 8694 }, { 8695 Name: "init", 8696 Image: "image", 8697 ImagePullPolicy: "IfNotPresent", 8698 TerminationMessagePolicy: "File", 8699 }}, 8700 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}}, 8701 }, { 8702 "duplicate ports", 8703 line(), 8704 []core.Container{{ 8705 Name: "abc", 8706 Image: "image", 8707 Ports: []core.ContainerPort{{ 8708 ContainerPort: 8080, HostPort: 8080, Protocol: "TCP", 8709 }, { 8710 ContainerPort: 8080, HostPort: 8080, Protocol: "TCP", 8711 }}, 8712 ImagePullPolicy: "IfNotPresent", 8713 TerminationMessagePolicy: "File", 8714 }}, 8715 field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}}, 8716 }, { 8717 "uses disallowed field: Lifecycle", 8718 line(), 8719 []core.Container{{ 8720 Name: "debug", 8721 Image: "image", 8722 ImagePullPolicy: "IfNotPresent", 8723 TerminationMessagePolicy: "File", 8724 Lifecycle: &core.Lifecycle{ 8725 PreStop: &core.LifecycleHandler{ 8726 Exec: &core.ExecAction{Command: []string{"ls", "-l"}}, 8727 }, 8728 }, 8729 }}, 8730 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}}, 8731 }, { 8732 "uses disallowed field: LivenessProbe", 8733 line(), 8734 []core.Container{{ 8735 Name: "debug", 8736 Image: "image", 8737 ImagePullPolicy: "IfNotPresent", 8738 TerminationMessagePolicy: "File", 8739 LivenessProbe: &core.Probe{ 8740 ProbeHandler: core.ProbeHandler{ 8741 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 8742 }, 8743 SuccessThreshold: 1, 8744 }, 8745 }}, 8746 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}}, 8747 }, { 8748 "uses disallowed field: ReadinessProbe", 8749 line(), 8750 []core.Container{{ 8751 Name: "debug", 8752 Image: "image", 8753 ImagePullPolicy: "IfNotPresent", 8754 TerminationMessagePolicy: "File", 8755 ReadinessProbe: &core.Probe{ 8756 ProbeHandler: core.ProbeHandler{ 8757 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 8758 }, 8759 }, 8760 }}, 8761 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}}, 8762 }, { 8763 "Container uses disallowed field: StartupProbe", 8764 line(), 8765 []core.Container{{ 8766 Name: "debug", 8767 Image: "image", 8768 ImagePullPolicy: "IfNotPresent", 8769 TerminationMessagePolicy: "File", 8770 StartupProbe: &core.Probe{ 8771 ProbeHandler: core.ProbeHandler{ 8772 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 8773 }, 8774 SuccessThreshold: 1, 8775 }, 8776 }}, 8777 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, 8778 }, { 8779 "Disallowed field with other errors should only return a single Forbidden", 8780 line(), 8781 []core.Container{{ 8782 Name: "debug", 8783 Image: "image", 8784 ImagePullPolicy: "IfNotPresent", 8785 TerminationMessagePolicy: "File", 8786 StartupProbe: &core.Probe{ 8787 ProbeHandler: core.ProbeHandler{ 8788 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 8789 }, 8790 InitialDelaySeconds: -1, 8791 TimeoutSeconds: -1, 8792 PeriodSeconds: -1, 8793 SuccessThreshold: -1, 8794 FailureThreshold: -1, 8795 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 8796 }, 8797 }}, 8798 field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}}, 8799 }, { 8800 "Not supported RestartPolicy: OnFailure", 8801 line(), 8802 []core.Container{{ 8803 Name: "init", 8804 Image: "image", 8805 ImagePullPolicy: "IfNotPresent", 8806 TerminationMessagePolicy: "File", 8807 RestartPolicy: &containerRestartPolicyOnFailure, 8808 }}, 8809 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyOnFailure}}, 8810 }, { 8811 "Not supported RestartPolicy: Never", 8812 line(), 8813 []core.Container{{ 8814 Name: "init", 8815 Image: "image", 8816 ImagePullPolicy: "IfNotPresent", 8817 TerminationMessagePolicy: "File", 8818 RestartPolicy: &containerRestartPolicyNever, 8819 }}, 8820 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyNever}}, 8821 }, { 8822 "Not supported RestartPolicy: invalid", 8823 line(), 8824 []core.Container{{ 8825 Name: "init", 8826 Image: "image", 8827 ImagePullPolicy: "IfNotPresent", 8828 TerminationMessagePolicy: "File", 8829 RestartPolicy: &containerRestartPolicyInvalid, 8830 }}, 8831 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyInvalid}}, 8832 }, { 8833 "Not supported RestartPolicy: empty", 8834 line(), 8835 []core.Container{{ 8836 Name: "init", 8837 Image: "image", 8838 ImagePullPolicy: "IfNotPresent", 8839 TerminationMessagePolicy: "File", 8840 RestartPolicy: &containerRestartPolicyEmpty, 8841 }}, 8842 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyEmpty}}, 8843 }, { 8844 "invalid startup probe in restartable container, successThreshold != 1", 8845 line(), 8846 []core.Container{{ 8847 Name: "restartable-init", 8848 Image: "image", 8849 ImagePullPolicy: "IfNotPresent", 8850 TerminationMessagePolicy: "File", 8851 RestartPolicy: &containerRestartPolicyAlways, 8852 StartupProbe: &core.Probe{ 8853 ProbeHandler: core.ProbeHandler{ 8854 TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)}, 8855 }, 8856 SuccessThreshold: 2, 8857 }, 8858 }}, 8859 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}}, 8860 }, { 8861 "invalid readiness probe, terminationGracePeriodSeconds set.", 8862 line(), 8863 []core.Container{{ 8864 Name: "life-123", 8865 Image: "image", 8866 ImagePullPolicy: "IfNotPresent", 8867 TerminationMessagePolicy: "File", 8868 RestartPolicy: &containerRestartPolicyAlways, 8869 ReadinessProbe: &core.Probe{ 8870 ProbeHandler: core.ProbeHandler{ 8871 TCPSocket: &core.TCPSocketAction{ 8872 Port: intstr.FromInt32(80), 8873 }, 8874 }, 8875 TerminationGracePeriodSeconds: utilpointer.Int64(10), 8876 }, 8877 }}, 8878 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].readinessProbe.terminationGracePeriodSeconds", BadValue: utilpointer.Int64(10)}}, 8879 }, { 8880 "invalid liveness probe, successThreshold != 1", 8881 line(), 8882 []core.Container{{ 8883 Name: "live-123", 8884 Image: "image", 8885 ImagePullPolicy: "IfNotPresent", 8886 TerminationMessagePolicy: "File", 8887 RestartPolicy: &containerRestartPolicyAlways, 8888 LivenessProbe: &core.Probe{ 8889 ProbeHandler: core.ProbeHandler{ 8890 TCPSocket: &core.TCPSocketAction{ 8891 Port: intstr.FromInt32(80), 8892 }, 8893 }, 8894 SuccessThreshold: 2, 8895 }, 8896 }}, 8897 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].livenessProbe.successThreshold", BadValue: int32(2)}}, 8898 }, { 8899 "invalid lifecycle, no exec command.", 8900 line(), 8901 []core.Container{{ 8902 Name: "life-123", 8903 Image: "image", 8904 ImagePullPolicy: "IfNotPresent", 8905 TerminationMessagePolicy: "File", 8906 RestartPolicy: &containerRestartPolicyAlways, 8907 Lifecycle: &core.Lifecycle{ 8908 PreStop: &core.LifecycleHandler{ 8909 Exec: &core.ExecAction{}, 8910 }, 8911 }, 8912 }}, 8913 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.exec.command", BadValue: ""}}, 8914 }, { 8915 "invalid lifecycle, no http path.", 8916 line(), 8917 []core.Container{{ 8918 Name: "life-123", 8919 Image: "image", 8920 ImagePullPolicy: "IfNotPresent", 8921 TerminationMessagePolicy: "File", 8922 RestartPolicy: &containerRestartPolicyAlways, 8923 Lifecycle: &core.Lifecycle{ 8924 PreStop: &core.LifecycleHandler{ 8925 HTTPGet: &core.HTTPGetAction{ 8926 Port: intstr.FromInt32(80), 8927 Scheme: "HTTP", 8928 }, 8929 }, 8930 }, 8931 }}, 8932 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.httpGet.path", BadValue: ""}}, 8933 }, { 8934 "invalid lifecycle, no http port.", 8935 line(), 8936 []core.Container{{ 8937 Name: "life-123", 8938 Image: "image", 8939 ImagePullPolicy: "IfNotPresent", 8940 TerminationMessagePolicy: "File", 8941 RestartPolicy: &containerRestartPolicyAlways, 8942 Lifecycle: &core.Lifecycle{ 8943 PreStop: &core.LifecycleHandler{ 8944 HTTPGet: &core.HTTPGetAction{ 8945 Path: "/", 8946 Scheme: "HTTP", 8947 }, 8948 }, 8949 }, 8950 }}, 8951 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.httpGet.port", BadValue: 0}}, 8952 }, { 8953 "invalid lifecycle, no http scheme.", 8954 line(), 8955 []core.Container{{ 8956 Name: "life-123", 8957 Image: "image", 8958 ImagePullPolicy: "IfNotPresent", 8959 TerminationMessagePolicy: "File", 8960 RestartPolicy: &containerRestartPolicyAlways, 8961 Lifecycle: &core.Lifecycle{ 8962 PreStop: &core.LifecycleHandler{ 8963 HTTPGet: &core.HTTPGetAction{ 8964 Path: "/", 8965 Port: intstr.FromInt32(80), 8966 }, 8967 }, 8968 }, 8969 }}, 8970 field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].lifecycle.preStop.httpGet.scheme", BadValue: core.URIScheme("")}}, 8971 }, { 8972 "invalid lifecycle, no tcp socket port.", 8973 line(), 8974 []core.Container{{ 8975 Name: "life-123", 8976 Image: "image", 8977 ImagePullPolicy: "IfNotPresent", 8978 TerminationMessagePolicy: "File", 8979 RestartPolicy: &containerRestartPolicyAlways, 8980 Lifecycle: &core.Lifecycle{ 8981 PreStop: &core.LifecycleHandler{ 8982 TCPSocket: &core.TCPSocketAction{}, 8983 }, 8984 }, 8985 }}, 8986 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}}, 8987 }, { 8988 "invalid lifecycle, zero tcp socket port.", 8989 line(), 8990 []core.Container{{ 8991 Name: "life-123", 8992 Image: "image", 8993 ImagePullPolicy: "IfNotPresent", 8994 TerminationMessagePolicy: "File", 8995 RestartPolicy: &containerRestartPolicyAlways, 8996 Lifecycle: &core.Lifecycle{ 8997 PreStop: &core.LifecycleHandler{ 8998 TCPSocket: &core.TCPSocketAction{ 8999 Port: intstr.FromInt32(0), 9000 }, 9001 }, 9002 }, 9003 }}, 9004 field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}}, 9005 }, { 9006 "invalid lifecycle, no action.", 9007 line(), 9008 []core.Container{{ 9009 Name: "life-123", 9010 Image: "image", 9011 ImagePullPolicy: "IfNotPresent", 9012 TerminationMessagePolicy: "File", 9013 RestartPolicy: &containerRestartPolicyAlways, 9014 Lifecycle: &core.Lifecycle{ 9015 PreStop: &core.LifecycleHandler{}, 9016 }, 9017 }}, 9018 field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}}, 9019 }, 9020 } 9021 9022 for _, tc := range errorCases { 9023 t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) { 9024 errs := validateInitContainers(tc.initContainers, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("initContainers"), PodValidationOptions{}, &PodRestartPolicy) 9025 if len(errs) == 0 { 9026 t.Fatal("expected error but received none") 9027 } 9028 9029 if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "Detail")); diff != "" { 9030 t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff) 9031 t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs)) 9032 } 9033 }) 9034 } 9035 } 9036 9037 func TestValidateRestartPolicy(t *testing.T) { 9038 successCases := []core.RestartPolicy{ 9039 core.RestartPolicyAlways, 9040 core.RestartPolicyOnFailure, 9041 core.RestartPolicyNever, 9042 } 9043 for _, policy := range successCases { 9044 if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 { 9045 t.Errorf("expected success: %v", errs) 9046 } 9047 } 9048 9049 errorCases := []core.RestartPolicy{"", "newpolicy"} 9050 9051 for k, policy := range errorCases { 9052 if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 { 9053 t.Errorf("expected failure for %d", k) 9054 } 9055 } 9056 } 9057 9058 func TestValidateDNSPolicy(t *testing.T) { 9059 successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSClusterFirstWithHostNet, core.DNSNone} 9060 for _, policy := range successCases { 9061 if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 { 9062 t.Errorf("expected success: %v", errs) 9063 } 9064 } 9065 9066 errorCases := []core.DNSPolicy{core.DNSPolicy("invalid"), core.DNSPolicy("")} 9067 for _, policy := range errorCases { 9068 if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 { 9069 t.Errorf("expected failure for %v", policy) 9070 } 9071 } 9072 } 9073 9074 func TestValidatePodDNSConfig(t *testing.T) { 9075 generateTestSearchPathFunc := func(numChars int) string { 9076 res := "" 9077 for i := 0; i < numChars; i++ { 9078 res = res + "a" 9079 } 9080 return res 9081 } 9082 testOptionValue := "2" 9083 testDNSNone := core.DNSNone 9084 testDNSClusterFirst := core.DNSClusterFirst 9085 9086 testCases := []struct { 9087 desc string 9088 dnsConfig *core.PodDNSConfig 9089 dnsPolicy *core.DNSPolicy 9090 opts PodValidationOptions 9091 expectedError bool 9092 }{{ 9093 desc: "valid: empty DNSConfig", 9094 dnsConfig: &core.PodDNSConfig{}, 9095 expectedError: false, 9096 }, { 9097 desc: "valid: 1 option", 9098 dnsConfig: &core.PodDNSConfig{ 9099 Options: []core.PodDNSConfigOption{ 9100 {Name: "ndots", Value: &testOptionValue}, 9101 }, 9102 }, 9103 expectedError: false, 9104 }, { 9105 desc: "valid: 1 nameserver", 9106 dnsConfig: &core.PodDNSConfig{ 9107 Nameservers: []string{"127.0.0.1"}, 9108 }, 9109 expectedError: false, 9110 }, { 9111 desc: "valid: DNSNone with 1 nameserver", 9112 dnsConfig: &core.PodDNSConfig{ 9113 Nameservers: []string{"127.0.0.1"}, 9114 }, 9115 dnsPolicy: &testDNSNone, 9116 expectedError: false, 9117 }, { 9118 desc: "valid: 1 search path", 9119 dnsConfig: &core.PodDNSConfig{ 9120 Searches: []string{"custom"}, 9121 }, 9122 expectedError: false, 9123 }, { 9124 desc: "valid: 1 search path with trailing period", 9125 dnsConfig: &core.PodDNSConfig{ 9126 Searches: []string{"custom."}, 9127 }, 9128 expectedError: false, 9129 }, { 9130 desc: "valid: 3 nameservers and 6 search paths(legacy)", 9131 dnsConfig: &core.PodDNSConfig{ 9132 Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, 9133 Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."}, 9134 }, 9135 expectedError: false, 9136 }, { 9137 desc: "valid: 3 nameservers and 32 search paths", 9138 dnsConfig: &core.PodDNSConfig{ 9139 Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"}, 9140 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"}, 9141 }, 9142 expectedError: false, 9143 }, { 9144 desc: "valid: 256 characters in search path list(legacy)", 9145 dnsConfig: &core.PodDNSConfig{ 9146 // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. 9147 Searches: []string{ 9148 generateTestSearchPathFunc(1), 9149 generateTestSearchPathFunc(50), 9150 generateTestSearchPathFunc(50), 9151 generateTestSearchPathFunc(50), 9152 generateTestSearchPathFunc(50), 9153 generateTestSearchPathFunc(50), 9154 }, 9155 }, 9156 expectedError: false, 9157 }, { 9158 desc: "valid: 2048 characters in search path list", 9159 dnsConfig: &core.PodDNSConfig{ 9160 // We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths. 9161 Searches: []string{ 9162 generateTestSearchPathFunc(64), 9163 generateTestSearchPathFunc(63), 9164 generateTestSearchPathFunc(63), 9165 generateTestSearchPathFunc(63), 9166 generateTestSearchPathFunc(63), 9167 generateTestSearchPathFunc(63), 9168 generateTestSearchPathFunc(63), 9169 generateTestSearchPathFunc(63), 9170 generateTestSearchPathFunc(63), 9171 generateTestSearchPathFunc(63), 9172 generateTestSearchPathFunc(63), 9173 generateTestSearchPathFunc(63), 9174 generateTestSearchPathFunc(63), 9175 generateTestSearchPathFunc(63), 9176 generateTestSearchPathFunc(63), 9177 generateTestSearchPathFunc(63), 9178 generateTestSearchPathFunc(63), 9179 generateTestSearchPathFunc(63), 9180 generateTestSearchPathFunc(63), 9181 generateTestSearchPathFunc(63), 9182 generateTestSearchPathFunc(63), 9183 generateTestSearchPathFunc(63), 9184 generateTestSearchPathFunc(63), 9185 generateTestSearchPathFunc(63), 9186 generateTestSearchPathFunc(63), 9187 generateTestSearchPathFunc(63), 9188 generateTestSearchPathFunc(63), 9189 generateTestSearchPathFunc(63), 9190 generateTestSearchPathFunc(63), 9191 generateTestSearchPathFunc(63), 9192 generateTestSearchPathFunc(63), 9193 generateTestSearchPathFunc(63), 9194 }, 9195 }, 9196 expectedError: false, 9197 }, { 9198 desc: "valid: ipv6 nameserver", 9199 dnsConfig: &core.PodDNSConfig{ 9200 Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"}, 9201 }, 9202 expectedError: false, 9203 }, { 9204 desc: "invalid: 4 nameservers", 9205 dnsConfig: &core.PodDNSConfig{ 9206 Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"}, 9207 }, 9208 expectedError: true, 9209 }, { 9210 desc: "valid: 7 search paths", 9211 dnsConfig: &core.PodDNSConfig{ 9212 Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"}, 9213 }, 9214 }, { 9215 desc: "invalid: 33 search paths", 9216 dnsConfig: &core.PodDNSConfig{ 9217 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"}, 9218 }, 9219 expectedError: true, 9220 }, { 9221 desc: "valid: 257 characters in search path list", 9222 dnsConfig: &core.PodDNSConfig{ 9223 // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths. 9224 Searches: []string{ 9225 generateTestSearchPathFunc(2), 9226 generateTestSearchPathFunc(50), 9227 generateTestSearchPathFunc(50), 9228 generateTestSearchPathFunc(50), 9229 generateTestSearchPathFunc(50), 9230 generateTestSearchPathFunc(50), 9231 }, 9232 }, 9233 }, { 9234 desc: "invalid: 2049 characters in search path list", 9235 dnsConfig: &core.PodDNSConfig{ 9236 // We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths. 9237 Searches: []string{ 9238 generateTestSearchPathFunc(65), 9239 generateTestSearchPathFunc(63), 9240 generateTestSearchPathFunc(63), 9241 generateTestSearchPathFunc(63), 9242 generateTestSearchPathFunc(63), 9243 generateTestSearchPathFunc(63), 9244 generateTestSearchPathFunc(63), 9245 generateTestSearchPathFunc(63), 9246 generateTestSearchPathFunc(63), 9247 generateTestSearchPathFunc(63), 9248 generateTestSearchPathFunc(63), 9249 generateTestSearchPathFunc(63), 9250 generateTestSearchPathFunc(63), 9251 generateTestSearchPathFunc(63), 9252 generateTestSearchPathFunc(63), 9253 generateTestSearchPathFunc(63), 9254 generateTestSearchPathFunc(63), 9255 generateTestSearchPathFunc(63), 9256 generateTestSearchPathFunc(63), 9257 generateTestSearchPathFunc(63), 9258 generateTestSearchPathFunc(63), 9259 generateTestSearchPathFunc(63), 9260 generateTestSearchPathFunc(63), 9261 generateTestSearchPathFunc(63), 9262 generateTestSearchPathFunc(63), 9263 generateTestSearchPathFunc(63), 9264 generateTestSearchPathFunc(63), 9265 generateTestSearchPathFunc(63), 9266 generateTestSearchPathFunc(63), 9267 generateTestSearchPathFunc(63), 9268 generateTestSearchPathFunc(63), 9269 generateTestSearchPathFunc(63), 9270 }, 9271 }, 9272 expectedError: true, 9273 }, { 9274 desc: "invalid search path", 9275 dnsConfig: &core.PodDNSConfig{ 9276 Searches: []string{"custom?"}, 9277 }, 9278 expectedError: true, 9279 }, { 9280 desc: "invalid nameserver", 9281 dnsConfig: &core.PodDNSConfig{ 9282 Nameservers: []string{"invalid"}, 9283 }, 9284 expectedError: true, 9285 }, { 9286 desc: "invalid empty option name", 9287 dnsConfig: &core.PodDNSConfig{ 9288 Options: []core.PodDNSConfigOption{ 9289 {Value: &testOptionValue}, 9290 }, 9291 }, 9292 expectedError: true, 9293 }, { 9294 desc: "invalid: DNSNone with 0 nameserver", 9295 dnsConfig: &core.PodDNSConfig{ 9296 Searches: []string{"custom"}, 9297 }, 9298 dnsPolicy: &testDNSNone, 9299 expectedError: true, 9300 }, 9301 } 9302 9303 for _, tc := range testCases { 9304 if tc.dnsPolicy == nil { 9305 tc.dnsPolicy = &testDNSClusterFirst 9306 } 9307 9308 errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"), tc.opts) 9309 if len(errs) != 0 && !tc.expectedError { 9310 t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs) 9311 } else if len(errs) == 0 && tc.expectedError { 9312 t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig) 9313 } 9314 } 9315 } 9316 9317 func TestValidatePodReadinessGates(t *testing.T) { 9318 successCases := []struct { 9319 desc string 9320 readinessGates []core.PodReadinessGate 9321 }{{ 9322 "no gate", 9323 []core.PodReadinessGate{}, 9324 }, { 9325 "one readiness gate", 9326 []core.PodReadinessGate{{ 9327 ConditionType: core.PodConditionType("example.com/condition"), 9328 }}, 9329 }, { 9330 "two readiness gates", 9331 []core.PodReadinessGate{{ 9332 ConditionType: core.PodConditionType("example.com/condition1"), 9333 }, { 9334 ConditionType: core.PodConditionType("example.com/condition2"), 9335 }}, 9336 }, 9337 } 9338 for _, tc := range successCases { 9339 if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 { 9340 t.Errorf("expect tc %q to success: %v", tc.desc, errs) 9341 } 9342 } 9343 9344 errorCases := []struct { 9345 desc string 9346 readinessGates []core.PodReadinessGate 9347 }{{ 9348 "invalid condition type", 9349 []core.PodReadinessGate{{ 9350 ConditionType: core.PodConditionType("invalid/condition/type"), 9351 }}, 9352 }, 9353 } 9354 for _, tc := range errorCases { 9355 if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 { 9356 t.Errorf("expected tc %q to fail", tc.desc) 9357 } 9358 } 9359 } 9360 9361 func TestValidatePodConditions(t *testing.T) { 9362 successCases := []struct { 9363 desc string 9364 podConditions []core.PodCondition 9365 }{{ 9366 "no condition", 9367 []core.PodCondition{}, 9368 }, { 9369 "one system condition", 9370 []core.PodCondition{{ 9371 Type: core.PodReady, 9372 Status: core.ConditionTrue, 9373 }}, 9374 }, { 9375 "one system condition and one custom condition", 9376 []core.PodCondition{{ 9377 Type: core.PodReady, 9378 Status: core.ConditionTrue, 9379 }, { 9380 Type: core.PodConditionType("example.com/condition"), 9381 Status: core.ConditionFalse, 9382 }}, 9383 }, { 9384 "two custom condition", 9385 []core.PodCondition{{ 9386 Type: core.PodConditionType("foobar"), 9387 Status: core.ConditionTrue, 9388 }, { 9389 Type: core.PodConditionType("example.com/condition"), 9390 Status: core.ConditionFalse, 9391 }}, 9392 }, 9393 } 9394 9395 for _, tc := range successCases { 9396 if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 { 9397 t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs) 9398 } 9399 } 9400 9401 errorCases := []struct { 9402 desc string 9403 podConditions []core.PodCondition 9404 }{{ 9405 "one system condition and a invalid custom condition", 9406 []core.PodCondition{{ 9407 Type: core.PodReady, 9408 Status: core.ConditionStatus("True"), 9409 }, { 9410 Type: core.PodConditionType("invalid/custom/condition"), 9411 Status: core.ConditionStatus("True"), 9412 }}, 9413 }, 9414 } 9415 for _, tc := range errorCases { 9416 if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 { 9417 t.Errorf("expected tc %q to fail", tc.desc) 9418 } 9419 } 9420 } 9421 9422 func TestValidatePodSpec(t *testing.T) { 9423 activeDeadlineSeconds := int64(30) 9424 activeDeadlineSecondsMax := int64(math.MaxInt32) 9425 9426 minUserID := int64(0) 9427 maxUserID := int64(2147483647) 9428 minGroupID := int64(0) 9429 maxGroupID := int64(2147483647) 9430 goodfsGroupChangePolicy := core.FSGroupChangeAlways 9431 badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid") 9432 badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("") 9433 9434 successCases := map[string]core.PodSpec{ 9435 "populate basic fields, leave defaults for most": { 9436 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9437 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9438 RestartPolicy: core.RestartPolicyAlways, 9439 DNSPolicy: core.DNSClusterFirst, 9440 }, 9441 "populate all fields": { 9442 Volumes: []core.Volume{ 9443 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 9444 }, 9445 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9446 InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9447 RestartPolicy: core.RestartPolicyAlways, 9448 NodeSelector: map[string]string{ 9449 "key": "value", 9450 }, 9451 NodeName: "foobar", 9452 DNSPolicy: core.DNSClusterFirst, 9453 ActiveDeadlineSeconds: &activeDeadlineSeconds, 9454 ServiceAccountName: "acct", 9455 }, 9456 "populate all fields with larger active deadline": { 9457 Volumes: []core.Volume{ 9458 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 9459 }, 9460 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9461 InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9462 RestartPolicy: core.RestartPolicyAlways, 9463 NodeSelector: map[string]string{ 9464 "key": "value", 9465 }, 9466 NodeName: "foobar", 9467 DNSPolicy: core.DNSClusterFirst, 9468 ActiveDeadlineSeconds: &activeDeadlineSecondsMax, 9469 ServiceAccountName: "acct", 9470 }, 9471 "populate HostNetwork": { 9472 Containers: []core.Container{ 9473 {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 9474 Ports: []core.ContainerPort{ 9475 {HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}}, 9476 }, 9477 }, 9478 SecurityContext: &core.PodSecurityContext{ 9479 HostNetwork: true, 9480 }, 9481 RestartPolicy: core.RestartPolicyAlways, 9482 DNSPolicy: core.DNSClusterFirst, 9483 }, 9484 "populate RunAsUser SupplementalGroups FSGroup with minID 0": { 9485 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9486 SecurityContext: &core.PodSecurityContext{ 9487 SupplementalGroups: []int64{minGroupID}, 9488 RunAsUser: &minUserID, 9489 FSGroup: &minGroupID, 9490 }, 9491 RestartPolicy: core.RestartPolicyAlways, 9492 DNSPolicy: core.DNSClusterFirst, 9493 }, 9494 "populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": { 9495 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9496 SecurityContext: &core.PodSecurityContext{ 9497 SupplementalGroups: []int64{maxGroupID}, 9498 RunAsUser: &maxUserID, 9499 FSGroup: &maxGroupID, 9500 }, 9501 RestartPolicy: core.RestartPolicyAlways, 9502 DNSPolicy: core.DNSClusterFirst, 9503 }, 9504 "populate HostIPC": { 9505 SecurityContext: &core.PodSecurityContext{ 9506 HostIPC: true, 9507 }, 9508 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9509 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9510 RestartPolicy: core.RestartPolicyAlways, 9511 DNSPolicy: core.DNSClusterFirst, 9512 }, 9513 "populate HostPID": { 9514 SecurityContext: &core.PodSecurityContext{ 9515 HostPID: true, 9516 }, 9517 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9518 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9519 RestartPolicy: core.RestartPolicyAlways, 9520 DNSPolicy: core.DNSClusterFirst, 9521 }, 9522 "populate Affinity": { 9523 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9524 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9525 RestartPolicy: core.RestartPolicyAlways, 9526 DNSPolicy: core.DNSClusterFirst, 9527 }, 9528 "populate HostAliases": { 9529 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}}, 9530 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9531 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9532 RestartPolicy: core.RestartPolicyAlways, 9533 DNSPolicy: core.DNSClusterFirst, 9534 }, 9535 "populate HostAliases with `foo.bar` hostnames": { 9536 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, 9537 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9538 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9539 RestartPolicy: core.RestartPolicyAlways, 9540 DNSPolicy: core.DNSClusterFirst, 9541 }, 9542 "populate HostAliases with HostNetwork": { 9543 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, 9544 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9545 SecurityContext: &core.PodSecurityContext{ 9546 HostNetwork: true, 9547 }, 9548 RestartPolicy: core.RestartPolicyAlways, 9549 DNSPolicy: core.DNSClusterFirst, 9550 }, 9551 "populate PriorityClassName": { 9552 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9553 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9554 RestartPolicy: core.RestartPolicyAlways, 9555 DNSPolicy: core.DNSClusterFirst, 9556 PriorityClassName: "valid-name", 9557 }, 9558 "populate ShareProcessNamespace": { 9559 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9560 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9561 RestartPolicy: core.RestartPolicyAlways, 9562 DNSPolicy: core.DNSClusterFirst, 9563 SecurityContext: &core.PodSecurityContext{ 9564 ShareProcessNamespace: &[]bool{true}[0], 9565 }, 9566 }, 9567 "populate RuntimeClassName": { 9568 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9569 RestartPolicy: core.RestartPolicyAlways, 9570 DNSPolicy: core.DNSClusterFirst, 9571 RuntimeClassName: utilpointer.String("valid-sandbox"), 9572 }, 9573 "populate Overhead": { 9574 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9575 RestartPolicy: core.RestartPolicyAlways, 9576 DNSPolicy: core.DNSClusterFirst, 9577 RuntimeClassName: utilpointer.String("valid-sandbox"), 9578 Overhead: core.ResourceList{}, 9579 }, 9580 "populate DNSPolicy": { 9581 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9582 SecurityContext: &core.PodSecurityContext{ 9583 FSGroupChangePolicy: &goodfsGroupChangePolicy, 9584 }, 9585 RestartPolicy: core.RestartPolicyAlways, 9586 DNSPolicy: core.DNSClusterFirst, 9587 }, 9588 } 9589 for k, v := range successCases { 9590 t.Run(k, func(t *testing.T) { 9591 opts := PodValidationOptions{ 9592 ResourceIsPod: true, 9593 } 9594 if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) != 0 { 9595 t.Errorf("expected success: %v", errs) 9596 } 9597 }) 9598 } 9599 9600 activeDeadlineSeconds = int64(0) 9601 activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1) 9602 9603 minUserID = int64(-1) 9604 maxUserID = int64(2147483648) 9605 minGroupID = int64(-1) 9606 maxGroupID = int64(2147483648) 9607 9608 failureCases := map[string]core.PodSpec{ 9609 "bad volume": { 9610 Volumes: []core.Volume{{}}, 9611 RestartPolicy: core.RestartPolicyAlways, 9612 DNSPolicy: core.DNSClusterFirst, 9613 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9614 }, 9615 "no containers": { 9616 RestartPolicy: core.RestartPolicyAlways, 9617 DNSPolicy: core.DNSClusterFirst, 9618 }, 9619 "bad container": { 9620 Containers: []core.Container{{}}, 9621 RestartPolicy: core.RestartPolicyAlways, 9622 DNSPolicy: core.DNSClusterFirst, 9623 }, 9624 "bad init container": { 9625 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9626 InitContainers: []core.Container{{}}, 9627 RestartPolicy: core.RestartPolicyAlways, 9628 DNSPolicy: core.DNSClusterFirst, 9629 }, 9630 "bad DNS policy": { 9631 DNSPolicy: core.DNSPolicy("invalid"), 9632 RestartPolicy: core.RestartPolicyAlways, 9633 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9634 }, 9635 "bad service account name": { 9636 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9637 RestartPolicy: core.RestartPolicyAlways, 9638 DNSPolicy: core.DNSClusterFirst, 9639 ServiceAccountName: "invalidName", 9640 }, 9641 "bad restart policy": { 9642 RestartPolicy: "UnknowPolicy", 9643 DNSPolicy: core.DNSClusterFirst, 9644 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9645 }, 9646 "with hostNetwork hostPort unspecified": { 9647 Containers: []core.Container{ 9648 {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{ 9649 {HostPort: 0, ContainerPort: 2600, Protocol: "TCP"}}, 9650 }, 9651 }, 9652 SecurityContext: &core.PodSecurityContext{ 9653 HostNetwork: true, 9654 }, 9655 RestartPolicy: core.RestartPolicyAlways, 9656 DNSPolicy: core.DNSClusterFirst, 9657 }, 9658 "with hostNetwork hostPort not equal to containerPort": { 9659 Containers: []core.Container{ 9660 {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{ 9661 {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, 9662 }, 9663 }, 9664 SecurityContext: &core.PodSecurityContext{ 9665 HostNetwork: true, 9666 }, 9667 RestartPolicy: core.RestartPolicyAlways, 9668 DNSPolicy: core.DNSClusterFirst, 9669 }, 9670 "with hostAliases with invalid IP": { 9671 SecurityContext: &core.PodSecurityContext{ 9672 HostNetwork: false, 9673 }, 9674 HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}}, 9675 }, 9676 "with hostAliases with invalid hostname": { 9677 SecurityContext: &core.PodSecurityContext{ 9678 HostNetwork: false, 9679 }, 9680 HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}}, 9681 }, 9682 "bad supplementalGroups large than math.MaxInt32": { 9683 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9684 SecurityContext: &core.PodSecurityContext{ 9685 SupplementalGroups: []int64{maxGroupID, 1234}, 9686 }, 9687 RestartPolicy: core.RestartPolicyAlways, 9688 DNSPolicy: core.DNSClusterFirst, 9689 }, 9690 "bad supplementalGroups less than 0": { 9691 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9692 SecurityContext: &core.PodSecurityContext{ 9693 SupplementalGroups: []int64{minGroupID, 1234}, 9694 }, 9695 RestartPolicy: core.RestartPolicyAlways, 9696 DNSPolicy: core.DNSClusterFirst, 9697 }, 9698 "bad runAsUser large than math.MaxInt32": { 9699 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9700 SecurityContext: &core.PodSecurityContext{ 9701 RunAsUser: &maxUserID, 9702 }, 9703 RestartPolicy: core.RestartPolicyAlways, 9704 DNSPolicy: core.DNSClusterFirst, 9705 }, 9706 "bad runAsUser less than 0": { 9707 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9708 SecurityContext: &core.PodSecurityContext{ 9709 RunAsUser: &minUserID, 9710 }, 9711 RestartPolicy: core.RestartPolicyAlways, 9712 DNSPolicy: core.DNSClusterFirst, 9713 }, 9714 "bad fsGroup large than math.MaxInt32": { 9715 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9716 SecurityContext: &core.PodSecurityContext{ 9717 FSGroup: &maxGroupID, 9718 }, 9719 RestartPolicy: core.RestartPolicyAlways, 9720 DNSPolicy: core.DNSClusterFirst, 9721 }, 9722 "bad fsGroup less than 0": { 9723 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9724 SecurityContext: &core.PodSecurityContext{ 9725 FSGroup: &minGroupID, 9726 }, 9727 RestartPolicy: core.RestartPolicyAlways, 9728 DNSPolicy: core.DNSClusterFirst, 9729 }, 9730 "bad-active-deadline-seconds": { 9731 Volumes: []core.Volume{ 9732 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 9733 }, 9734 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9735 RestartPolicy: core.RestartPolicyAlways, 9736 NodeSelector: map[string]string{ 9737 "key": "value", 9738 }, 9739 NodeName: "foobar", 9740 DNSPolicy: core.DNSClusterFirst, 9741 ActiveDeadlineSeconds: &activeDeadlineSeconds, 9742 }, 9743 "active-deadline-seconds-too-large": { 9744 Volumes: []core.Volume{ 9745 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 9746 }, 9747 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9748 RestartPolicy: core.RestartPolicyAlways, 9749 NodeSelector: map[string]string{ 9750 "key": "value", 9751 }, 9752 NodeName: "foobar", 9753 DNSPolicy: core.DNSClusterFirst, 9754 ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge, 9755 }, 9756 "bad nodeName": { 9757 NodeName: "node name", 9758 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9759 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9760 RestartPolicy: core.RestartPolicyAlways, 9761 DNSPolicy: core.DNSClusterFirst, 9762 }, 9763 "bad PriorityClassName": { 9764 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9765 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9766 RestartPolicy: core.RestartPolicyAlways, 9767 DNSPolicy: core.DNSClusterFirst, 9768 PriorityClassName: "InvalidName", 9769 }, 9770 "ShareProcessNamespace and HostPID both set": { 9771 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9772 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9773 RestartPolicy: core.RestartPolicyAlways, 9774 DNSPolicy: core.DNSClusterFirst, 9775 SecurityContext: &core.PodSecurityContext{ 9776 HostPID: true, 9777 ShareProcessNamespace: &[]bool{true}[0], 9778 }, 9779 }, 9780 "bad RuntimeClassName": { 9781 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9782 RestartPolicy: core.RestartPolicyAlways, 9783 DNSPolicy: core.DNSClusterFirst, 9784 RuntimeClassName: utilpointer.String("invalid/sandbox"), 9785 }, 9786 "bad empty fsGroupchangepolicy": { 9787 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9788 SecurityContext: &core.PodSecurityContext{ 9789 FSGroupChangePolicy: &badfsGroupChangePolicy2, 9790 }, 9791 RestartPolicy: core.RestartPolicyAlways, 9792 DNSPolicy: core.DNSClusterFirst, 9793 }, 9794 "bad invalid fsgroupchangepolicy": { 9795 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9796 SecurityContext: &core.PodSecurityContext{ 9797 FSGroupChangePolicy: &badfsGroupChangePolicy1, 9798 }, 9799 RestartPolicy: core.RestartPolicyAlways, 9800 DNSPolicy: core.DNSClusterFirst, 9801 }, 9802 "disallowed resources resize policy for init containers": { 9803 InitContainers: []core.Container{{ 9804 Name: "initctr", 9805 Image: "initimage", 9806 ResizePolicy: []core.ContainerResizePolicy{ 9807 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 9808 }, 9809 ImagePullPolicy: "IfNotPresent", 9810 TerminationMessagePolicy: "File", 9811 }}, 9812 Containers: []core.Container{{ 9813 Name: "ctr", 9814 Image: "image", 9815 ResizePolicy: []core.ContainerResizePolicy{ 9816 {ResourceName: "cpu", RestartPolicy: "NotRequired"}, 9817 }, 9818 ImagePullPolicy: "IfNotPresent", 9819 TerminationMessagePolicy: "File", 9820 }}, 9821 RestartPolicy: core.RestartPolicyAlways, 9822 DNSPolicy: core.DNSClusterFirst, 9823 }, 9824 } 9825 for k, v := range failureCases { 9826 opts := PodValidationOptions{ 9827 ResourceIsPod: true, 9828 } 9829 if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) == 0 { 9830 t.Errorf("expected failure for %q", k) 9831 } 9832 } 9833 } 9834 9835 func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec { 9836 var out core.PodSpec 9837 out.Containers = in.Containers 9838 out.RestartPolicy = in.RestartPolicy 9839 out.DNSPolicy = in.DNSPolicy 9840 out.Tolerations = tolerations 9841 return out 9842 } 9843 9844 func TestValidatePod(t *testing.T) { 9845 validPodSpec := func(affinity *core.Affinity) core.PodSpec { 9846 spec := core.PodSpec{ 9847 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9848 RestartPolicy: core.RestartPolicyAlways, 9849 DNSPolicy: core.DNSClusterFirst, 9850 } 9851 if affinity != nil { 9852 spec.Affinity = affinity 9853 } 9854 return spec 9855 } 9856 validPVCSpec := core.PersistentVolumeClaimSpec{ 9857 AccessModes: []core.PersistentVolumeAccessMode{ 9858 core.ReadWriteOnce, 9859 }, 9860 Resources: core.VolumeResourceRequirements{ 9861 Requests: core.ResourceList{ 9862 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 9863 }, 9864 }, 9865 } 9866 validPVCTemplate := core.PersistentVolumeClaimTemplate{ 9867 Spec: validPVCSpec, 9868 } 9869 longPodName := strings.Repeat("a", 200) 9870 longVolName := strings.Repeat("b", 60) 9871 9872 successCases := map[string]core.Pod{ 9873 "basic fields": { 9874 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 9875 Spec: core.PodSpec{ 9876 Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, 9877 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9878 RestartPolicy: core.RestartPolicyAlways, 9879 DNSPolicy: core.DNSClusterFirst, 9880 }, 9881 }, 9882 "just about everything": { 9883 ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, 9884 Spec: core.PodSpec{ 9885 Volumes: []core.Volume{ 9886 {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, 9887 }, 9888 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 9889 RestartPolicy: core.RestartPolicyAlways, 9890 DNSPolicy: core.DNSClusterFirst, 9891 NodeSelector: map[string]string{ 9892 "key": "value", 9893 }, 9894 NodeName: "foobar", 9895 }, 9896 }, 9897 "serialized node affinity requirements": { 9898 ObjectMeta: metav1.ObjectMeta{ 9899 Name: "123", 9900 Namespace: "ns", 9901 }, 9902 Spec: validPodSpec( 9903 // TODO: Uncomment and move this block and move inside NodeAffinity once 9904 // RequiredDuringSchedulingRequiredDuringExecution is implemented 9905 // RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{ 9906 // NodeSelectorTerms: []core.NodeSelectorTerm{ 9907 // { 9908 // MatchExpressions: []core.NodeSelectorRequirement{ 9909 // { 9910 // Key: "key1", 9911 // Operator: core.NodeSelectorOpExists 9912 // }, 9913 // }, 9914 // }, 9915 // }, 9916 // }, 9917 &core.Affinity{ 9918 NodeAffinity: &core.NodeAffinity{ 9919 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 9920 NodeSelectorTerms: []core.NodeSelectorTerm{{ 9921 MatchExpressions: []core.NodeSelectorRequirement{{ 9922 Key: "key2", 9923 Operator: core.NodeSelectorOpIn, 9924 Values: []string{"value1", "value2"}, 9925 }}, 9926 MatchFields: []core.NodeSelectorRequirement{{ 9927 Key: "metadata.name", 9928 Operator: core.NodeSelectorOpIn, 9929 Values: []string{"host1"}, 9930 }}, 9931 }}, 9932 }, 9933 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 9934 Weight: 10, 9935 Preference: core.NodeSelectorTerm{ 9936 MatchExpressions: []core.NodeSelectorRequirement{{ 9937 Key: "foo", 9938 Operator: core.NodeSelectorOpIn, 9939 Values: []string{"bar"}, 9940 }}, 9941 }, 9942 }}, 9943 }, 9944 }, 9945 ), 9946 }, 9947 "serialized node affinity requirements, II": { 9948 ObjectMeta: metav1.ObjectMeta{ 9949 Name: "123", 9950 Namespace: "ns", 9951 }, 9952 Spec: validPodSpec( 9953 // TODO: Uncomment and move this block and move inside NodeAffinity once 9954 // RequiredDuringSchedulingRequiredDuringExecution is implemented 9955 // RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{ 9956 // NodeSelectorTerms: []core.NodeSelectorTerm{ 9957 // { 9958 // MatchExpressions: []core.NodeSelectorRequirement{ 9959 // { 9960 // Key: "key1", 9961 // Operator: core.NodeSelectorOpExists 9962 // }, 9963 // }, 9964 // }, 9965 // }, 9966 // }, 9967 &core.Affinity{ 9968 NodeAffinity: &core.NodeAffinity{ 9969 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 9970 NodeSelectorTerms: []core.NodeSelectorTerm{{ 9971 MatchExpressions: []core.NodeSelectorRequirement{}, 9972 }}, 9973 }, 9974 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 9975 Weight: 10, 9976 Preference: core.NodeSelectorTerm{ 9977 MatchExpressions: []core.NodeSelectorRequirement{}, 9978 }, 9979 }}, 9980 }, 9981 }, 9982 ), 9983 }, 9984 "serialized pod affinity in affinity requirements in annotations": { 9985 ObjectMeta: metav1.ObjectMeta{ 9986 Name: "123", 9987 Namespace: "ns", 9988 // TODO: Uncomment and move this block into Annotations map once 9989 // RequiredDuringSchedulingRequiredDuringExecution is implemented 9990 // "requiredDuringSchedulingRequiredDuringExecution": [{ 9991 // "labelSelector": { 9992 // "matchExpressions": [{ 9993 // "key": "key2", 9994 // "operator": "In", 9995 // "values": ["value1", "value2"] 9996 // }] 9997 // }, 9998 // "namespaces":["ns"], 9999 // "topologyKey": "zone" 10000 // }] 10001 }, 10002 Spec: validPodSpec(&core.Affinity{ 10003 PodAffinity: &core.PodAffinity{ 10004 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ 10005 LabelSelector: &metav1.LabelSelector{ 10006 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10007 Key: "key2", 10008 Operator: metav1.LabelSelectorOpIn, 10009 Values: []string{"value1", "value2"}, 10010 }}, 10011 }, 10012 TopologyKey: "zone", 10013 Namespaces: []string{"ns"}, 10014 NamespaceSelector: &metav1.LabelSelector{ 10015 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10016 Key: "key", 10017 Operator: metav1.LabelSelectorOpIn, 10018 Values: []string{"value1", "value2"}, 10019 }}, 10020 }, 10021 }}, 10022 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 10023 Weight: 10, 10024 PodAffinityTerm: core.PodAffinityTerm{ 10025 LabelSelector: &metav1.LabelSelector{ 10026 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10027 Key: "key2", 10028 Operator: metav1.LabelSelectorOpNotIn, 10029 Values: []string{"value1", "value2"}, 10030 }}, 10031 }, 10032 Namespaces: []string{"ns"}, 10033 TopologyKey: "region", 10034 }, 10035 }}, 10036 }, 10037 }), 10038 }, 10039 "serialized pod anti affinity with different Label Operators in affinity requirements in annotations": { 10040 ObjectMeta: metav1.ObjectMeta{ 10041 Name: "123", 10042 Namespace: "ns", 10043 // TODO: Uncomment and move this block into Annotations map once 10044 // RequiredDuringSchedulingRequiredDuringExecution is implemented 10045 // "requiredDuringSchedulingRequiredDuringExecution": [{ 10046 // "labelSelector": { 10047 // "matchExpressions": [{ 10048 // "key": "key2", 10049 // "operator": "In", 10050 // "values": ["value1", "value2"] 10051 // }] 10052 // }, 10053 // "namespaces":["ns"], 10054 // "topologyKey": "zone" 10055 // }] 10056 }, 10057 Spec: validPodSpec(&core.Affinity{ 10058 PodAntiAffinity: &core.PodAntiAffinity{ 10059 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ 10060 LabelSelector: &metav1.LabelSelector{ 10061 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10062 Key: "key2", 10063 Operator: metav1.LabelSelectorOpExists, 10064 }}, 10065 }, 10066 TopologyKey: "zone", 10067 Namespaces: []string{"ns"}, 10068 }}, 10069 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 10070 Weight: 10, 10071 PodAffinityTerm: core.PodAffinityTerm{ 10072 LabelSelector: &metav1.LabelSelector{ 10073 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10074 Key: "key2", 10075 Operator: metav1.LabelSelectorOpDoesNotExist, 10076 }}, 10077 }, 10078 Namespaces: []string{"ns"}, 10079 TopologyKey: "region", 10080 }, 10081 }}, 10082 }, 10083 }), 10084 }, 10085 "populate forgiveness tolerations with exists operator in annotations.": { 10086 ObjectMeta: metav1.ObjectMeta{ 10087 Name: "123", 10088 Namespace: "ns", 10089 }, 10090 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), 10091 }, 10092 "populate forgiveness tolerations with equal operator in annotations.": { 10093 ObjectMeta: metav1.ObjectMeta{ 10094 Name: "123", 10095 Namespace: "ns", 10096 }, 10097 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), 10098 }, 10099 "populate tolerations equal operator in annotations.": { 10100 ObjectMeta: metav1.ObjectMeta{ 10101 Name: "123", 10102 Namespace: "ns", 10103 }, 10104 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), 10105 }, 10106 "populate tolerations exists operator in annotations.": { 10107 ObjectMeta: metav1.ObjectMeta{ 10108 Name: "123", 10109 Namespace: "ns", 10110 }, 10111 Spec: validPodSpec(nil), 10112 }, 10113 "empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": { 10114 ObjectMeta: metav1.ObjectMeta{ 10115 Name: "123", 10116 Namespace: "ns", 10117 }, 10118 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}), 10119 }, 10120 "empty operator is OK for toleration, defaults to Equal.": { 10121 ObjectMeta: metav1.ObjectMeta{ 10122 Name: "123", 10123 Namespace: "ns", 10124 }, 10125 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}), 10126 }, 10127 "empty effect is OK for toleration, empty toleration effect means match all taint effects.": { 10128 ObjectMeta: metav1.ObjectMeta{ 10129 Name: "123", 10130 Namespace: "ns", 10131 }, 10132 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}), 10133 }, 10134 "negative tolerationSeconds is OK for toleration.": { 10135 ObjectMeta: metav1.ObjectMeta{ 10136 Name: "pod-forgiveness-invalid", 10137 Namespace: "ns", 10138 }, 10139 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}), 10140 }, 10141 "runtime default seccomp profile": { 10142 ObjectMeta: metav1.ObjectMeta{ 10143 Name: "123", 10144 Namespace: "ns", 10145 Annotations: map[string]string{ 10146 core.SeccompPodAnnotationKey: core.SeccompProfileRuntimeDefault, 10147 }, 10148 }, 10149 Spec: validPodSpec(nil), 10150 }, 10151 "docker default seccomp profile": { 10152 ObjectMeta: metav1.ObjectMeta{ 10153 Name: "123", 10154 Namespace: "ns", 10155 Annotations: map[string]string{ 10156 core.SeccompPodAnnotationKey: core.DeprecatedSeccompProfileDockerDefault, 10157 }, 10158 }, 10159 Spec: validPodSpec(nil), 10160 }, 10161 "unconfined seccomp profile": { 10162 ObjectMeta: metav1.ObjectMeta{ 10163 Name: "123", 10164 Namespace: "ns", 10165 Annotations: map[string]string{ 10166 core.SeccompPodAnnotationKey: "unconfined", 10167 }, 10168 }, 10169 Spec: validPodSpec(nil), 10170 }, 10171 "localhost seccomp profile": { 10172 ObjectMeta: metav1.ObjectMeta{ 10173 Name: "123", 10174 Namespace: "ns", 10175 Annotations: map[string]string{ 10176 core.SeccompPodAnnotationKey: "localhost/foo", 10177 }, 10178 }, 10179 Spec: validPodSpec(nil), 10180 }, 10181 "localhost seccomp profile for a container": { 10182 ObjectMeta: metav1.ObjectMeta{ 10183 Name: "123", 10184 Namespace: "ns", 10185 Annotations: map[string]string{ 10186 core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo", 10187 }, 10188 }, 10189 Spec: validPodSpec(nil), 10190 }, 10191 "runtime default seccomp profile for a pod": { 10192 ObjectMeta: metav1.ObjectMeta{ 10193 Name: "123", 10194 Namespace: "ns", 10195 }, 10196 Spec: core.PodSpec{ 10197 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10198 RestartPolicy: core.RestartPolicyAlways, 10199 DNSPolicy: core.DNSDefault, 10200 SecurityContext: &core.PodSecurityContext{ 10201 SeccompProfile: &core.SeccompProfile{ 10202 Type: core.SeccompProfileTypeRuntimeDefault, 10203 }, 10204 }, 10205 }, 10206 }, 10207 "runtime default seccomp profile for a container": { 10208 ObjectMeta: metav1.ObjectMeta{ 10209 Name: "123", 10210 Namespace: "ns", 10211 }, 10212 Spec: core.PodSpec{ 10213 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 10214 SecurityContext: &core.SecurityContext{ 10215 SeccompProfile: &core.SeccompProfile{ 10216 Type: core.SeccompProfileTypeRuntimeDefault, 10217 }, 10218 }, 10219 }}, 10220 RestartPolicy: core.RestartPolicyAlways, 10221 DNSPolicy: core.DNSDefault, 10222 }, 10223 }, 10224 "unconfined seccomp profile for a pod": { 10225 ObjectMeta: metav1.ObjectMeta{ 10226 Name: "123", 10227 Namespace: "ns", 10228 }, 10229 Spec: core.PodSpec{ 10230 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10231 RestartPolicy: core.RestartPolicyAlways, 10232 DNSPolicy: core.DNSDefault, 10233 SecurityContext: &core.PodSecurityContext{ 10234 SeccompProfile: &core.SeccompProfile{ 10235 Type: core.SeccompProfileTypeUnconfined, 10236 }, 10237 }, 10238 }, 10239 }, 10240 "unconfined seccomp profile for a container": { 10241 ObjectMeta: metav1.ObjectMeta{ 10242 Name: "123", 10243 Namespace: "ns", 10244 }, 10245 Spec: core.PodSpec{ 10246 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 10247 SecurityContext: &core.SecurityContext{ 10248 SeccompProfile: &core.SeccompProfile{ 10249 Type: core.SeccompProfileTypeUnconfined, 10250 }, 10251 }, 10252 }}, 10253 RestartPolicy: core.RestartPolicyAlways, 10254 DNSPolicy: core.DNSDefault, 10255 }, 10256 }, 10257 "localhost seccomp profile for a pod": { 10258 ObjectMeta: metav1.ObjectMeta{ 10259 Name: "123", 10260 Namespace: "ns", 10261 }, 10262 Spec: core.PodSpec{ 10263 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10264 RestartPolicy: core.RestartPolicyAlways, 10265 DNSPolicy: core.DNSDefault, 10266 SecurityContext: &core.PodSecurityContext{ 10267 SeccompProfile: &core.SeccompProfile{ 10268 Type: core.SeccompProfileTypeLocalhost, 10269 LocalhostProfile: utilpointer.String("filename.json"), 10270 }, 10271 }, 10272 }, 10273 }, 10274 "localhost seccomp profile for a container, II": { 10275 ObjectMeta: metav1.ObjectMeta{ 10276 Name: "123", 10277 Namespace: "ns", 10278 }, 10279 Spec: core.PodSpec{ 10280 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 10281 SecurityContext: &core.SecurityContext{ 10282 SeccompProfile: &core.SeccompProfile{ 10283 Type: core.SeccompProfileTypeLocalhost, 10284 LocalhostProfile: utilpointer.String("filename.json"), 10285 }, 10286 }, 10287 }}, 10288 RestartPolicy: core.RestartPolicyAlways, 10289 DNSPolicy: core.DNSDefault, 10290 }, 10291 }, 10292 "default AppArmor profile for a container": { 10293 ObjectMeta: metav1.ObjectMeta{ 10294 Name: "123", 10295 Namespace: "ns", 10296 Annotations: map[string]string{ 10297 v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileRuntimeDefault, 10298 }, 10299 }, 10300 Spec: validPodSpec(nil), 10301 }, 10302 "default AppArmor profile for an init container": { 10303 ObjectMeta: metav1.ObjectMeta{ 10304 Name: "123", 10305 Namespace: "ns", 10306 Annotations: map[string]string{ 10307 v1.AppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.AppArmorBetaProfileRuntimeDefault, 10308 }, 10309 }, 10310 Spec: core.PodSpec{ 10311 InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10312 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10313 RestartPolicy: core.RestartPolicyAlways, 10314 DNSPolicy: core.DNSClusterFirst, 10315 }, 10316 }, 10317 "localhost AppArmor profile for a container": { 10318 ObjectMeta: metav1.ObjectMeta{ 10319 Name: "123", 10320 Namespace: "ns", 10321 Annotations: map[string]string{ 10322 v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileNamePrefix + "foo", 10323 }, 10324 }, 10325 Spec: validPodSpec(nil), 10326 }, 10327 "syntactically valid sysctls": { 10328 ObjectMeta: metav1.ObjectMeta{ 10329 Name: "123", 10330 Namespace: "ns", 10331 }, 10332 Spec: core.PodSpec{ 10333 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10334 RestartPolicy: core.RestartPolicyAlways, 10335 DNSPolicy: core.DNSClusterFirst, 10336 SecurityContext: &core.PodSecurityContext{ 10337 Sysctls: []core.Sysctl{{ 10338 Name: "kernel.shmmni", 10339 Value: "32768", 10340 }, { 10341 Name: "kernel.shmmax", 10342 Value: "1000000000", 10343 }, { 10344 Name: "knet.ipv4.route.min_pmtu", 10345 Value: "1000", 10346 }}, 10347 }, 10348 }, 10349 }, 10350 "valid extended resources for init container": { 10351 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 10352 Spec: core.PodSpec{ 10353 InitContainers: []core.Container{{ 10354 Name: "valid-extended", 10355 Image: "image", 10356 ImagePullPolicy: "IfNotPresent", 10357 Resources: core.ResourceRequirements{ 10358 Requests: core.ResourceList{ 10359 core.ResourceName("example.com/a"): resource.MustParse("10"), 10360 }, 10361 Limits: core.ResourceList{ 10362 core.ResourceName("example.com/a"): resource.MustParse("10"), 10363 }, 10364 }, 10365 TerminationMessagePolicy: "File", 10366 }}, 10367 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10368 RestartPolicy: core.RestartPolicyAlways, 10369 DNSPolicy: core.DNSClusterFirst, 10370 }, 10371 }, 10372 "valid extended resources for regular container": { 10373 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 10374 Spec: core.PodSpec{ 10375 InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10376 Containers: []core.Container{{ 10377 Name: "valid-extended", 10378 Image: "image", 10379 ImagePullPolicy: "IfNotPresent", 10380 Resources: core.ResourceRequirements{ 10381 Requests: core.ResourceList{ 10382 core.ResourceName("example.com/a"): resource.MustParse("10"), 10383 }, 10384 Limits: core.ResourceList{ 10385 core.ResourceName("example.com/a"): resource.MustParse("10"), 10386 }, 10387 }, 10388 TerminationMessagePolicy: "File", 10389 }}, 10390 RestartPolicy: core.RestartPolicyAlways, 10391 DNSPolicy: core.DNSClusterFirst, 10392 }, 10393 }, 10394 "valid serviceaccount token projected volume with serviceaccount name specified": { 10395 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 10396 Spec: core.PodSpec{ 10397 ServiceAccountName: "some-service-account", 10398 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10399 RestartPolicy: core.RestartPolicyAlways, 10400 DNSPolicy: core.DNSClusterFirst, 10401 Volumes: []core.Volume{{ 10402 Name: "projected-volume", 10403 VolumeSource: core.VolumeSource{ 10404 Projected: &core.ProjectedVolumeSource{ 10405 Sources: []core.VolumeProjection{{ 10406 ServiceAccountToken: &core.ServiceAccountTokenProjection{ 10407 Audience: "foo-audience", 10408 ExpirationSeconds: 6000, 10409 Path: "foo-path", 10410 }, 10411 }}, 10412 }, 10413 }, 10414 }}, 10415 }, 10416 }, 10417 "valid ClusterTrustBundlePEM projected volume referring to a CTB by name": { 10418 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 10419 Spec: core.PodSpec{ 10420 ServiceAccountName: "some-service-account", 10421 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10422 RestartPolicy: core.RestartPolicyAlways, 10423 DNSPolicy: core.DNSClusterFirst, 10424 Volumes: []core.Volume{ 10425 { 10426 Name: "projected-volume", 10427 VolumeSource: core.VolumeSource{ 10428 Projected: &core.ProjectedVolumeSource{ 10429 Sources: []core.VolumeProjection{ 10430 { 10431 ClusterTrustBundle: &core.ClusterTrustBundleProjection{ 10432 Path: "foo-path", 10433 Name: utilpointer.String("foo"), 10434 }, 10435 }, 10436 }, 10437 }, 10438 }, 10439 }, 10440 }, 10441 }, 10442 }, 10443 "valid ClusterTrustBundlePEM projected volume referring to a CTB by signer name": { 10444 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 10445 Spec: core.PodSpec{ 10446 ServiceAccountName: "some-service-account", 10447 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10448 RestartPolicy: core.RestartPolicyAlways, 10449 DNSPolicy: core.DNSClusterFirst, 10450 Volumes: []core.Volume{ 10451 { 10452 Name: "projected-volume", 10453 VolumeSource: core.VolumeSource{ 10454 Projected: &core.ProjectedVolumeSource{ 10455 Sources: []core.VolumeProjection{ 10456 { 10457 ClusterTrustBundle: &core.ClusterTrustBundleProjection{ 10458 Path: "foo-path", 10459 SignerName: utilpointer.String("example.com/foo"), 10460 LabelSelector: &metav1.LabelSelector{ 10461 MatchLabels: map[string]string{ 10462 "version": "live", 10463 }, 10464 }, 10465 }, 10466 }, 10467 }, 10468 }, 10469 }, 10470 }, 10471 }, 10472 }, 10473 }, 10474 "ephemeral volume + PVC, no conflict between them": { 10475 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 10476 Spec: core.PodSpec{ 10477 Volumes: []core.Volume{ 10478 {Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}}, 10479 {Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}}, 10480 }, 10481 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10482 RestartPolicy: core.RestartPolicyAlways, 10483 DNSPolicy: core.DNSClusterFirst, 10484 }, 10485 }, 10486 "negative pod-deletion-cost": { 10487 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "-100"}}, 10488 Spec: core.PodSpec{ 10489 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10490 RestartPolicy: core.RestartPolicyAlways, 10491 DNSPolicy: core.DNSClusterFirst, 10492 }, 10493 }, 10494 "positive pod-deletion-cost": { 10495 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "100"}}, 10496 Spec: core.PodSpec{ 10497 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10498 RestartPolicy: core.RestartPolicyAlways, 10499 DNSPolicy: core.DNSClusterFirst, 10500 }, 10501 }, 10502 "MatchLabelKeys/MismatchLabelKeys in required PodAffinity": { 10503 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 10504 Spec: core.PodSpec{ 10505 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10506 RestartPolicy: core.RestartPolicyAlways, 10507 DNSPolicy: core.DNSClusterFirst, 10508 Affinity: &core.Affinity{ 10509 PodAffinity: &core.PodAffinity{ 10510 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 10511 { 10512 LabelSelector: &metav1.LabelSelector{ 10513 MatchExpressions: []metav1.LabelSelectorRequirement{ 10514 { 10515 Key: "key", 10516 Operator: metav1.LabelSelectorOpNotIn, 10517 Values: []string{"value1", "value2"}, 10518 }, 10519 { 10520 Key: "key2", 10521 Operator: metav1.LabelSelectorOpIn, 10522 Values: []string{"value1"}, 10523 }, 10524 { 10525 Key: "key3", 10526 Operator: metav1.LabelSelectorOpNotIn, 10527 Values: []string{"value1"}, 10528 }, 10529 }, 10530 }, 10531 TopologyKey: "k8s.io/zone", 10532 MatchLabelKeys: []string{"key2"}, 10533 MismatchLabelKeys: []string{"key3"}, 10534 }, 10535 }, 10536 }, 10537 }, 10538 }, 10539 }, 10540 "MatchLabelKeys/MismatchLabelKeys in preferred PodAffinity": { 10541 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 10542 Spec: core.PodSpec{ 10543 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10544 RestartPolicy: core.RestartPolicyAlways, 10545 DNSPolicy: core.DNSClusterFirst, 10546 Affinity: &core.Affinity{ 10547 PodAffinity: &core.PodAffinity{ 10548 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 10549 { 10550 Weight: 10, 10551 PodAffinityTerm: core.PodAffinityTerm{ 10552 LabelSelector: &metav1.LabelSelector{ 10553 MatchExpressions: []metav1.LabelSelectorRequirement{ 10554 { 10555 Key: "key", 10556 Operator: metav1.LabelSelectorOpNotIn, 10557 Values: []string{"value1", "value2"}, 10558 }, 10559 { 10560 Key: "key2", 10561 Operator: metav1.LabelSelectorOpIn, 10562 Values: []string{"value1"}, 10563 }, 10564 { 10565 Key: "key3", 10566 Operator: metav1.LabelSelectorOpNotIn, 10567 Values: []string{"value1"}, 10568 }, 10569 }, 10570 }, 10571 TopologyKey: "k8s.io/zone", 10572 MatchLabelKeys: []string{"key2"}, 10573 MismatchLabelKeys: []string{"key3"}, 10574 }, 10575 }, 10576 }, 10577 }, 10578 }, 10579 }, 10580 }, 10581 "MatchLabelKeys/MismatchLabelKeys in required PodAntiAffinity": { 10582 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 10583 Spec: core.PodSpec{ 10584 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10585 RestartPolicy: core.RestartPolicyAlways, 10586 DNSPolicy: core.DNSClusterFirst, 10587 Affinity: &core.Affinity{ 10588 PodAntiAffinity: &core.PodAntiAffinity{ 10589 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 10590 { 10591 LabelSelector: &metav1.LabelSelector{ 10592 MatchExpressions: []metav1.LabelSelectorRequirement{ 10593 { 10594 Key: "key", 10595 Operator: metav1.LabelSelectorOpNotIn, 10596 Values: []string{"value1", "value2"}, 10597 }, 10598 { 10599 Key: "key2", 10600 Operator: metav1.LabelSelectorOpIn, 10601 Values: []string{"value1"}, 10602 }, 10603 { 10604 Key: "key3", 10605 Operator: metav1.LabelSelectorOpNotIn, 10606 Values: []string{"value1"}, 10607 }, 10608 }, 10609 }, 10610 TopologyKey: "k8s.io/zone", 10611 MatchLabelKeys: []string{"key2"}, 10612 MismatchLabelKeys: []string{"key3"}, 10613 }, 10614 }, 10615 }, 10616 }, 10617 }, 10618 }, 10619 "MatchLabelKeys/MismatchLabelKeys in preferred PodAntiAffinity": { 10620 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 10621 Spec: core.PodSpec{ 10622 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10623 RestartPolicy: core.RestartPolicyAlways, 10624 DNSPolicy: core.DNSClusterFirst, 10625 Affinity: &core.Affinity{ 10626 PodAntiAffinity: &core.PodAntiAffinity{ 10627 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 10628 { 10629 Weight: 10, 10630 PodAffinityTerm: core.PodAffinityTerm{ 10631 LabelSelector: &metav1.LabelSelector{ 10632 MatchExpressions: []metav1.LabelSelectorRequirement{ 10633 { 10634 Key: "key", 10635 Operator: metav1.LabelSelectorOpNotIn, 10636 Values: []string{"value1", "value2"}, 10637 }, 10638 { 10639 Key: "key2", 10640 Operator: metav1.LabelSelectorOpIn, 10641 Values: []string{"value1"}, 10642 }, 10643 { 10644 Key: "key3", 10645 Operator: metav1.LabelSelectorOpNotIn, 10646 Values: []string{"value1"}, 10647 }, 10648 }, 10649 }, 10650 TopologyKey: "k8s.io/zone", 10651 MatchLabelKeys: []string{"key2"}, 10652 MismatchLabelKeys: []string{"key3"}, 10653 }, 10654 }, 10655 }, 10656 }, 10657 }, 10658 }, 10659 }, 10660 "LabelSelector can have the same key as MismatchLabelKeys": { 10661 // Note: On the contrary, in case of matchLabelKeys, keys in matchLabelKeys are not allowed to be specified in labelSelector by users. 10662 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 10663 Spec: core.PodSpec{ 10664 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10665 RestartPolicy: core.RestartPolicyAlways, 10666 DNSPolicy: core.DNSClusterFirst, 10667 Affinity: &core.Affinity{ 10668 PodAffinity: &core.PodAffinity{ 10669 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 10670 { 10671 LabelSelector: &metav1.LabelSelector{ 10672 MatchExpressions: []metav1.LabelSelectorRequirement{ 10673 { 10674 Key: "key", 10675 Operator: metav1.LabelSelectorOpNotIn, 10676 Values: []string{"value1", "value2"}, 10677 }, 10678 { 10679 // This is the same key as in MismatchLabelKeys 10680 // but it's allowed. 10681 Key: "key2", 10682 Operator: metav1.LabelSelectorOpIn, 10683 Values: []string{"value1"}, 10684 }, 10685 { 10686 Key: "key2", 10687 Operator: metav1.LabelSelectorOpNotIn, 10688 Values: []string{"value1"}, 10689 }, 10690 }, 10691 }, 10692 TopologyKey: "k8s.io/zone", 10693 MismatchLabelKeys: []string{"key2"}, 10694 }, 10695 }, 10696 }, 10697 }, 10698 }, 10699 }, 10700 } 10701 10702 for k, v := range successCases { 10703 t.Run(k, func(t *testing.T) { 10704 if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 { 10705 t.Errorf("expected success: %v", errs) 10706 } 10707 }) 10708 } 10709 10710 errorCases := map[string]struct { 10711 spec core.Pod 10712 expectedError string 10713 }{ 10714 "bad name": { 10715 expectedError: "metadata.name", 10716 spec: core.Pod{ 10717 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"}, 10718 Spec: core.PodSpec{ 10719 RestartPolicy: core.RestartPolicyAlways, 10720 DNSPolicy: core.DNSClusterFirst, 10721 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10722 }, 10723 }, 10724 }, 10725 "image whitespace": { 10726 expectedError: "spec.containers[0].image", 10727 spec: core.Pod{ 10728 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, 10729 Spec: core.PodSpec{ 10730 RestartPolicy: core.RestartPolicyAlways, 10731 DNSPolicy: core.DNSClusterFirst, 10732 Containers: []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10733 }, 10734 }, 10735 }, 10736 "image leading and trailing whitespace": { 10737 expectedError: "spec.containers[0].image", 10738 spec: core.Pod{ 10739 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, 10740 Spec: core.PodSpec{ 10741 RestartPolicy: core.RestartPolicyAlways, 10742 DNSPolicy: core.DNSClusterFirst, 10743 Containers: []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10744 }, 10745 }, 10746 }, 10747 "bad namespace": { 10748 expectedError: "metadata.namespace", 10749 spec: core.Pod{ 10750 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, 10751 Spec: core.PodSpec{ 10752 RestartPolicy: core.RestartPolicyAlways, 10753 DNSPolicy: core.DNSClusterFirst, 10754 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10755 }, 10756 }, 10757 }, 10758 "bad spec": { 10759 expectedError: "spec.containers[0].name", 10760 spec: core.Pod{ 10761 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, 10762 Spec: core.PodSpec{ 10763 Containers: []core.Container{{}}, 10764 }, 10765 }, 10766 }, 10767 "bad label": { 10768 expectedError: "NoUppercaseOrSpecialCharsLike=Equals", 10769 spec: core.Pod{ 10770 ObjectMeta: metav1.ObjectMeta{ 10771 Name: "abc", 10772 Namespace: "ns", 10773 Labels: map[string]string{ 10774 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 10775 }, 10776 }, 10777 Spec: core.PodSpec{ 10778 RestartPolicy: core.RestartPolicyAlways, 10779 DNSPolicy: core.DNSClusterFirst, 10780 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 10781 }, 10782 }, 10783 }, 10784 "invalid node selector requirement in node affinity, operator can't be null": { 10785 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator", 10786 spec: core.Pod{ 10787 ObjectMeta: metav1.ObjectMeta{ 10788 Name: "123", 10789 Namespace: "ns", 10790 }, 10791 Spec: validPodSpec(&core.Affinity{ 10792 NodeAffinity: &core.NodeAffinity{ 10793 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 10794 NodeSelectorTerms: []core.NodeSelectorTerm{{ 10795 MatchExpressions: []core.NodeSelectorRequirement{{ 10796 Key: "key1", 10797 }}, 10798 }}, 10799 }, 10800 }, 10801 }), 10802 }, 10803 }, 10804 "invalid node selector requirement in node affinity, key is invalid": { 10805 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key", 10806 spec: core.Pod{ 10807 ObjectMeta: metav1.ObjectMeta{ 10808 Name: "123", 10809 Namespace: "ns", 10810 }, 10811 Spec: validPodSpec(&core.Affinity{ 10812 NodeAffinity: &core.NodeAffinity{ 10813 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 10814 NodeSelectorTerms: []core.NodeSelectorTerm{{ 10815 MatchExpressions: []core.NodeSelectorRequirement{{ 10816 Key: "invalid key ___@#", 10817 Operator: core.NodeSelectorOpExists, 10818 }}, 10819 }}, 10820 }, 10821 }, 10822 }), 10823 }, 10824 }, 10825 "invalid node field selector requirement in node affinity, more values for field selector": { 10826 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values", 10827 spec: core.Pod{ 10828 ObjectMeta: metav1.ObjectMeta{ 10829 Name: "123", 10830 Namespace: "ns", 10831 }, 10832 Spec: validPodSpec(&core.Affinity{ 10833 NodeAffinity: &core.NodeAffinity{ 10834 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 10835 NodeSelectorTerms: []core.NodeSelectorTerm{{ 10836 MatchFields: []core.NodeSelectorRequirement{{ 10837 Key: "metadata.name", 10838 Operator: core.NodeSelectorOpIn, 10839 Values: []string{"host1", "host2"}, 10840 }}, 10841 }}, 10842 }, 10843 }, 10844 }), 10845 }, 10846 }, 10847 "invalid node field selector requirement in node affinity, invalid operator": { 10848 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].operator", 10849 spec: core.Pod{ 10850 ObjectMeta: metav1.ObjectMeta{ 10851 Name: "123", 10852 Namespace: "ns", 10853 }, 10854 Spec: validPodSpec(&core.Affinity{ 10855 NodeAffinity: &core.NodeAffinity{ 10856 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 10857 NodeSelectorTerms: []core.NodeSelectorTerm{{ 10858 MatchFields: []core.NodeSelectorRequirement{{ 10859 Key: "metadata.name", 10860 Operator: core.NodeSelectorOpExists, 10861 }}, 10862 }}, 10863 }, 10864 }, 10865 }), 10866 }, 10867 }, 10868 "invalid node field selector requirement in node affinity, invalid key": { 10869 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key", 10870 spec: core.Pod{ 10871 ObjectMeta: metav1.ObjectMeta{ 10872 Name: "123", 10873 Namespace: "ns", 10874 }, 10875 Spec: validPodSpec(&core.Affinity{ 10876 NodeAffinity: &core.NodeAffinity{ 10877 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 10878 NodeSelectorTerms: []core.NodeSelectorTerm{{ 10879 MatchFields: []core.NodeSelectorRequirement{{ 10880 Key: "metadata.namespace", 10881 Operator: core.NodeSelectorOpIn, 10882 Values: []string{"ns1"}, 10883 }}, 10884 }}, 10885 }, 10886 }, 10887 }), 10888 }, 10889 }, 10890 "invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": { 10891 expectedError: "must be in the range 1-100", 10892 spec: core.Pod{ 10893 ObjectMeta: metav1.ObjectMeta{ 10894 Name: "123", 10895 Namespace: "ns", 10896 }, 10897 Spec: validPodSpec(&core.Affinity{ 10898 NodeAffinity: &core.NodeAffinity{ 10899 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 10900 Weight: 199, 10901 Preference: core.NodeSelectorTerm{ 10902 MatchExpressions: []core.NodeSelectorRequirement{{ 10903 Key: "foo", 10904 Operator: core.NodeSelectorOpIn, 10905 Values: []string{"bar"}, 10906 }}, 10907 }, 10908 }}, 10909 }, 10910 }), 10911 }, 10912 }, 10913 "invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": { 10914 expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms", 10915 spec: core.Pod{ 10916 ObjectMeta: metav1.ObjectMeta{ 10917 Name: "123", 10918 Namespace: "ns", 10919 }, 10920 Spec: validPodSpec(&core.Affinity{ 10921 NodeAffinity: &core.NodeAffinity{ 10922 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 10923 NodeSelectorTerms: []core.NodeSelectorTerm{}, 10924 }, 10925 }, 10926 }), 10927 }, 10928 }, 10929 "invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": { 10930 expectedError: "must be in the range 1-100", 10931 spec: core.Pod{ 10932 ObjectMeta: metav1.ObjectMeta{ 10933 Name: "123", 10934 Namespace: "ns", 10935 }, 10936 Spec: validPodSpec(&core.Affinity{ 10937 PodAffinity: &core.PodAffinity{ 10938 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 10939 Weight: 109, 10940 PodAffinityTerm: core.PodAffinityTerm{ 10941 LabelSelector: &metav1.LabelSelector{ 10942 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10943 Key: "key2", 10944 Operator: metav1.LabelSelectorOpNotIn, 10945 Values: []string{"value1", "value2"}, 10946 }}, 10947 }, 10948 Namespaces: []string{"ns"}, 10949 TopologyKey: "region", 10950 }, 10951 }}, 10952 }, 10953 }), 10954 }, 10955 }, 10956 "invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": { 10957 expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values", 10958 spec: core.Pod{ 10959 ObjectMeta: metav1.ObjectMeta{ 10960 Name: "123", 10961 Namespace: "ns", 10962 }, 10963 Spec: validPodSpec(&core.Affinity{ 10964 PodAntiAffinity: &core.PodAntiAffinity{ 10965 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 10966 Weight: 10, 10967 PodAffinityTerm: core.PodAffinityTerm{ 10968 LabelSelector: &metav1.LabelSelector{ 10969 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10970 Key: "key2", 10971 Operator: metav1.LabelSelectorOpExists, 10972 Values: []string{"value1", "value2"}, 10973 }}, 10974 }, 10975 Namespaces: []string{"ns"}, 10976 TopologyKey: "region", 10977 }, 10978 }}, 10979 }, 10980 }), 10981 }, 10982 }, 10983 "invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, In operator must include Values": { 10984 expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values", 10985 spec: core.Pod{ 10986 ObjectMeta: metav1.ObjectMeta{ 10987 Name: "123", 10988 Namespace: "ns", 10989 }, 10990 Spec: validPodSpec(&core.Affinity{ 10991 PodAntiAffinity: &core.PodAntiAffinity{ 10992 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 10993 Weight: 10, 10994 PodAffinityTerm: core.PodAffinityTerm{ 10995 NamespaceSelector: &metav1.LabelSelector{ 10996 MatchExpressions: []metav1.LabelSelectorRequirement{{ 10997 Key: "key2", 10998 Operator: metav1.LabelSelectorOpIn, 10999 }}, 11000 }, 11001 Namespaces: []string{"ns"}, 11002 TopologyKey: "region", 11003 }, 11004 }}, 11005 }, 11006 }), 11007 }, 11008 }, 11009 "invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, Exists operator can not have values": { 11010 expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values", 11011 spec: core.Pod{ 11012 ObjectMeta: metav1.ObjectMeta{ 11013 Name: "123", 11014 Namespace: "ns", 11015 }, 11016 Spec: validPodSpec(&core.Affinity{ 11017 PodAntiAffinity: &core.PodAntiAffinity{ 11018 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 11019 Weight: 10, 11020 PodAffinityTerm: core.PodAffinityTerm{ 11021 NamespaceSelector: &metav1.LabelSelector{ 11022 MatchExpressions: []metav1.LabelSelectorRequirement{{ 11023 Key: "key2", 11024 Operator: metav1.LabelSelectorOpExists, 11025 Values: []string{"value1", "value2"}, 11026 }}, 11027 }, 11028 Namespaces: []string{"ns"}, 11029 TopologyKey: "region", 11030 }, 11031 }}, 11032 }, 11033 }), 11034 }, 11035 }, 11036 "invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, namespace should be valid": { 11037 expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace", 11038 spec: core.Pod{ 11039 ObjectMeta: metav1.ObjectMeta{ 11040 Name: "123", 11041 Namespace: "ns", 11042 }, 11043 Spec: validPodSpec(&core.Affinity{ 11044 PodAffinity: &core.PodAffinity{ 11045 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 11046 Weight: 10, 11047 PodAffinityTerm: core.PodAffinityTerm{ 11048 LabelSelector: &metav1.LabelSelector{ 11049 MatchExpressions: []metav1.LabelSelectorRequirement{{ 11050 Key: "key2", 11051 Operator: metav1.LabelSelectorOpExists, 11052 }}, 11053 }, 11054 Namespaces: []string{"INVALID_NAMESPACE"}, 11055 TopologyKey: "region", 11056 }, 11057 }}, 11058 }, 11059 }), 11060 }, 11061 }, 11062 "invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": { 11063 expectedError: "can not be empty", 11064 spec: core.Pod{ 11065 ObjectMeta: metav1.ObjectMeta{ 11066 Name: "123", 11067 Namespace: "ns", 11068 }, 11069 Spec: validPodSpec(&core.Affinity{ 11070 PodAffinity: &core.PodAffinity{ 11071 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ 11072 LabelSelector: &metav1.LabelSelector{ 11073 MatchExpressions: []metav1.LabelSelectorRequirement{{ 11074 Key: "key2", 11075 Operator: metav1.LabelSelectorOpIn, 11076 Values: []string{"value1", "value2"}, 11077 }}, 11078 }, 11079 Namespaces: []string{"ns"}, 11080 }}, 11081 }, 11082 }), 11083 }, 11084 }, 11085 "invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": { 11086 expectedError: "can not be empty", 11087 spec: core.Pod{ 11088 ObjectMeta: metav1.ObjectMeta{ 11089 Name: "123", 11090 Namespace: "ns", 11091 }, 11092 Spec: validPodSpec(&core.Affinity{ 11093 PodAntiAffinity: &core.PodAntiAffinity{ 11094 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{ 11095 LabelSelector: &metav1.LabelSelector{ 11096 MatchExpressions: []metav1.LabelSelectorRequirement{{ 11097 Key: "key2", 11098 Operator: metav1.LabelSelectorOpIn, 11099 Values: []string{"value1", "value2"}, 11100 }}, 11101 }, 11102 Namespaces: []string{"ns"}, 11103 }}, 11104 }, 11105 }), 11106 }, 11107 }, 11108 "invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": { 11109 expectedError: "can not be empty", 11110 spec: core.Pod{ 11111 ObjectMeta: metav1.ObjectMeta{ 11112 Name: "123", 11113 Namespace: "ns", 11114 }, 11115 Spec: validPodSpec(&core.Affinity{ 11116 PodAffinity: &core.PodAffinity{ 11117 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 11118 Weight: 10, 11119 PodAffinityTerm: core.PodAffinityTerm{ 11120 LabelSelector: &metav1.LabelSelector{ 11121 MatchExpressions: []metav1.LabelSelectorRequirement{{ 11122 Key: "key2", 11123 Operator: metav1.LabelSelectorOpNotIn, 11124 Values: []string{"value1", "value2"}, 11125 }}, 11126 }, 11127 Namespaces: []string{"ns"}, 11128 }, 11129 }}, 11130 }, 11131 }), 11132 }, 11133 }, 11134 "invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": { 11135 expectedError: "can not be empty", 11136 spec: core.Pod{ 11137 ObjectMeta: metav1.ObjectMeta{ 11138 Name: "123", 11139 Namespace: "ns", 11140 }, 11141 Spec: validPodSpec(&core.Affinity{ 11142 PodAntiAffinity: &core.PodAntiAffinity{ 11143 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{ 11144 Weight: 10, 11145 PodAffinityTerm: core.PodAffinityTerm{ 11146 LabelSelector: &metav1.LabelSelector{ 11147 MatchExpressions: []metav1.LabelSelectorRequirement{{ 11148 Key: "key2", 11149 Operator: metav1.LabelSelectorOpNotIn, 11150 Values: []string{"value1", "value2"}, 11151 }}, 11152 }, 11153 Namespaces: []string{"ns"}, 11154 }, 11155 }}, 11156 }, 11157 }), 11158 }, 11159 }, 11160 "invalid soft pod affinity, key in MatchLabelKeys isn't correctly defined": { 11161 expectedError: "prefix part must be non-empty", 11162 spec: core.Pod{ 11163 ObjectMeta: metav1.ObjectMeta{ 11164 Name: "123", 11165 Namespace: "ns", 11166 }, 11167 Spec: validPodSpec(&core.Affinity{ 11168 PodAffinity: &core.PodAffinity{ 11169 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11170 { 11171 Weight: 10, 11172 PodAffinityTerm: core.PodAffinityTerm{ 11173 LabelSelector: &metav1.LabelSelector{ 11174 MatchExpressions: []metav1.LabelSelectorRequirement{ 11175 { 11176 Key: "key", 11177 Operator: metav1.LabelSelectorOpNotIn, 11178 Values: []string{"value1", "value2"}, 11179 }, 11180 }, 11181 }, 11182 TopologyKey: "k8s.io/zone", 11183 MatchLabelKeys: []string{"/simple"}, 11184 }, 11185 }, 11186 }, 11187 }, 11188 }), 11189 }, 11190 }, 11191 "invalid hard pod affinity, key in MatchLabelKeys isn't correctly defined": { 11192 expectedError: "prefix part must be non-empty", 11193 spec: core.Pod{ 11194 ObjectMeta: metav1.ObjectMeta{ 11195 Name: "123", 11196 Namespace: "ns", 11197 }, 11198 Spec: validPodSpec(&core.Affinity{ 11199 PodAffinity: &core.PodAffinity{ 11200 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11201 { 11202 LabelSelector: &metav1.LabelSelector{ 11203 MatchExpressions: []metav1.LabelSelectorRequirement{ 11204 { 11205 Key: "key", 11206 Operator: metav1.LabelSelectorOpNotIn, 11207 Values: []string{"value1", "value2"}, 11208 }, 11209 }, 11210 }, 11211 TopologyKey: "k8s.io/zone", 11212 MatchLabelKeys: []string{"/simple"}, 11213 }, 11214 }, 11215 }, 11216 }), 11217 }, 11218 }, 11219 "invalid soft pod anti-affinity, key in MatchLabelKeys isn't correctly defined": { 11220 expectedError: "prefix part must be non-empty", 11221 spec: core.Pod{ 11222 ObjectMeta: metav1.ObjectMeta{ 11223 Name: "123", 11224 Namespace: "ns", 11225 }, 11226 Spec: validPodSpec(&core.Affinity{ 11227 PodAntiAffinity: &core.PodAntiAffinity{ 11228 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11229 { 11230 Weight: 10, 11231 PodAffinityTerm: core.PodAffinityTerm{ 11232 LabelSelector: &metav1.LabelSelector{ 11233 MatchExpressions: []metav1.LabelSelectorRequirement{ 11234 { 11235 Key: "key", 11236 Operator: metav1.LabelSelectorOpNotIn, 11237 Values: []string{"value1", "value2"}, 11238 }, 11239 }, 11240 }, 11241 TopologyKey: "k8s.io/zone", 11242 MatchLabelKeys: []string{"/simple"}, 11243 }, 11244 }, 11245 }, 11246 }, 11247 }), 11248 }, 11249 }, 11250 "invalid hard pod anti-affinity, key in MatchLabelKeys isn't correctly defined": { 11251 expectedError: "prefix part must be non-empty", 11252 spec: core.Pod{ 11253 ObjectMeta: metav1.ObjectMeta{ 11254 Name: "123", 11255 Namespace: "ns", 11256 }, 11257 Spec: validPodSpec(&core.Affinity{ 11258 PodAntiAffinity: &core.PodAntiAffinity{ 11259 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11260 { 11261 LabelSelector: &metav1.LabelSelector{ 11262 MatchExpressions: []metav1.LabelSelectorRequirement{ 11263 { 11264 Key: "key", 11265 Operator: metav1.LabelSelectorOpNotIn, 11266 Values: []string{"value1", "value2"}, 11267 }, 11268 }, 11269 }, 11270 TopologyKey: "k8s.io/zone", 11271 MatchLabelKeys: []string{"/simple"}, 11272 }, 11273 }, 11274 }, 11275 }), 11276 }, 11277 }, 11278 "invalid soft pod affinity, key in MismatchLabelKeys isn't correctly defined": { 11279 expectedError: "prefix part must be non-empty", 11280 spec: core.Pod{ 11281 ObjectMeta: metav1.ObjectMeta{ 11282 Name: "123", 11283 Namespace: "ns", 11284 }, 11285 Spec: validPodSpec(&core.Affinity{ 11286 PodAffinity: &core.PodAffinity{ 11287 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11288 { 11289 Weight: 10, 11290 PodAffinityTerm: core.PodAffinityTerm{ 11291 LabelSelector: &metav1.LabelSelector{ 11292 MatchExpressions: []metav1.LabelSelectorRequirement{ 11293 { 11294 Key: "key", 11295 Operator: metav1.LabelSelectorOpNotIn, 11296 Values: []string{"value1", "value2"}, 11297 }, 11298 }, 11299 }, 11300 TopologyKey: "k8s.io/zone", 11301 MismatchLabelKeys: []string{"/simple"}, 11302 }, 11303 }, 11304 }, 11305 }, 11306 }), 11307 }, 11308 }, 11309 "invalid hard pod affinity, key in MismatchLabelKeys isn't correctly defined": { 11310 expectedError: "prefix part must be non-empty", 11311 spec: core.Pod{ 11312 ObjectMeta: metav1.ObjectMeta{ 11313 Name: "123", 11314 Namespace: "ns", 11315 }, 11316 Spec: validPodSpec(&core.Affinity{ 11317 PodAffinity: &core.PodAffinity{ 11318 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11319 { 11320 LabelSelector: &metav1.LabelSelector{ 11321 MatchExpressions: []metav1.LabelSelectorRequirement{ 11322 { 11323 Key: "key", 11324 Operator: metav1.LabelSelectorOpNotIn, 11325 Values: []string{"value1", "value2"}, 11326 }, 11327 }, 11328 }, 11329 TopologyKey: "k8s.io/zone", 11330 MismatchLabelKeys: []string{"/simple"}, 11331 }, 11332 }, 11333 }, 11334 }), 11335 }, 11336 }, 11337 "invalid soft pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": { 11338 expectedError: "prefix part must be non-empty", 11339 spec: core.Pod{ 11340 ObjectMeta: metav1.ObjectMeta{ 11341 Name: "123", 11342 Namespace: "ns", 11343 }, 11344 Spec: validPodSpec(&core.Affinity{ 11345 PodAntiAffinity: &core.PodAntiAffinity{ 11346 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11347 { 11348 Weight: 10, 11349 PodAffinityTerm: core.PodAffinityTerm{ 11350 LabelSelector: &metav1.LabelSelector{ 11351 MatchExpressions: []metav1.LabelSelectorRequirement{ 11352 { 11353 Key: "key", 11354 Operator: metav1.LabelSelectorOpNotIn, 11355 Values: []string{"value1", "value2"}, 11356 }, 11357 }, 11358 }, 11359 TopologyKey: "k8s.io/zone", 11360 MismatchLabelKeys: []string{"/simple"}, 11361 }, 11362 }, 11363 }, 11364 }, 11365 }), 11366 }, 11367 }, 11368 "invalid hard pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": { 11369 expectedError: "prefix part must be non-empty", 11370 spec: core.Pod{ 11371 ObjectMeta: metav1.ObjectMeta{ 11372 Name: "123", 11373 Namespace: "ns", 11374 }, 11375 Spec: validPodSpec(&core.Affinity{ 11376 PodAntiAffinity: &core.PodAntiAffinity{ 11377 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11378 { 11379 LabelSelector: &metav1.LabelSelector{ 11380 MatchExpressions: []metav1.LabelSelectorRequirement{ 11381 { 11382 Key: "key", 11383 Operator: metav1.LabelSelectorOpNotIn, 11384 Values: []string{"value1", "value2"}, 11385 }, 11386 }, 11387 }, 11388 TopologyKey: "k8s.io/zone", 11389 MismatchLabelKeys: []string{"/simple"}, 11390 }, 11391 }, 11392 }, 11393 }), 11394 }, 11395 }, 11396 "invalid soft pod affinity, key exists in both matchLabelKeys and labelSelector": { 11397 expectedError: "exists in both matchLabelKeys and labelSelector", 11398 spec: core.Pod{ 11399 ObjectMeta: metav1.ObjectMeta{ 11400 Name: "123", 11401 Namespace: "ns", 11402 Labels: map[string]string{"key": "value1"}, 11403 }, 11404 Spec: validPodSpec(&core.Affinity{ 11405 PodAffinity: &core.PodAffinity{ 11406 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11407 { 11408 Weight: 10, 11409 PodAffinityTerm: core.PodAffinityTerm{ 11410 LabelSelector: &metav1.LabelSelector{ 11411 MatchExpressions: []metav1.LabelSelectorRequirement{ 11412 // This one should be created from MatchLabelKeys. 11413 { 11414 Key: "key", 11415 Operator: metav1.LabelSelectorOpIn, 11416 Values: []string{"value1"}, 11417 }, 11418 { 11419 Key: "key", 11420 Operator: metav1.LabelSelectorOpNotIn, 11421 Values: []string{"value2"}, 11422 }, 11423 }, 11424 }, 11425 TopologyKey: "k8s.io/zone", 11426 MatchLabelKeys: []string{"key"}, 11427 }, 11428 }, 11429 }, 11430 }, 11431 }), 11432 }, 11433 }, 11434 "invalid hard pod affinity, key exists in both matchLabelKeys and labelSelector": { 11435 expectedError: "exists in both matchLabelKeys and labelSelector", 11436 spec: core.Pod{ 11437 ObjectMeta: metav1.ObjectMeta{ 11438 Name: "123", 11439 Namespace: "ns", 11440 Labels: map[string]string{"key": "value1"}, 11441 }, 11442 Spec: validPodSpec(&core.Affinity{ 11443 PodAffinity: &core.PodAffinity{ 11444 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11445 { 11446 LabelSelector: &metav1.LabelSelector{ 11447 MatchExpressions: []metav1.LabelSelectorRequirement{ 11448 // This one should be created from MatchLabelKeys. 11449 { 11450 Key: "key", 11451 Operator: metav1.LabelSelectorOpIn, 11452 Values: []string{"value1"}, 11453 }, 11454 { 11455 Key: "key", 11456 Operator: metav1.LabelSelectorOpNotIn, 11457 Values: []string{"value2"}, 11458 }, 11459 }, 11460 }, 11461 TopologyKey: "k8s.io/zone", 11462 MatchLabelKeys: []string{"key"}, 11463 }, 11464 }, 11465 }, 11466 }), 11467 }, 11468 }, 11469 "invalid soft pod anti-affinity, key exists in both matchLabelKeys and labelSelector": { 11470 expectedError: "exists in both matchLabelKeys and labelSelector", 11471 spec: core.Pod{ 11472 ObjectMeta: metav1.ObjectMeta{ 11473 Name: "123", 11474 Namespace: "ns", 11475 Labels: map[string]string{"key": "value1"}, 11476 }, 11477 Spec: validPodSpec(&core.Affinity{ 11478 PodAntiAffinity: &core.PodAntiAffinity{ 11479 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11480 { 11481 Weight: 10, 11482 PodAffinityTerm: core.PodAffinityTerm{ 11483 LabelSelector: &metav1.LabelSelector{ 11484 MatchExpressions: []metav1.LabelSelectorRequirement{ 11485 // This one should be created from MatchLabelKeys. 11486 { 11487 Key: "key", 11488 Operator: metav1.LabelSelectorOpIn, 11489 Values: []string{"value1"}, 11490 }, 11491 { 11492 Key: "key", 11493 Operator: metav1.LabelSelectorOpNotIn, 11494 Values: []string{"value2"}, 11495 }, 11496 }, 11497 }, 11498 TopologyKey: "k8s.io/zone", 11499 MatchLabelKeys: []string{"key"}, 11500 }, 11501 }, 11502 }, 11503 }, 11504 }), 11505 }, 11506 }, 11507 "invalid hard pod anti-affinity, key exists in both matchLabelKeys and labelSelector": { 11508 expectedError: "exists in both matchLabelKeys and labelSelector", 11509 spec: core.Pod{ 11510 ObjectMeta: metav1.ObjectMeta{ 11511 Name: "123", 11512 Namespace: "ns", 11513 Labels: map[string]string{"key": "value1"}, 11514 }, 11515 Spec: validPodSpec(&core.Affinity{ 11516 PodAntiAffinity: &core.PodAntiAffinity{ 11517 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11518 { 11519 LabelSelector: &metav1.LabelSelector{ 11520 MatchExpressions: []metav1.LabelSelectorRequirement{ 11521 // This one should be created from MatchLabelKeys. 11522 { 11523 Key: "key", 11524 Operator: metav1.LabelSelectorOpIn, 11525 Values: []string{"value1"}, 11526 }, 11527 { 11528 Key: "key", 11529 Operator: metav1.LabelSelectorOpNotIn, 11530 Values: []string{"value2"}, 11531 }, 11532 }, 11533 }, 11534 TopologyKey: "k8s.io/zone", 11535 MatchLabelKeys: []string{"key"}, 11536 }, 11537 }, 11538 }, 11539 }), 11540 }, 11541 }, 11542 "invalid soft pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": { 11543 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys", 11544 spec: core.Pod{ 11545 ObjectMeta: metav1.ObjectMeta{ 11546 Name: "123", 11547 Namespace: "ns", 11548 }, 11549 Spec: validPodSpec(&core.Affinity{ 11550 PodAffinity: &core.PodAffinity{ 11551 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11552 { 11553 Weight: 10, 11554 PodAffinityTerm: core.PodAffinityTerm{ 11555 LabelSelector: &metav1.LabelSelector{ 11556 MatchExpressions: []metav1.LabelSelectorRequirement{ 11557 { 11558 Key: "key", 11559 Operator: metav1.LabelSelectorOpNotIn, 11560 Values: []string{"value1", "value2"}, 11561 }, 11562 }, 11563 }, 11564 TopologyKey: "k8s.io/zone", 11565 MatchLabelKeys: []string{"samekey"}, 11566 MismatchLabelKeys: []string{"samekey"}, 11567 }, 11568 }, 11569 }, 11570 }, 11571 }), 11572 }, 11573 }, 11574 "invalid hard pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": { 11575 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys", 11576 spec: core.Pod{ 11577 ObjectMeta: metav1.ObjectMeta{ 11578 Name: "123", 11579 Namespace: "ns", 11580 }, 11581 Spec: validPodSpec(&core.Affinity{ 11582 PodAffinity: &core.PodAffinity{ 11583 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11584 { 11585 LabelSelector: &metav1.LabelSelector{ 11586 MatchExpressions: []metav1.LabelSelectorRequirement{ 11587 { 11588 Key: "key", 11589 Operator: metav1.LabelSelectorOpNotIn, 11590 Values: []string{"value1", "value2"}, 11591 }, 11592 }, 11593 }, 11594 TopologyKey: "k8s.io/zone", 11595 MatchLabelKeys: []string{"samekey"}, 11596 MismatchLabelKeys: []string{"samekey"}, 11597 }, 11598 }, 11599 }, 11600 }), 11601 }, 11602 }, 11603 "invalid soft pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": { 11604 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys", 11605 spec: core.Pod{ 11606 ObjectMeta: metav1.ObjectMeta{ 11607 Name: "123", 11608 Namespace: "ns", 11609 }, 11610 Spec: validPodSpec(&core.Affinity{ 11611 PodAntiAffinity: &core.PodAntiAffinity{ 11612 PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{ 11613 { 11614 Weight: 10, 11615 PodAffinityTerm: core.PodAffinityTerm{ 11616 LabelSelector: &metav1.LabelSelector{ 11617 MatchExpressions: []metav1.LabelSelectorRequirement{ 11618 { 11619 Key: "key", 11620 Operator: metav1.LabelSelectorOpNotIn, 11621 Values: []string{"value1", "value2"}, 11622 }, 11623 }, 11624 }, 11625 TopologyKey: "k8s.io/zone", 11626 MatchLabelKeys: []string{"samekey"}, 11627 MismatchLabelKeys: []string{"samekey"}, 11628 }, 11629 }, 11630 }, 11631 }, 11632 }), 11633 }, 11634 }, 11635 "invalid hard pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": { 11636 expectedError: "exists in both matchLabelKeys and mismatchLabelKeys", 11637 spec: core.Pod{ 11638 ObjectMeta: metav1.ObjectMeta{ 11639 Name: "123", 11640 Namespace: "ns", 11641 }, 11642 Spec: validPodSpec(&core.Affinity{ 11643 PodAntiAffinity: &core.PodAntiAffinity{ 11644 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 11645 { 11646 LabelSelector: &metav1.LabelSelector{ 11647 MatchExpressions: []metav1.LabelSelectorRequirement{ 11648 { 11649 Key: "key", 11650 Operator: metav1.LabelSelectorOpNotIn, 11651 Values: []string{"value1", "value2"}, 11652 }, 11653 }, 11654 }, 11655 TopologyKey: "k8s.io/zone", 11656 MatchLabelKeys: []string{"samekey"}, 11657 MismatchLabelKeys: []string{"samekey"}, 11658 }, 11659 }, 11660 }, 11661 }), 11662 }, 11663 }, 11664 "invalid toleration key": { 11665 expectedError: "spec.tolerations[0].key", 11666 spec: core.Pod{ 11667 ObjectMeta: metav1.ObjectMeta{ 11668 Name: "123", 11669 Namespace: "ns", 11670 }, 11671 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), 11672 }, 11673 }, 11674 "invalid toleration operator": { 11675 expectedError: "spec.tolerations[0].operator", 11676 spec: core.Pod{ 11677 ObjectMeta: metav1.ObjectMeta{ 11678 Name: "123", 11679 Namespace: "ns", 11680 }, 11681 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}), 11682 }, 11683 }, 11684 "value must be empty when `operator` is 'Exists'": { 11685 expectedError: "spec.tolerations[0].operator", 11686 spec: core.Pod{ 11687 ObjectMeta: metav1.ObjectMeta{ 11688 Name: "123", 11689 Namespace: "ns", 11690 }, 11691 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}), 11692 }, 11693 }, 11694 11695 "operator must be 'Exists' when `key` is empty": { 11696 expectedError: "spec.tolerations[0].operator", 11697 spec: core.Pod{ 11698 ObjectMeta: metav1.ObjectMeta{ 11699 Name: "123", 11700 Namespace: "ns", 11701 }, 11702 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), 11703 }, 11704 }, 11705 "effect must be 'NoExecute' when `TolerationSeconds` is set": { 11706 expectedError: "spec.tolerations[0].effect", 11707 spec: core.Pod{ 11708 ObjectMeta: metav1.ObjectMeta{ 11709 Name: "pod-forgiveness-invalid", 11710 Namespace: "ns", 11711 }, 11712 Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}), 11713 }, 11714 }, 11715 "must be a valid pod seccomp profile": { 11716 expectedError: "must be a valid seccomp profile", 11717 spec: core.Pod{ 11718 ObjectMeta: metav1.ObjectMeta{ 11719 Name: "123", 11720 Namespace: "ns", 11721 Annotations: map[string]string{ 11722 core.SeccompPodAnnotationKey: "foo", 11723 }, 11724 }, 11725 Spec: validPodSpec(nil), 11726 }, 11727 }, 11728 "must be a valid container seccomp profile": { 11729 expectedError: "must be a valid seccomp profile", 11730 spec: core.Pod{ 11731 ObjectMeta: metav1.ObjectMeta{ 11732 Name: "123", 11733 Namespace: "ns", 11734 Annotations: map[string]string{ 11735 core.SeccompContainerAnnotationKeyPrefix + "foo": "foo", 11736 }, 11737 }, 11738 Spec: validPodSpec(nil), 11739 }, 11740 }, 11741 "must be a non-empty container name in seccomp annotation": { 11742 expectedError: "name part must be non-empty", 11743 spec: core.Pod{ 11744 ObjectMeta: metav1.ObjectMeta{ 11745 Name: "123", 11746 Namespace: "ns", 11747 Annotations: map[string]string{ 11748 core.SeccompContainerAnnotationKeyPrefix: "foo", 11749 }, 11750 }, 11751 Spec: validPodSpec(nil), 11752 }, 11753 }, 11754 "must be a non-empty container profile in seccomp annotation": { 11755 expectedError: "must be a valid seccomp profile", 11756 spec: core.Pod{ 11757 ObjectMeta: metav1.ObjectMeta{ 11758 Name: "123", 11759 Namespace: "ns", 11760 Annotations: map[string]string{ 11761 core.SeccompContainerAnnotationKeyPrefix + "foo": "", 11762 }, 11763 }, 11764 Spec: validPodSpec(nil), 11765 }, 11766 }, 11767 "must match seccomp profile type and pod annotation": { 11768 expectedError: "seccomp type in annotation and field must match", 11769 spec: core.Pod{ 11770 ObjectMeta: metav1.ObjectMeta{ 11771 Name: "123", 11772 Namespace: "ns", 11773 Annotations: map[string]string{ 11774 core.SeccompPodAnnotationKey: "unconfined", 11775 }, 11776 }, 11777 Spec: core.PodSpec{ 11778 SecurityContext: &core.PodSecurityContext{ 11779 SeccompProfile: &core.SeccompProfile{ 11780 Type: core.SeccompProfileTypeRuntimeDefault, 11781 }, 11782 }, 11783 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 11784 RestartPolicy: core.RestartPolicyAlways, 11785 DNSPolicy: core.DNSClusterFirst, 11786 }, 11787 }, 11788 }, 11789 "must match seccomp profile type and container annotation": { 11790 expectedError: "seccomp type in annotation and field must match", 11791 spec: core.Pod{ 11792 ObjectMeta: metav1.ObjectMeta{ 11793 Name: "123", 11794 Namespace: "ns", 11795 Annotations: map[string]string{ 11796 core.SeccompContainerAnnotationKeyPrefix + "ctr": "unconfined", 11797 }, 11798 }, 11799 Spec: core.PodSpec{ 11800 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 11801 SecurityContext: &core.SecurityContext{ 11802 SeccompProfile: &core.SeccompProfile{ 11803 Type: core.SeccompProfileTypeRuntimeDefault, 11804 }, 11805 }}}, 11806 RestartPolicy: core.RestartPolicyAlways, 11807 DNSPolicy: core.DNSClusterFirst, 11808 }, 11809 }, 11810 }, 11811 "must be a relative path in a node-local seccomp profile annotation": { 11812 expectedError: "must be a relative path", 11813 spec: core.Pod{ 11814 ObjectMeta: metav1.ObjectMeta{ 11815 Name: "123", 11816 Namespace: "ns", 11817 Annotations: map[string]string{ 11818 core.SeccompPodAnnotationKey: "localhost//foo", 11819 }, 11820 }, 11821 Spec: validPodSpec(nil), 11822 }, 11823 }, 11824 "must not start with '../'": { 11825 expectedError: "must not contain '..'", 11826 spec: core.Pod{ 11827 ObjectMeta: metav1.ObjectMeta{ 11828 Name: "123", 11829 Namespace: "ns", 11830 Annotations: map[string]string{ 11831 core.SeccompPodAnnotationKey: "localhost/../foo", 11832 }, 11833 }, 11834 Spec: validPodSpec(nil), 11835 }, 11836 }, 11837 "AppArmor profile must apply to a container": { 11838 expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]", 11839 spec: core.Pod{ 11840 ObjectMeta: metav1.ObjectMeta{ 11841 Name: "123", 11842 Namespace: "ns", 11843 Annotations: map[string]string{ 11844 v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileRuntimeDefault, 11845 v1.AppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.AppArmorBetaProfileRuntimeDefault, 11846 v1.AppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.AppArmorBetaProfileRuntimeDefault, 11847 }, 11848 }, 11849 Spec: core.PodSpec{ 11850 InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 11851 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 11852 RestartPolicy: core.RestartPolicyAlways, 11853 DNSPolicy: core.DNSClusterFirst, 11854 }, 11855 }, 11856 }, 11857 "AppArmor profile format must be valid": { 11858 expectedError: "invalid AppArmor profile name", 11859 spec: core.Pod{ 11860 ObjectMeta: metav1.ObjectMeta{ 11861 Name: "123", 11862 Namespace: "ns", 11863 Annotations: map[string]string{ 11864 v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name", 11865 }, 11866 }, 11867 Spec: validPodSpec(nil), 11868 }, 11869 }, 11870 "only default AppArmor profile may start with runtime/": { 11871 expectedError: "invalid AppArmor profile name", 11872 spec: core.Pod{ 11873 ObjectMeta: metav1.ObjectMeta{ 11874 Name: "123", 11875 Namespace: "ns", 11876 Annotations: map[string]string{ 11877 v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo", 11878 }, 11879 }, 11880 Spec: validPodSpec(nil), 11881 }, 11882 }, 11883 "invalid extended resource name in container request": { 11884 expectedError: "must be a standard resource for containers", 11885 spec: core.Pod{ 11886 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 11887 Spec: core.PodSpec{ 11888 Containers: []core.Container{{ 11889 Name: "invalid", 11890 Image: "image", 11891 ImagePullPolicy: "IfNotPresent", 11892 Resources: core.ResourceRequirements{ 11893 Requests: core.ResourceList{ 11894 core.ResourceName("invalid-name"): resource.MustParse("2"), 11895 }, 11896 Limits: core.ResourceList{ 11897 core.ResourceName("invalid-name"): resource.MustParse("2"), 11898 }, 11899 }, 11900 }}, 11901 RestartPolicy: core.RestartPolicyAlways, 11902 DNSPolicy: core.DNSClusterFirst, 11903 }, 11904 }, 11905 }, 11906 "invalid extended resource requirement: request must be == limit": { 11907 expectedError: "must be equal to example.com/a", 11908 spec: core.Pod{ 11909 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 11910 Spec: core.PodSpec{ 11911 Containers: []core.Container{{ 11912 Name: "invalid", 11913 Image: "image", 11914 ImagePullPolicy: "IfNotPresent", 11915 Resources: core.ResourceRequirements{ 11916 Requests: core.ResourceList{ 11917 core.ResourceName("example.com/a"): resource.MustParse("2"), 11918 }, 11919 Limits: core.ResourceList{ 11920 core.ResourceName("example.com/a"): resource.MustParse("1"), 11921 }, 11922 }, 11923 }}, 11924 RestartPolicy: core.RestartPolicyAlways, 11925 DNSPolicy: core.DNSClusterFirst, 11926 }, 11927 }, 11928 }, 11929 "invalid extended resource requirement without limit": { 11930 expectedError: "Limit must be set", 11931 spec: core.Pod{ 11932 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 11933 Spec: core.PodSpec{ 11934 Containers: []core.Container{{ 11935 Name: "invalid", 11936 Image: "image", 11937 ImagePullPolicy: "IfNotPresent", 11938 Resources: core.ResourceRequirements{ 11939 Requests: core.ResourceList{ 11940 core.ResourceName("example.com/a"): resource.MustParse("2"), 11941 }, 11942 }, 11943 }}, 11944 RestartPolicy: core.RestartPolicyAlways, 11945 DNSPolicy: core.DNSClusterFirst, 11946 }, 11947 }, 11948 }, 11949 "invalid fractional extended resource in container request": { 11950 expectedError: "must be an integer", 11951 spec: core.Pod{ 11952 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 11953 Spec: core.PodSpec{ 11954 Containers: []core.Container{{ 11955 Name: "invalid", 11956 Image: "image", 11957 ImagePullPolicy: "IfNotPresent", 11958 Resources: core.ResourceRequirements{ 11959 Requests: core.ResourceList{ 11960 core.ResourceName("example.com/a"): resource.MustParse("500m"), 11961 }, 11962 }, 11963 }}, 11964 RestartPolicy: core.RestartPolicyAlways, 11965 DNSPolicy: core.DNSClusterFirst, 11966 }, 11967 }, 11968 }, 11969 "invalid fractional extended resource in init container request": { 11970 expectedError: "must be an integer", 11971 spec: core.Pod{ 11972 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 11973 Spec: core.PodSpec{ 11974 InitContainers: []core.Container{{ 11975 Name: "invalid", 11976 Image: "image", 11977 ImagePullPolicy: "IfNotPresent", 11978 Resources: core.ResourceRequirements{ 11979 Requests: core.ResourceList{ 11980 core.ResourceName("example.com/a"): resource.MustParse("500m"), 11981 }, 11982 }, 11983 }}, 11984 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 11985 RestartPolicy: core.RestartPolicyAlways, 11986 DNSPolicy: core.DNSClusterFirst, 11987 }, 11988 }, 11989 }, 11990 "invalid fractional extended resource in container limit": { 11991 expectedError: "must be an integer", 11992 spec: core.Pod{ 11993 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 11994 Spec: core.PodSpec{ 11995 Containers: []core.Container{{ 11996 Name: "invalid", 11997 Image: "image", 11998 ImagePullPolicy: "IfNotPresent", 11999 Resources: core.ResourceRequirements{ 12000 Requests: core.ResourceList{ 12001 core.ResourceName("example.com/a"): resource.MustParse("5"), 12002 }, 12003 Limits: core.ResourceList{ 12004 core.ResourceName("example.com/a"): resource.MustParse("2.5"), 12005 }, 12006 }, 12007 }}, 12008 RestartPolicy: core.RestartPolicyAlways, 12009 DNSPolicy: core.DNSClusterFirst, 12010 }, 12011 }, 12012 }, 12013 "invalid fractional extended resource in init container limit": { 12014 expectedError: "must be an integer", 12015 spec: core.Pod{ 12016 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 12017 Spec: core.PodSpec{ 12018 InitContainers: []core.Container{{ 12019 Name: "invalid", 12020 Image: "image", 12021 ImagePullPolicy: "IfNotPresent", 12022 Resources: core.ResourceRequirements{ 12023 Requests: core.ResourceList{ 12024 core.ResourceName("example.com/a"): resource.MustParse("2.5"), 12025 }, 12026 Limits: core.ResourceList{ 12027 core.ResourceName("example.com/a"): resource.MustParse("2.5"), 12028 }, 12029 }, 12030 }}, 12031 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12032 RestartPolicy: core.RestartPolicyAlways, 12033 DNSPolicy: core.DNSClusterFirst, 12034 }, 12035 }, 12036 }, 12037 "mirror-pod present without nodeName": { 12038 expectedError: "mirror", 12039 spec: core.Pod{ 12040 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, 12041 Spec: core.PodSpec{ 12042 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12043 RestartPolicy: core.RestartPolicyAlways, 12044 DNSPolicy: core.DNSClusterFirst, 12045 }, 12046 }, 12047 }, 12048 "mirror-pod populated without nodeName": { 12049 expectedError: "mirror", 12050 spec: core.Pod{ 12051 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, 12052 Spec: core.PodSpec{ 12053 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12054 RestartPolicy: core.RestartPolicyAlways, 12055 DNSPolicy: core.DNSClusterFirst, 12056 }, 12057 }, 12058 }, 12059 "serviceaccount token projected volume with no serviceaccount name specified": { 12060 expectedError: "must not be specified when serviceAccountName is not set", 12061 spec: core.Pod{ 12062 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 12063 Spec: core.PodSpec{ 12064 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12065 RestartPolicy: core.RestartPolicyAlways, 12066 DNSPolicy: core.DNSClusterFirst, 12067 Volumes: []core.Volume{{ 12068 Name: "projected-volume", 12069 VolumeSource: core.VolumeSource{ 12070 Projected: &core.ProjectedVolumeSource{ 12071 Sources: []core.VolumeProjection{{ 12072 ServiceAccountToken: &core.ServiceAccountTokenProjection{ 12073 Audience: "foo-audience", 12074 ExpirationSeconds: 6000, 12075 Path: "foo-path", 12076 }, 12077 }}, 12078 }, 12079 }, 12080 }}, 12081 }, 12082 }, 12083 }, 12084 "ClusterTrustBundlePEM projected volume using both byName and bySigner": { 12085 expectedError: "only one of name and signerName may be used", 12086 spec: core.Pod{ 12087 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 12088 Spec: core.PodSpec{ 12089 ServiceAccountName: "some-service-account", 12090 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12091 RestartPolicy: core.RestartPolicyAlways, 12092 DNSPolicy: core.DNSClusterFirst, 12093 Volumes: []core.Volume{ 12094 { 12095 Name: "projected-volume", 12096 VolumeSource: core.VolumeSource{ 12097 Projected: &core.ProjectedVolumeSource{ 12098 Sources: []core.VolumeProjection{ 12099 { 12100 ClusterTrustBundle: &core.ClusterTrustBundleProjection{ 12101 Path: "foo-path", 12102 SignerName: utilpointer.String("example.com/foo"), 12103 LabelSelector: &metav1.LabelSelector{ 12104 MatchLabels: map[string]string{ 12105 "version": "live", 12106 }, 12107 }, 12108 Name: utilpointer.String("foo"), 12109 }, 12110 }, 12111 }, 12112 }, 12113 }, 12114 }, 12115 }, 12116 }, 12117 }, 12118 }, 12119 "ClusterTrustBundlePEM projected volume byName with no name": { 12120 expectedError: "must be a valid object name", 12121 spec: core.Pod{ 12122 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 12123 Spec: core.PodSpec{ 12124 ServiceAccountName: "some-service-account", 12125 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12126 RestartPolicy: core.RestartPolicyAlways, 12127 DNSPolicy: core.DNSClusterFirst, 12128 Volumes: []core.Volume{ 12129 { 12130 Name: "projected-volume", 12131 VolumeSource: core.VolumeSource{ 12132 Projected: &core.ProjectedVolumeSource{ 12133 Sources: []core.VolumeProjection{ 12134 { 12135 ClusterTrustBundle: &core.ClusterTrustBundleProjection{ 12136 Path: "foo-path", 12137 Name: utilpointer.String(""), 12138 }, 12139 }, 12140 }, 12141 }, 12142 }, 12143 }, 12144 }, 12145 }, 12146 }, 12147 }, 12148 "ClusterTrustBundlePEM projected volume bySigner with no signer name": { 12149 expectedError: "must be a valid signer name", 12150 spec: core.Pod{ 12151 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 12152 Spec: core.PodSpec{ 12153 ServiceAccountName: "some-service-account", 12154 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12155 RestartPolicy: core.RestartPolicyAlways, 12156 DNSPolicy: core.DNSClusterFirst, 12157 Volumes: []core.Volume{ 12158 { 12159 Name: "projected-volume", 12160 VolumeSource: core.VolumeSource{ 12161 Projected: &core.ProjectedVolumeSource{ 12162 Sources: []core.VolumeProjection{ 12163 { 12164 ClusterTrustBundle: &core.ClusterTrustBundleProjection{ 12165 Path: "foo-path", 12166 SignerName: utilpointer.String(""), 12167 LabelSelector: &metav1.LabelSelector{ 12168 MatchLabels: map[string]string{ 12169 "foo": "bar", 12170 }, 12171 }, 12172 }, 12173 }, 12174 }, 12175 }, 12176 }, 12177 }, 12178 }, 12179 }, 12180 }, 12181 }, 12182 "ClusterTrustBundlePEM projected volume bySigner with invalid signer name": { 12183 expectedError: "must be a fully qualified domain and path of the form", 12184 spec: core.Pod{ 12185 ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, 12186 Spec: core.PodSpec{ 12187 ServiceAccountName: "some-service-account", 12188 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12189 RestartPolicy: core.RestartPolicyAlways, 12190 DNSPolicy: core.DNSClusterFirst, 12191 Volumes: []core.Volume{ 12192 { 12193 Name: "projected-volume", 12194 VolumeSource: core.VolumeSource{ 12195 Projected: &core.ProjectedVolumeSource{ 12196 Sources: []core.VolumeProjection{ 12197 { 12198 ClusterTrustBundle: &core.ClusterTrustBundleProjection{ 12199 Path: "foo-path", 12200 SignerName: utilpointer.String("example.com/foo/invalid"), 12201 }, 12202 }, 12203 }, 12204 }, 12205 }, 12206 }, 12207 }, 12208 }, 12209 }, 12210 }, 12211 "final PVC name for ephemeral volume must be valid": { 12212 expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters", 12213 spec: core.Pod{ 12214 ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"}, 12215 Spec: core.PodSpec{ 12216 Volumes: []core.Volume{ 12217 {Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}}, 12218 {Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}}, 12219 }, 12220 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12221 RestartPolicy: core.RestartPolicyAlways, 12222 DNSPolicy: core.DNSClusterFirst, 12223 }, 12224 }, 12225 }, 12226 "PersistentVolumeClaimVolumeSource must not reference a generated PVC": { 12227 expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume", 12228 spec: core.Pod{ 12229 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, 12230 Spec: core.PodSpec{ 12231 Volumes: []core.Volume{ 12232 {Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}}, 12233 {Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}}, 12234 }, 12235 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12236 RestartPolicy: core.RestartPolicyAlways, 12237 DNSPolicy: core.DNSClusterFirst, 12238 }, 12239 }, 12240 }, 12241 "invalid pod-deletion-cost": { 12242 expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"text\": must be a 32bit integer", 12243 spec: core.Pod{ 12244 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "text"}}, 12245 Spec: core.PodSpec{ 12246 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12247 RestartPolicy: core.RestartPolicyAlways, 12248 DNSPolicy: core.DNSClusterFirst, 12249 }, 12250 }, 12251 }, 12252 "invalid leading zeros pod-deletion-cost": { 12253 expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"008\": must be a 32bit integer", 12254 spec: core.Pod{ 12255 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "008"}}, 12256 Spec: core.PodSpec{ 12257 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12258 RestartPolicy: core.RestartPolicyAlways, 12259 DNSPolicy: core.DNSClusterFirst, 12260 }, 12261 }, 12262 }, 12263 "invalid leading plus sign pod-deletion-cost": { 12264 expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"+10\": must be a 32bit integer", 12265 spec: core.Pod{ 12266 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "+10"}}, 12267 Spec: core.PodSpec{ 12268 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 12269 RestartPolicy: core.RestartPolicyAlways, 12270 DNSPolicy: core.DNSClusterFirst, 12271 }, 12272 }, 12273 }, 12274 } 12275 for k, v := range errorCases { 12276 t.Run(k, func(t *testing.T) { 12277 if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 { 12278 t.Errorf("expected failure") 12279 } else if v.expectedError == "" { 12280 t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error()) 12281 } else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) { 12282 t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError) 12283 } 12284 }) 12285 } 12286 } 12287 12288 func TestValidatePodCreateWithSchedulingGates(t *testing.T) { 12289 applyEssentials := func(pod *core.Pod) { 12290 pod.Spec.Containers = []core.Container{ 12291 {Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, 12292 } 12293 pod.Spec.RestartPolicy = core.RestartPolicyAlways 12294 pod.Spec.DNSPolicy = core.DNSClusterFirst 12295 } 12296 fldPath := field.NewPath("spec") 12297 12298 tests := []struct { 12299 name string 12300 pod *core.Pod 12301 featureEnabled bool 12302 wantFieldErrors field.ErrorList 12303 }{{ 12304 name: "create a Pod with nodeName and schedulingGates, feature disabled", 12305 pod: &core.Pod{ 12306 ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, 12307 Spec: core.PodSpec{ 12308 NodeName: "node", 12309 SchedulingGates: []core.PodSchedulingGate{ 12310 {Name: "foo"}, 12311 }, 12312 }, 12313 }, 12314 featureEnabled: false, 12315 wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")}, 12316 }, { 12317 name: "create a Pod with nodeName and schedulingGates, feature enabled", 12318 pod: &core.Pod{ 12319 ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, 12320 Spec: core.PodSpec{ 12321 NodeName: "node", 12322 SchedulingGates: []core.PodSchedulingGate{ 12323 {Name: "foo"}, 12324 }, 12325 }, 12326 }, 12327 featureEnabled: true, 12328 wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")}, 12329 }, { 12330 name: "create a Pod with schedulingGates, feature disabled", 12331 pod: &core.Pod{ 12332 ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, 12333 Spec: core.PodSpec{ 12334 SchedulingGates: []core.PodSchedulingGate{ 12335 {Name: "foo"}, 12336 }, 12337 }, 12338 }, 12339 featureEnabled: false, 12340 wantFieldErrors: nil, 12341 }, { 12342 name: "create a Pod with schedulingGates, feature enabled", 12343 pod: &core.Pod{ 12344 ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"}, 12345 Spec: core.PodSpec{ 12346 SchedulingGates: []core.PodSchedulingGate{ 12347 {Name: "foo"}, 12348 }, 12349 }, 12350 }, 12351 featureEnabled: true, 12352 wantFieldErrors: nil, 12353 }, 12354 } 12355 12356 for _, tt := range tests { 12357 t.Run(tt.name, func(t *testing.T) { 12358 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)() 12359 12360 applyEssentials(tt.pod) 12361 errs := ValidatePodCreate(tt.pod, PodValidationOptions{}) 12362 if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" { 12363 t.Errorf("unexpected field errors (-want, +got):\n%s", diff) 12364 } 12365 }) 12366 } 12367 } 12368 12369 func TestValidatePodUpdate(t *testing.T) { 12370 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)() 12371 var ( 12372 activeDeadlineSecondsZero = int64(0) 12373 activeDeadlineSecondsNegative = int64(-30) 12374 activeDeadlineSecondsPositive = int64(30) 12375 activeDeadlineSecondsLarger = int64(31) 12376 validfsGroupChangePolicy = core.FSGroupChangeOnRootMismatch 12377 12378 now = metav1.Now() 12379 grace = int64(30) 12380 grace2 = int64(31) 12381 ) 12382 12383 tests := []struct { 12384 new core.Pod 12385 old core.Pod 12386 opts PodValidationOptions 12387 err string 12388 test string 12389 }{ 12390 {new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, { 12391 new: core.Pod{ 12392 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12393 }, 12394 old: core.Pod{ 12395 ObjectMeta: metav1.ObjectMeta{Name: "bar"}, 12396 }, 12397 err: "metadata.name", 12398 test: "ids", 12399 }, { 12400 new: core.Pod{ 12401 ObjectMeta: metav1.ObjectMeta{ 12402 Name: "foo", 12403 Labels: map[string]string{ 12404 "foo": "bar", 12405 }, 12406 }, 12407 }, 12408 old: core.Pod{ 12409 ObjectMeta: metav1.ObjectMeta{ 12410 Name: "foo", 12411 Labels: map[string]string{ 12412 "bar": "foo", 12413 }, 12414 }, 12415 }, 12416 err: "", 12417 test: "labels", 12418 }, { 12419 new: core.Pod{ 12420 ObjectMeta: metav1.ObjectMeta{ 12421 Name: "foo", 12422 Annotations: map[string]string{ 12423 "foo": "bar", 12424 }, 12425 }, 12426 }, 12427 old: core.Pod{ 12428 ObjectMeta: metav1.ObjectMeta{ 12429 Name: "foo", 12430 Annotations: map[string]string{ 12431 "bar": "foo", 12432 }, 12433 }, 12434 }, 12435 err: "", 12436 test: "annotations", 12437 }, { 12438 new: core.Pod{ 12439 ObjectMeta: metav1.ObjectMeta{ 12440 Name: "foo", 12441 }, 12442 Spec: core.PodSpec{ 12443 Containers: []core.Container{{ 12444 Image: "foo:V1", 12445 }}, 12446 }, 12447 }, 12448 old: core.Pod{ 12449 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12450 Spec: core.PodSpec{ 12451 Containers: []core.Container{{ 12452 Image: "foo:V2", 12453 }, { 12454 Image: "bar:V2", 12455 }}, 12456 }, 12457 }, 12458 err: "may not add or remove containers", 12459 test: "less containers", 12460 }, { 12461 new: core.Pod{ 12462 ObjectMeta: metav1.ObjectMeta{ 12463 Name: "foo", 12464 }, 12465 Spec: core.PodSpec{ 12466 Containers: []core.Container{{ 12467 Image: "foo:V1", 12468 }, { 12469 Image: "bar:V2", 12470 }}, 12471 }, 12472 }, 12473 old: core.Pod{ 12474 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12475 Spec: core.PodSpec{ 12476 Containers: []core.Container{{ 12477 Image: "foo:V2", 12478 }}, 12479 }, 12480 }, 12481 err: "may not add or remove containers", 12482 test: "more containers", 12483 }, { 12484 new: core.Pod{ 12485 ObjectMeta: metav1.ObjectMeta{ 12486 Name: "foo", 12487 }, 12488 Spec: core.PodSpec{ 12489 InitContainers: []core.Container{{ 12490 Image: "foo:V1", 12491 }}, 12492 }, 12493 }, 12494 old: core.Pod{ 12495 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12496 Spec: core.PodSpec{ 12497 InitContainers: []core.Container{{ 12498 Image: "foo:V2", 12499 }, { 12500 Image: "bar:V2", 12501 }}, 12502 }, 12503 }, 12504 err: "may not add or remove containers", 12505 test: "more init containers", 12506 }, { 12507 new: core.Pod{ 12508 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12509 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, 12510 }, 12511 old: core.Pod{ 12512 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, 12513 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, 12514 }, 12515 err: "metadata.deletionTimestamp", 12516 test: "deletion timestamp removed", 12517 }, { 12518 new: core.Pod{ 12519 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now}, 12520 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, 12521 }, 12522 old: core.Pod{ 12523 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12524 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, 12525 }, 12526 err: "metadata.deletionTimestamp", 12527 test: "deletion timestamp added", 12528 }, { 12529 new: core.Pod{ 12530 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace}, 12531 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, 12532 }, 12533 old: core.Pod{ 12534 ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2}, 12535 Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}}, 12536 }, 12537 err: "metadata.deletionGracePeriodSeconds", 12538 test: "deletion grace period seconds changed", 12539 }, { 12540 new: core.Pod{ 12541 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12542 Spec: core.PodSpec{ 12543 Containers: []core.Container{{ 12544 Name: "container", 12545 Image: "foo:V1", 12546 TerminationMessagePolicy: "File", 12547 ImagePullPolicy: "Always", 12548 }}, 12549 }, 12550 }, 12551 old: core.Pod{ 12552 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12553 Spec: core.PodSpec{ 12554 Containers: []core.Container{{ 12555 Name: "container", 12556 Image: "foo:V2", 12557 TerminationMessagePolicy: "File", 12558 ImagePullPolicy: "Always", 12559 }}, 12560 }, 12561 }, 12562 err: "", 12563 test: "image change", 12564 }, { 12565 new: core.Pod{ 12566 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12567 Spec: core.PodSpec{ 12568 InitContainers: []core.Container{{ 12569 Name: "container", 12570 Image: "foo:V1", 12571 TerminationMessagePolicy: "File", 12572 ImagePullPolicy: "Always", 12573 }}, 12574 }, 12575 }, 12576 old: core.Pod{ 12577 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12578 Spec: core.PodSpec{ 12579 InitContainers: []core.Container{{ 12580 Name: "container", 12581 Image: "foo:V2", 12582 TerminationMessagePolicy: "File", 12583 ImagePullPolicy: "Always", 12584 }}, 12585 }, 12586 }, 12587 err: "", 12588 test: "init container image change", 12589 }, { 12590 new: core.Pod{ 12591 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12592 Spec: core.PodSpec{ 12593 Containers: []core.Container{{ 12594 Name: "container", 12595 TerminationMessagePolicy: "File", 12596 ImagePullPolicy: "Always", 12597 }}, 12598 }, 12599 }, 12600 old: core.Pod{ 12601 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12602 Spec: core.PodSpec{ 12603 Containers: []core.Container{{ 12604 Name: "container", 12605 Image: "foo:V2", 12606 TerminationMessagePolicy: "File", 12607 ImagePullPolicy: "Always", 12608 }}, 12609 }, 12610 }, 12611 err: "spec.containers[0].image", 12612 test: "image change to empty", 12613 }, { 12614 new: core.Pod{ 12615 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12616 Spec: core.PodSpec{ 12617 InitContainers: []core.Container{{ 12618 Name: "container", 12619 TerminationMessagePolicy: "File", 12620 ImagePullPolicy: "Always", 12621 }}, 12622 }, 12623 }, 12624 old: core.Pod{ 12625 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12626 Spec: core.PodSpec{ 12627 InitContainers: []core.Container{{ 12628 Name: "container", 12629 Image: "foo:V2", 12630 TerminationMessagePolicy: "File", 12631 ImagePullPolicy: "Always", 12632 }}, 12633 }, 12634 }, 12635 err: "spec.initContainers[0].image", 12636 test: "init container image change to empty", 12637 }, { 12638 new: core.Pod{ 12639 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12640 Spec: core.PodSpec{ 12641 EphemeralContainers: []core.EphemeralContainer{{ 12642 EphemeralContainerCommon: core.EphemeralContainerCommon{ 12643 Name: "ephemeral", 12644 Image: "busybox", 12645 }, 12646 }}, 12647 }, 12648 }, 12649 old: core.Pod{ 12650 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 12651 Spec: core.PodSpec{}, 12652 }, 12653 err: "Forbidden: pod updates may not change fields other than", 12654 test: "ephemeralContainer changes are not allowed via normal pod update", 12655 }, { 12656 new: core.Pod{ 12657 Spec: core.PodSpec{}, 12658 }, 12659 old: core.Pod{ 12660 Spec: core.PodSpec{}, 12661 }, 12662 err: "", 12663 test: "activeDeadlineSeconds no change, nil", 12664 }, { 12665 new: core.Pod{ 12666 Spec: core.PodSpec{ 12667 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12668 }, 12669 }, 12670 old: core.Pod{ 12671 Spec: core.PodSpec{ 12672 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12673 }, 12674 }, 12675 err: "", 12676 test: "activeDeadlineSeconds no change, set", 12677 }, { 12678 new: core.Pod{ 12679 Spec: core.PodSpec{ 12680 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12681 }, 12682 }, 12683 old: core.Pod{}, 12684 err: "", 12685 test: "activeDeadlineSeconds change to positive from nil", 12686 }, { 12687 new: core.Pod{ 12688 Spec: core.PodSpec{ 12689 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12690 }, 12691 }, 12692 old: core.Pod{ 12693 Spec: core.PodSpec{ 12694 ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, 12695 }, 12696 }, 12697 err: "", 12698 test: "activeDeadlineSeconds change to smaller positive", 12699 }, { 12700 new: core.Pod{ 12701 Spec: core.PodSpec{ 12702 ActiveDeadlineSeconds: &activeDeadlineSecondsLarger, 12703 }, 12704 }, 12705 old: core.Pod{ 12706 Spec: core.PodSpec{ 12707 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12708 }, 12709 }, 12710 err: "spec.activeDeadlineSeconds", 12711 test: "activeDeadlineSeconds change to larger positive", 12712 }, 12713 12714 { 12715 new: core.Pod{ 12716 Spec: core.PodSpec{ 12717 ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, 12718 }, 12719 }, 12720 old: core.Pod{}, 12721 err: "spec.activeDeadlineSeconds", 12722 test: "activeDeadlineSeconds change to negative from nil", 12723 }, { 12724 new: core.Pod{ 12725 Spec: core.PodSpec{ 12726 ActiveDeadlineSeconds: &activeDeadlineSecondsNegative, 12727 }, 12728 }, 12729 old: core.Pod{ 12730 Spec: core.PodSpec{ 12731 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12732 }, 12733 }, 12734 err: "spec.activeDeadlineSeconds", 12735 test: "activeDeadlineSeconds change to negative from positive", 12736 }, { 12737 new: core.Pod{ 12738 Spec: core.PodSpec{ 12739 ActiveDeadlineSeconds: &activeDeadlineSecondsZero, 12740 }, 12741 }, 12742 old: core.Pod{ 12743 Spec: core.PodSpec{ 12744 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12745 }, 12746 }, 12747 err: "spec.activeDeadlineSeconds", 12748 test: "activeDeadlineSeconds change to zero from positive", 12749 }, { 12750 new: core.Pod{ 12751 Spec: core.PodSpec{ 12752 ActiveDeadlineSeconds: &activeDeadlineSecondsZero, 12753 }, 12754 }, 12755 old: core.Pod{}, 12756 err: "spec.activeDeadlineSeconds", 12757 test: "activeDeadlineSeconds change to zero from nil", 12758 }, { 12759 new: core.Pod{}, 12760 old: core.Pod{ 12761 Spec: core.PodSpec{ 12762 ActiveDeadlineSeconds: &activeDeadlineSecondsPositive, 12763 }, 12764 }, 12765 err: "spec.activeDeadlineSeconds", 12766 test: "activeDeadlineSeconds change to nil from positive", 12767 }, { 12768 new: core.Pod{ 12769 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12770 Spec: core.PodSpec{ 12771 Containers: []core.Container{{ 12772 Name: "container", 12773 TerminationMessagePolicy: "File", 12774 ImagePullPolicy: "Always", 12775 Image: "foo:V2", 12776 Resources: core.ResourceRequirements{ 12777 Limits: getResources("200m", "0", "1Gi"), 12778 }, 12779 }}, 12780 }, 12781 }, 12782 old: core.Pod{ 12783 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12784 Spec: core.PodSpec{ 12785 Containers: []core.Container{{ 12786 Name: "container", 12787 TerminationMessagePolicy: "File", 12788 ImagePullPolicy: "Always", 12789 Image: "foo:V2", 12790 Resources: core.ResourceRequirements{ 12791 Limits: getResources("100m", "0", "1Gi"), 12792 }, 12793 }}, 12794 }, 12795 }, 12796 err: "", 12797 test: "cpu limit change", 12798 }, { 12799 new: core.Pod{ 12800 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12801 Spec: core.PodSpec{ 12802 Containers: []core.Container{{ 12803 Name: "container", 12804 TerminationMessagePolicy: "File", 12805 ImagePullPolicy: "Always", 12806 Image: "foo:V1", 12807 Resources: core.ResourceRequirements{ 12808 Limits: getResourceLimits("100m", "100Mi"), 12809 }, 12810 }}, 12811 }, 12812 }, 12813 old: core.Pod{ 12814 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12815 Spec: core.PodSpec{ 12816 Containers: []core.Container{{ 12817 Name: "container", 12818 TerminationMessagePolicy: "File", 12819 ImagePullPolicy: "Always", 12820 Image: "foo:V2", 12821 Resources: core.ResourceRequirements{ 12822 Limits: getResourceLimits("100m", "200Mi"), 12823 }, 12824 }}, 12825 }, 12826 }, 12827 err: "", 12828 test: "memory limit change", 12829 }, { 12830 new: core.Pod{ 12831 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12832 Spec: core.PodSpec{ 12833 Containers: []core.Container{{ 12834 Name: "container", 12835 TerminationMessagePolicy: "File", 12836 ImagePullPolicy: "Always", 12837 Image: "foo:V1", 12838 Resources: core.ResourceRequirements{ 12839 Limits: getResources("100m", "100Mi", "1Gi"), 12840 }, 12841 }}, 12842 }, 12843 }, 12844 old: core.Pod{ 12845 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12846 Spec: core.PodSpec{ 12847 Containers: []core.Container{{ 12848 Name: "container", 12849 TerminationMessagePolicy: "File", 12850 ImagePullPolicy: "Always", 12851 Image: "foo:V2", 12852 Resources: core.ResourceRequirements{ 12853 Limits: getResources("100m", "100Mi", "2Gi"), 12854 }, 12855 }}, 12856 }, 12857 }, 12858 err: "Forbidden: pod updates may not change fields other than", 12859 test: "storage limit change", 12860 }, { 12861 new: core.Pod{ 12862 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12863 Spec: core.PodSpec{ 12864 Containers: []core.Container{{ 12865 Name: "container", 12866 TerminationMessagePolicy: "File", 12867 ImagePullPolicy: "Always", 12868 Image: "foo:V1", 12869 Resources: core.ResourceRequirements{ 12870 Requests: getResourceLimits("100m", "0"), 12871 }, 12872 }}, 12873 }, 12874 }, 12875 old: core.Pod{ 12876 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12877 Spec: core.PodSpec{ 12878 Containers: []core.Container{{ 12879 Name: "container", 12880 TerminationMessagePolicy: "File", 12881 ImagePullPolicy: "Always", 12882 Image: "foo:V2", 12883 Resources: core.ResourceRequirements{ 12884 Requests: getResourceLimits("200m", "0"), 12885 }, 12886 }}, 12887 }, 12888 }, 12889 err: "", 12890 test: "cpu request change", 12891 }, { 12892 new: core.Pod{ 12893 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12894 Spec: core.PodSpec{ 12895 Containers: []core.Container{{ 12896 Name: "container", 12897 TerminationMessagePolicy: "File", 12898 ImagePullPolicy: "Always", 12899 Image: "foo:V1", 12900 Resources: core.ResourceRequirements{ 12901 Requests: getResourceLimits("0", "200Mi"), 12902 }, 12903 }}, 12904 }, 12905 }, 12906 old: core.Pod{ 12907 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12908 Spec: core.PodSpec{ 12909 Containers: []core.Container{{ 12910 Name: "container", 12911 TerminationMessagePolicy: "File", 12912 ImagePullPolicy: "Always", 12913 Image: "foo:V2", 12914 Resources: core.ResourceRequirements{ 12915 Requests: getResourceLimits("0", "100Mi"), 12916 }, 12917 }}, 12918 }, 12919 }, 12920 err: "", 12921 test: "memory request change", 12922 }, { 12923 new: core.Pod{ 12924 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12925 Spec: core.PodSpec{ 12926 Containers: []core.Container{{ 12927 Name: "container", 12928 TerminationMessagePolicy: "File", 12929 ImagePullPolicy: "Always", 12930 Image: "foo:V1", 12931 Resources: core.ResourceRequirements{ 12932 Requests: getResources("100m", "0", "2Gi"), 12933 }, 12934 }}, 12935 }, 12936 }, 12937 old: core.Pod{ 12938 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12939 Spec: core.PodSpec{ 12940 Containers: []core.Container{{ 12941 Name: "container", 12942 TerminationMessagePolicy: "File", 12943 ImagePullPolicy: "Always", 12944 Image: "foo:V2", 12945 Resources: core.ResourceRequirements{ 12946 Requests: getResources("100m", "0", "1Gi"), 12947 }, 12948 }}, 12949 }, 12950 }, 12951 err: "Forbidden: pod updates may not change fields other than", 12952 test: "storage request change", 12953 }, { 12954 new: core.Pod{ 12955 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12956 Spec: core.PodSpec{ 12957 Containers: []core.Container{{ 12958 Name: "container", 12959 TerminationMessagePolicy: "File", 12960 ImagePullPolicy: "Always", 12961 Image: "foo:V1", 12962 Resources: core.ResourceRequirements{ 12963 Limits: getResources("200m", "400Mi", "1Gi"), 12964 Requests: getResources("200m", "400Mi", "1Gi"), 12965 }, 12966 }}, 12967 }, 12968 }, 12969 old: core.Pod{ 12970 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12971 Spec: core.PodSpec{ 12972 Containers: []core.Container{{ 12973 Name: "container", 12974 TerminationMessagePolicy: "File", 12975 ImagePullPolicy: "Always", 12976 Image: "foo:V1", 12977 Resources: core.ResourceRequirements{ 12978 Limits: getResources("100m", "100Mi", "1Gi"), 12979 Requests: getResources("100m", "100Mi", "1Gi"), 12980 }, 12981 }}, 12982 }, 12983 }, 12984 err: "", 12985 test: "Pod QoS unchanged, guaranteed -> guaranteed", 12986 }, { 12987 new: core.Pod{ 12988 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 12989 Spec: core.PodSpec{ 12990 Containers: []core.Container{{ 12991 Name: "container", 12992 TerminationMessagePolicy: "File", 12993 ImagePullPolicy: "Always", 12994 Image: "foo:V1", 12995 Resources: core.ResourceRequirements{ 12996 Limits: getResources("200m", "200Mi", "2Gi"), 12997 Requests: getResources("100m", "100Mi", "1Gi"), 12998 }, 12999 }}, 13000 }, 13001 }, 13002 old: core.Pod{ 13003 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13004 Spec: core.PodSpec{ 13005 Containers: []core.Container{{ 13006 Name: "container", 13007 TerminationMessagePolicy: "File", 13008 ImagePullPolicy: "Always", 13009 Image: "foo:V1", 13010 Resources: core.ResourceRequirements{ 13011 Limits: getResources("400m", "400Mi", "2Gi"), 13012 Requests: getResources("200m", "200Mi", "1Gi"), 13013 }, 13014 }}, 13015 }, 13016 }, 13017 err: "", 13018 test: "Pod QoS unchanged, burstable -> burstable", 13019 }, { 13020 new: core.Pod{ 13021 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13022 Spec: core.PodSpec{ 13023 Containers: []core.Container{{ 13024 Name: "container", 13025 TerminationMessagePolicy: "File", 13026 ImagePullPolicy: "Always", 13027 Image: "foo:V2", 13028 Resources: core.ResourceRequirements{ 13029 Limits: getResourceLimits("200m", "200Mi"), 13030 Requests: getResourceLimits("100m", "100Mi"), 13031 }, 13032 }}, 13033 }, 13034 }, 13035 old: core.Pod{ 13036 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13037 Spec: core.PodSpec{ 13038 Containers: []core.Container{{ 13039 Name: "container", 13040 TerminationMessagePolicy: "File", 13041 ImagePullPolicy: "Always", 13042 Image: "foo:V2", 13043 Resources: core.ResourceRequirements{ 13044 Requests: getResourceLimits("100m", "100Mi"), 13045 }, 13046 }}, 13047 }, 13048 }, 13049 err: "", 13050 test: "Pod QoS unchanged, burstable -> burstable, add limits", 13051 }, { 13052 new: core.Pod{ 13053 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13054 Spec: core.PodSpec{ 13055 Containers: []core.Container{{ 13056 Name: "container", 13057 TerminationMessagePolicy: "File", 13058 ImagePullPolicy: "Always", 13059 Image: "foo:V2", 13060 Resources: core.ResourceRequirements{ 13061 Requests: getResourceLimits("100m", "100Mi"), 13062 }, 13063 }}, 13064 }, 13065 }, 13066 old: core.Pod{ 13067 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13068 Spec: core.PodSpec{ 13069 Containers: []core.Container{{ 13070 Name: "container", 13071 TerminationMessagePolicy: "File", 13072 ImagePullPolicy: "Always", 13073 Image: "foo:V2", 13074 Resources: core.ResourceRequirements{ 13075 Limits: getResourceLimits("200m", "200Mi"), 13076 Requests: getResourceLimits("100m", "100Mi"), 13077 }, 13078 }}, 13079 }, 13080 }, 13081 err: "", 13082 test: "Pod QoS unchanged, burstable -> burstable, remove limits", 13083 }, { 13084 new: core.Pod{ 13085 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13086 Spec: core.PodSpec{ 13087 Containers: []core.Container{{ 13088 Name: "container", 13089 TerminationMessagePolicy: "File", 13090 ImagePullPolicy: "Always", 13091 Image: "foo:V2", 13092 Resources: core.ResourceRequirements{ 13093 Limits: getResources("400m", "", "1Gi"), 13094 Requests: getResources("300m", "", "1Gi"), 13095 }, 13096 }}, 13097 }, 13098 }, 13099 old: core.Pod{ 13100 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13101 Spec: core.PodSpec{ 13102 Containers: []core.Container{{ 13103 Name: "container", 13104 TerminationMessagePolicy: "File", 13105 ImagePullPolicy: "Always", 13106 Image: "foo:V2", 13107 Resources: core.ResourceRequirements{ 13108 Limits: getResources("200m", "500Mi", "1Gi"), 13109 }, 13110 }}, 13111 }, 13112 }, 13113 err: "", 13114 test: "Pod QoS unchanged, burstable -> burstable, add requests", 13115 }, { 13116 new: core.Pod{ 13117 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13118 Spec: core.PodSpec{ 13119 Containers: []core.Container{{ 13120 Name: "container", 13121 TerminationMessagePolicy: "File", 13122 ImagePullPolicy: "Always", 13123 Image: "foo:V2", 13124 Resources: core.ResourceRequirements{ 13125 Limits: getResources("400m", "500Mi", "2Gi"), 13126 }, 13127 }}, 13128 }, 13129 }, 13130 old: core.Pod{ 13131 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13132 Spec: core.PodSpec{ 13133 Containers: []core.Container{{ 13134 Name: "container", 13135 TerminationMessagePolicy: "File", 13136 ImagePullPolicy: "Always", 13137 Image: "foo:V2", 13138 Resources: core.ResourceRequirements{ 13139 Limits: getResources("200m", "300Mi", "2Gi"), 13140 Requests: getResourceLimits("100m", "200Mi"), 13141 }, 13142 }}, 13143 }, 13144 }, 13145 err: "", 13146 test: "Pod QoS unchanged, burstable -> burstable, remove requests", 13147 }, { 13148 new: core.Pod{ 13149 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13150 Spec: core.PodSpec{ 13151 Containers: []core.Container{{ 13152 Name: "container", 13153 TerminationMessagePolicy: "File", 13154 ImagePullPolicy: "Always", 13155 Image: "foo:V2", 13156 Resources: core.ResourceRequirements{ 13157 Limits: getResourceLimits("200m", "200Mi"), 13158 Requests: getResourceLimits("100m", "100Mi"), 13159 }, 13160 }}, 13161 }, 13162 }, 13163 old: core.Pod{ 13164 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13165 Spec: core.PodSpec{ 13166 Containers: []core.Container{{ 13167 Name: "container", 13168 TerminationMessagePolicy: "File", 13169 ImagePullPolicy: "Always", 13170 Image: "foo:V2", 13171 Resources: core.ResourceRequirements{ 13172 Limits: getResourceLimits("100m", "100Mi"), 13173 Requests: getResourceLimits("100m", "100Mi"), 13174 }, 13175 }}, 13176 }, 13177 }, 13178 err: "Pod QoS is immutable", 13179 test: "Pod QoS change, guaranteed -> burstable", 13180 }, { 13181 new: core.Pod{ 13182 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13183 Spec: core.PodSpec{ 13184 Containers: []core.Container{{ 13185 Name: "container", 13186 TerminationMessagePolicy: "File", 13187 ImagePullPolicy: "Always", 13188 Image: "foo:V2", 13189 Resources: core.ResourceRequirements{ 13190 Limits: getResourceLimits("100m", "100Mi"), 13191 Requests: getResourceLimits("100m", "100Mi"), 13192 }, 13193 }}, 13194 }, 13195 }, 13196 old: core.Pod{ 13197 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13198 Spec: core.PodSpec{ 13199 Containers: []core.Container{{ 13200 Name: "container", 13201 TerminationMessagePolicy: "File", 13202 ImagePullPolicy: "Always", 13203 Image: "foo:V2", 13204 Resources: core.ResourceRequirements{ 13205 Requests: getResourceLimits("100m", "100Mi"), 13206 }, 13207 }}, 13208 }, 13209 }, 13210 err: "Pod QoS is immutable", 13211 test: "Pod QoS change, burstable -> guaranteed", 13212 }, { 13213 new: core.Pod{ 13214 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13215 Spec: core.PodSpec{ 13216 Containers: []core.Container{{ 13217 Name: "container", 13218 TerminationMessagePolicy: "File", 13219 ImagePullPolicy: "Always", 13220 Image: "foo:V2", 13221 Resources: core.ResourceRequirements{ 13222 Limits: getResourceLimits("200m", "200Mi"), 13223 Requests: getResourceLimits("100m", "100Mi"), 13224 }, 13225 }}, 13226 }, 13227 }, 13228 old: core.Pod{ 13229 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13230 Spec: core.PodSpec{ 13231 Containers: []core.Container{{ 13232 Name: "container", 13233 TerminationMessagePolicy: "File", 13234 ImagePullPolicy: "Always", 13235 Image: "foo:V2", 13236 }}, 13237 }, 13238 }, 13239 err: "Pod QoS is immutable", 13240 test: "Pod QoS change, besteffort -> burstable", 13241 }, { 13242 new: core.Pod{ 13243 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13244 Spec: core.PodSpec{ 13245 Containers: []core.Container{{ 13246 Name: "container", 13247 TerminationMessagePolicy: "File", 13248 ImagePullPolicy: "Always", 13249 Image: "foo:V2", 13250 }}, 13251 }, 13252 }, 13253 old: core.Pod{ 13254 ObjectMeta: metav1.ObjectMeta{Name: "pod"}, 13255 Spec: core.PodSpec{ 13256 Containers: []core.Container{{ 13257 Name: "container", 13258 TerminationMessagePolicy: "File", 13259 ImagePullPolicy: "Always", 13260 Image: "foo:V2", 13261 Resources: core.ResourceRequirements{ 13262 Limits: getResourceLimits("200m", "200Mi"), 13263 Requests: getResourceLimits("100m", "100Mi"), 13264 }, 13265 }}, 13266 }, 13267 }, 13268 err: "Pod QoS is immutable", 13269 test: "Pod QoS change, burstable -> besteffort", 13270 }, { 13271 new: core.Pod{ 13272 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 13273 Spec: core.PodSpec{ 13274 Containers: []core.Container{{ 13275 Image: "foo:V1", 13276 }}, 13277 SecurityContext: &core.PodSecurityContext{ 13278 FSGroupChangePolicy: &validfsGroupChangePolicy, 13279 }, 13280 }, 13281 }, 13282 old: core.Pod{ 13283 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 13284 Spec: core.PodSpec{ 13285 Containers: []core.Container{{ 13286 Image: "foo:V2", 13287 }}, 13288 SecurityContext: &core.PodSecurityContext{ 13289 FSGroupChangePolicy: nil, 13290 }, 13291 }, 13292 }, 13293 err: "spec: Forbidden: pod updates may not change fields", 13294 test: "fsGroupChangePolicy change", 13295 }, { 13296 new: core.Pod{ 13297 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 13298 Spec: core.PodSpec{ 13299 Containers: []core.Container{{ 13300 Image: "foo:V1", 13301 Ports: []core.ContainerPort{ 13302 {HostPort: 8080, ContainerPort: 80}, 13303 }, 13304 }}, 13305 }, 13306 }, 13307 old: core.Pod{ 13308 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 13309 Spec: core.PodSpec{ 13310 Containers: []core.Container{{ 13311 Image: "foo:V2", 13312 Ports: []core.ContainerPort{ 13313 {HostPort: 8000, ContainerPort: 80}, 13314 }, 13315 }}, 13316 }, 13317 }, 13318 err: "spec: Forbidden: pod updates may not change fields", 13319 test: "port change", 13320 }, { 13321 new: core.Pod{ 13322 ObjectMeta: metav1.ObjectMeta{ 13323 Name: "foo", 13324 Labels: map[string]string{ 13325 "foo": "bar", 13326 }, 13327 }, 13328 }, 13329 old: core.Pod{ 13330 ObjectMeta: metav1.ObjectMeta{ 13331 Name: "foo", 13332 Labels: map[string]string{ 13333 "Bar": "foo", 13334 }, 13335 }, 13336 }, 13337 err: "", 13338 test: "bad label change", 13339 }, { 13340 new: core.Pod{ 13341 ObjectMeta: metav1.ObjectMeta{ 13342 Name: "foo", 13343 }, 13344 Spec: core.PodSpec{ 13345 NodeName: "node1", 13346 Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}}, 13347 }, 13348 }, 13349 old: core.Pod{ 13350 ObjectMeta: metav1.ObjectMeta{ 13351 Name: "foo", 13352 }, 13353 Spec: core.PodSpec{ 13354 NodeName: "node1", 13355 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, 13356 }, 13357 }, 13358 err: "spec.tolerations: Forbidden", 13359 test: "existing toleration value modified in pod spec updates", 13360 }, { 13361 new: core.Pod{ 13362 ObjectMeta: metav1.ObjectMeta{ 13363 Name: "foo", 13364 }, 13365 Spec: core.PodSpec{ 13366 NodeName: "node1", 13367 Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}}, 13368 }, 13369 }, 13370 old: core.Pod{ 13371 ObjectMeta: metav1.ObjectMeta{ 13372 Name: "foo", 13373 }, 13374 Spec: core.PodSpec{ 13375 NodeName: "node1", 13376 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, 13377 }, 13378 }, 13379 err: "spec.tolerations: Forbidden", 13380 test: "existing toleration value modified in pod spec updates with modified tolerationSeconds", 13381 }, { 13382 new: core.Pod{ 13383 ObjectMeta: metav1.ObjectMeta{ 13384 Name: "foo", 13385 }, 13386 Spec: core.PodSpec{ 13387 NodeName: "node1", 13388 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, 13389 }, 13390 }, 13391 old: core.Pod{ 13392 ObjectMeta: metav1.ObjectMeta{ 13393 Name: "foo", 13394 }, 13395 Spec: core.PodSpec{ 13396 NodeName: "node1", 13397 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}}, 13398 }}, 13399 err: "", 13400 test: "modified tolerationSeconds in existing toleration value in pod spec updates", 13401 }, { 13402 new: core.Pod{ 13403 ObjectMeta: metav1.ObjectMeta{ 13404 Name: "foo", 13405 }, 13406 Spec: core.PodSpec{ 13407 Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}}, 13408 }, 13409 }, 13410 old: core.Pod{ 13411 ObjectMeta: metav1.ObjectMeta{ 13412 Name: "foo", 13413 }, 13414 Spec: core.PodSpec{ 13415 NodeName: "", 13416 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, 13417 }, 13418 }, 13419 err: "spec.tolerations: Forbidden", 13420 test: "toleration modified in updates to an unscheduled pod", 13421 }, { 13422 new: core.Pod{ 13423 ObjectMeta: metav1.ObjectMeta{ 13424 Name: "foo", 13425 }, 13426 Spec: core.PodSpec{ 13427 NodeName: "node1", 13428 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, 13429 }, 13430 }, 13431 old: core.Pod{ 13432 ObjectMeta: metav1.ObjectMeta{ 13433 Name: "foo", 13434 }, 13435 Spec: core.PodSpec{ 13436 NodeName: "node1", 13437 Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}}, 13438 }, 13439 }, 13440 err: "", 13441 test: "tolerations unmodified in updates to a scheduled pod", 13442 }, { 13443 new: core.Pod{ 13444 ObjectMeta: metav1.ObjectMeta{ 13445 Name: "foo", 13446 }, 13447 Spec: core.PodSpec{ 13448 NodeName: "node1", 13449 Tolerations: []core.Toleration{ 13450 {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}, 13451 {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]}, 13452 }, 13453 }}, 13454 old: core.Pod{ 13455 ObjectMeta: metav1.ObjectMeta{ 13456 Name: "foo", 13457 }, 13458 Spec: core.PodSpec{ 13459 NodeName: "node1", 13460 Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, 13461 }, 13462 }, 13463 err: "", 13464 test: "added valid new toleration to existing tolerations in pod spec updates", 13465 }, { 13466 new: core.Pod{ 13467 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{ 13468 NodeName: "node1", 13469 Tolerations: []core.Toleration{ 13470 {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}, 13471 {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]}, 13472 }, 13473 }}, 13474 old: core.Pod{ 13475 ObjectMeta: metav1.ObjectMeta{ 13476 Name: "foo", 13477 }, 13478 Spec: core.PodSpec{ 13479 NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}}, 13480 }}, 13481 err: "spec.tolerations[1].effect", 13482 test: "added invalid new toleration to existing tolerations in pod spec updates", 13483 }, { 13484 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, 13485 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}, 13486 err: "spec: Forbidden: pod updates may not change fields", 13487 test: "removed nodeName from pod spec", 13488 }, { 13489 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}}, 13490 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, 13491 err: "metadata.annotations[kubernetes.io/config.mirror]", 13492 test: "added mirror pod annotation", 13493 }, { 13494 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}}, 13495 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}}, 13496 err: "metadata.annotations[kubernetes.io/config.mirror]", 13497 test: "removed mirror pod annotation", 13498 }, { 13499 new: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}}, 13500 old: core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}}, 13501 err: "metadata.annotations[kubernetes.io/config.mirror]", 13502 test: "changed mirror pod annotation", 13503 }, { 13504 new: core.Pod{ 13505 ObjectMeta: metav1.ObjectMeta{ 13506 Name: "foo", 13507 }, 13508 Spec: core.PodSpec{ 13509 NodeName: "node1", 13510 PriorityClassName: "bar-priority", 13511 }, 13512 }, 13513 old: core.Pod{ 13514 ObjectMeta: metav1.ObjectMeta{ 13515 Name: "foo", 13516 }, 13517 Spec: core.PodSpec{ 13518 NodeName: "node1", 13519 PriorityClassName: "foo-priority", 13520 }, 13521 }, 13522 err: "spec: Forbidden: pod updates", 13523 test: "changed priority class name", 13524 }, { 13525 new: core.Pod{ 13526 ObjectMeta: metav1.ObjectMeta{ 13527 Name: "foo", 13528 }, 13529 Spec: core.PodSpec{ 13530 NodeName: "node1", 13531 PriorityClassName: "", 13532 }, 13533 }, 13534 old: core.Pod{ 13535 ObjectMeta: metav1.ObjectMeta{ 13536 Name: "foo", 13537 }, 13538 Spec: core.PodSpec{ 13539 NodeName: "node1", 13540 PriorityClassName: "foo-priority", 13541 }, 13542 }, 13543 err: "spec: Forbidden: pod updates", 13544 test: "removed priority class name", 13545 }, { 13546 new: core.Pod{ 13547 ObjectMeta: metav1.ObjectMeta{ 13548 Name: "foo", 13549 }, 13550 Spec: core.PodSpec{ 13551 TerminationGracePeriodSeconds: utilpointer.Int64(1), 13552 }, 13553 }, 13554 old: core.Pod{ 13555 ObjectMeta: metav1.ObjectMeta{ 13556 Name: "foo", 13557 }, 13558 Spec: core.PodSpec{ 13559 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 13560 }, 13561 }, 13562 err: "", 13563 test: "update termination grace period seconds", 13564 }, { 13565 new: core.Pod{ 13566 ObjectMeta: metav1.ObjectMeta{ 13567 Name: "foo", 13568 }, 13569 Spec: core.PodSpec{ 13570 TerminationGracePeriodSeconds: utilpointer.Int64(0), 13571 }, 13572 }, 13573 old: core.Pod{ 13574 ObjectMeta: metav1.ObjectMeta{ 13575 Name: "foo", 13576 }, 13577 Spec: core.PodSpec{ 13578 TerminationGracePeriodSeconds: utilpointer.Int64(-1), 13579 }, 13580 }, 13581 err: "spec: Forbidden: pod updates", 13582 test: "update termination grace period seconds not 1", 13583 }, { 13584 new: core.Pod{ 13585 ObjectMeta: metav1.ObjectMeta{ 13586 Name: "foo", 13587 }, 13588 Spec: core.PodSpec{ 13589 OS: &core.PodOS{Name: core.Windows}, 13590 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}, 13591 }, 13592 }, 13593 old: core.Pod{ 13594 ObjectMeta: metav1.ObjectMeta{ 13595 Name: "foo", 13596 }, 13597 Spec: core.PodSpec{ 13598 OS: &core.PodOS{Name: core.Linux}, 13599 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}, 13600 }, 13601 }, 13602 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", 13603 test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set", 13604 }, { 13605 new: core.Pod{ 13606 ObjectMeta: metav1.ObjectMeta{ 13607 Name: "foo", 13608 }, 13609 Spec: core.PodSpec{ 13610 OS: &core.PodOS{Name: core.Windows}, 13611 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}, 13612 }, 13613 }, 13614 old: core.Pod{ 13615 ObjectMeta: metav1.ObjectMeta{ 13616 Name: "foo", 13617 }, 13618 Spec: core.PodSpec{ 13619 OS: &core.PodOS{Name: core.Linux}, 13620 SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}, 13621 }, 13622 }, 13623 err: "spec.securityContext.seLinuxOptions: Forbidden", 13624 test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set, we'd get SELinux errors as well", 13625 }, { 13626 new: core.Pod{ 13627 ObjectMeta: metav1.ObjectMeta{ 13628 Name: "foo", 13629 }, 13630 Spec: core.PodSpec{ 13631 OS: &core.PodOS{Name: "dummy"}, 13632 }, 13633 }, 13634 old: core.Pod{ 13635 ObjectMeta: metav1.ObjectMeta{ 13636 Name: "foo", 13637 }, 13638 Spec: core.PodSpec{}, 13639 }, 13640 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", 13641 test: "invalid PodOS update, IdentifyPodOS featuregate set", 13642 }, { 13643 new: core.Pod{ 13644 ObjectMeta: metav1.ObjectMeta{ 13645 Name: "foo", 13646 }, 13647 Spec: core.PodSpec{ 13648 OS: &core.PodOS{Name: core.Linux}, 13649 }, 13650 }, 13651 old: core.Pod{ 13652 ObjectMeta: metav1.ObjectMeta{ 13653 Name: "foo", 13654 }, 13655 Spec: core.PodSpec{ 13656 OS: &core.PodOS{Name: core.Windows}, 13657 }, 13658 }, 13659 err: "Forbidden: pod updates may not change fields other than ", 13660 test: "update pod spec OS to a valid value, featuregate disabled", 13661 }, { 13662 new: core.Pod{ 13663 Spec: core.PodSpec{ 13664 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}}, 13665 }, 13666 }, 13667 old: core.Pod{}, 13668 err: "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'", 13669 test: "update pod spec schedulingGates: add new scheduling gate", 13670 }, { 13671 new: core.Pod{ 13672 Spec: core.PodSpec{ 13673 SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}}, 13674 }, 13675 }, 13676 old: core.Pod{ 13677 Spec: core.PodSpec{ 13678 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}}, 13679 }, 13680 }, 13681 err: "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'", 13682 test: "update pod spec schedulingGates: mutating an existing scheduling gate", 13683 }, { 13684 new: core.Pod{ 13685 Spec: core.PodSpec{ 13686 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13687 }, 13688 }, 13689 old: core.Pod{ 13690 Spec: core.PodSpec{ 13691 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}}, 13692 }, 13693 }, 13694 err: "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'", 13695 test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion", 13696 }, { 13697 new: core.Pod{}, 13698 old: core.Pod{ 13699 Spec: core.PodSpec{ 13700 SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}}, 13701 }, 13702 }, 13703 err: "", 13704 test: "update pod spec schedulingGates: legal deletion", 13705 }, { 13706 old: core.Pod{ 13707 Spec: core.PodSpec{ 13708 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13709 }, 13710 }, 13711 new: core.Pod{ 13712 Spec: core.PodSpec{ 13713 NodeSelector: map[string]string{ 13714 "foo": "bar", 13715 }, 13716 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13717 }, 13718 }, 13719 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", 13720 test: "node selector is immutable when AllowMutableNodeSelector is false", 13721 }, { 13722 old: core.Pod{ 13723 Spec: core.PodSpec{ 13724 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13725 }, 13726 }, 13727 new: core.Pod{ 13728 Spec: core.PodSpec{ 13729 NodeSelector: map[string]string{ 13730 "foo": "bar", 13731 }, 13732 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13733 }, 13734 }, 13735 opts: PodValidationOptions{ 13736 AllowMutableNodeSelectorAndNodeAffinity: true, 13737 }, 13738 test: "adding node selector is allowed for gated pods", 13739 }, { 13740 old: core.Pod{ 13741 Spec: core.PodSpec{ 13742 NodeSelector: map[string]string{ 13743 "foo": "bar", 13744 }, 13745 }, 13746 }, 13747 new: core.Pod{ 13748 Spec: core.PodSpec{ 13749 NodeSelector: map[string]string{ 13750 "foo": "bar", 13751 "foo2": "bar2", 13752 }, 13753 }, 13754 }, 13755 opts: PodValidationOptions{ 13756 AllowMutableNodeSelectorAndNodeAffinity: true, 13757 }, 13758 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", 13759 test: "adding node selector is not allowed for non-gated pods", 13760 }, { 13761 old: core.Pod{ 13762 Spec: core.PodSpec{ 13763 NodeSelector: map[string]string{ 13764 "foo": "bar", 13765 }, 13766 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13767 }, 13768 }, 13769 new: core.Pod{ 13770 Spec: core.PodSpec{ 13771 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13772 }, 13773 }, 13774 opts: PodValidationOptions{ 13775 AllowMutableNodeSelectorAndNodeAffinity: true, 13776 }, 13777 err: "spec.nodeSelector: Invalid value:", 13778 test: "removing node selector is not allowed for gated pods", 13779 }, { 13780 old: core.Pod{ 13781 Spec: core.PodSpec{ 13782 NodeSelector: map[string]string{ 13783 "foo": "bar", 13784 }, 13785 }, 13786 }, 13787 new: core.Pod{}, 13788 opts: PodValidationOptions{ 13789 AllowMutableNodeSelectorAndNodeAffinity: true, 13790 }, 13791 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", 13792 test: "removing node selector is not allowed for non-gated pods", 13793 }, { 13794 old: core.Pod{ 13795 Spec: core.PodSpec{ 13796 NodeSelector: map[string]string{ 13797 "foo": "bar", 13798 }, 13799 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13800 }, 13801 }, 13802 new: core.Pod{ 13803 Spec: core.PodSpec{ 13804 NodeSelector: map[string]string{ 13805 "foo": "bar", 13806 "foo2": "bar2", 13807 }, 13808 }, 13809 }, 13810 opts: PodValidationOptions{ 13811 AllowMutableNodeSelectorAndNodeAffinity: true, 13812 }, 13813 test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added", 13814 }, { 13815 old: core.Pod{ 13816 Spec: core.PodSpec{ 13817 NodeSelector: map[string]string{ 13818 "foo": "bar", 13819 }, 13820 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13821 }, 13822 }, 13823 new: core.Pod{ 13824 Spec: core.PodSpec{ 13825 NodeSelector: map[string]string{ 13826 "foo": "new value", 13827 }, 13828 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13829 }, 13830 }, 13831 opts: PodValidationOptions{ 13832 AllowMutableNodeSelectorAndNodeAffinity: true, 13833 }, 13834 err: "spec.nodeSelector: Invalid value:", 13835 test: "modifying value of existing node selector is not allowed", 13836 }, { 13837 old: core.Pod{ 13838 Spec: core.PodSpec{ 13839 Affinity: &core.Affinity{ 13840 NodeAffinity: &core.NodeAffinity{ 13841 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13842 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13843 MatchExpressions: []core.NodeSelectorRequirement{{ 13844 Key: "expr", 13845 Operator: core.NodeSelectorOpIn, 13846 Values: []string{"foo"}, 13847 }}, 13848 }}, 13849 }, 13850 }, 13851 }, 13852 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13853 }, 13854 }, 13855 new: core.Pod{ 13856 Spec: core.PodSpec{ 13857 Affinity: &core.Affinity{ 13858 NodeAffinity: &core.NodeAffinity{ 13859 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13860 // Add 1 MatchExpression and 1 MatchField. 13861 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13862 MatchExpressions: []core.NodeSelectorRequirement{{ 13863 Key: "expr", 13864 Operator: core.NodeSelectorOpIn, 13865 Values: []string{"foo"}, 13866 }, { 13867 Key: "expr2", 13868 Operator: core.NodeSelectorOpIn, 13869 Values: []string{"foo2"}, 13870 }}, 13871 MatchFields: []core.NodeSelectorRequirement{{ 13872 Key: "metadata.name", 13873 Operator: core.NodeSelectorOpIn, 13874 Values: []string{"foo"}, 13875 }}, 13876 }}, 13877 }, 13878 }, 13879 }, 13880 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13881 }, 13882 }, 13883 opts: PodValidationOptions{ 13884 AllowMutableNodeSelectorAndNodeAffinity: true, 13885 }, 13886 test: "addition to nodeAffinity is allowed for gated pods", 13887 }, { 13888 old: core.Pod{ 13889 Spec: core.PodSpec{ 13890 Affinity: &core.Affinity{ 13891 NodeAffinity: &core.NodeAffinity{ 13892 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13893 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13894 MatchExpressions: []core.NodeSelectorRequirement{{ 13895 Key: "expr", 13896 Operator: core.NodeSelectorOpIn, 13897 Values: []string{"foo"}, 13898 }}, 13899 }}, 13900 }, 13901 }, 13902 }, 13903 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13904 }, 13905 }, 13906 new: core.Pod{ 13907 Spec: core.PodSpec{ 13908 Affinity: &core.Affinity{ 13909 NodeAffinity: &core.NodeAffinity{}, 13910 }, 13911 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13912 }, 13913 }, 13914 opts: PodValidationOptions{ 13915 AllowMutableNodeSelectorAndNodeAffinity: true, 13916 }, 13917 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:", 13918 test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated", 13919 }, { 13920 old: core.Pod{ 13921 Spec: core.PodSpec{ 13922 Affinity: &core.Affinity{ 13923 NodeAffinity: &core.NodeAffinity{ 13924 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13925 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13926 MatchExpressions: []core.NodeSelectorRequirement{{ 13927 Key: "expr", 13928 Operator: core.NodeSelectorOpIn, 13929 Values: []string{"foo"}, 13930 }}, 13931 }}, 13932 }, 13933 }, 13934 }, 13935 }, 13936 }, 13937 new: core.Pod{ 13938 Spec: core.PodSpec{ 13939 Affinity: &core.Affinity{ 13940 NodeAffinity: &core.NodeAffinity{ 13941 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13942 // Add 1 MatchExpression and 1 MatchField. 13943 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13944 MatchExpressions: []core.NodeSelectorRequirement{{ 13945 Key: "expr", 13946 Operator: core.NodeSelectorOpIn, 13947 Values: []string{"foo"}, 13948 }, { 13949 Key: "expr2", 13950 Operator: core.NodeSelectorOpIn, 13951 Values: []string{"foo2"}, 13952 }}, 13953 MatchFields: []core.NodeSelectorRequirement{{ 13954 Key: "metadata.name", 13955 Operator: core.NodeSelectorOpIn, 13956 Values: []string{"foo"}, 13957 }}, 13958 }}, 13959 }, 13960 }, 13961 }, 13962 }, 13963 }, 13964 opts: PodValidationOptions{ 13965 AllowMutableNodeSelectorAndNodeAffinity: true, 13966 }, 13967 err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image", 13968 test: "addition to nodeAffinity is not allowed for non-gated pods", 13969 }, { 13970 old: core.Pod{ 13971 Spec: core.PodSpec{ 13972 Affinity: &core.Affinity{ 13973 NodeAffinity: &core.NodeAffinity{ 13974 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13975 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13976 MatchExpressions: []core.NodeSelectorRequirement{{ 13977 Key: "expr", 13978 Operator: core.NodeSelectorOpIn, 13979 Values: []string{"foo"}, 13980 }}, 13981 }}, 13982 }, 13983 }, 13984 }, 13985 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 13986 }, 13987 }, 13988 new: core.Pod{ 13989 Spec: core.PodSpec{ 13990 Affinity: &core.Affinity{ 13991 NodeAffinity: &core.NodeAffinity{ 13992 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 13993 // Add 1 MatchExpression and 1 MatchField. 13994 NodeSelectorTerms: []core.NodeSelectorTerm{{ 13995 MatchExpressions: []core.NodeSelectorRequirement{{ 13996 Key: "expr", 13997 Operator: core.NodeSelectorOpIn, 13998 Values: []string{"foo"}, 13999 }, { 14000 Key: "expr2", 14001 Operator: core.NodeSelectorOpIn, 14002 Values: []string{"foo2"}, 14003 }}, 14004 MatchFields: []core.NodeSelectorRequirement{{ 14005 Key: "metadata.name", 14006 Operator: core.NodeSelectorOpIn, 14007 Values: []string{"foo"}, 14008 }}, 14009 }}, 14010 }, 14011 }, 14012 }, 14013 }, 14014 }, 14015 opts: PodValidationOptions{ 14016 AllowMutableNodeSelectorAndNodeAffinity: true, 14017 }, 14018 test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs", 14019 }, { 14020 old: core.Pod{ 14021 Spec: core.PodSpec{ 14022 Affinity: &core.Affinity{ 14023 NodeAffinity: &core.NodeAffinity{ 14024 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14025 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14026 MatchExpressions: []core.NodeSelectorRequirement{{ 14027 Key: "expr", 14028 Operator: core.NodeSelectorOpIn, 14029 Values: []string{"foo"}, 14030 }}, 14031 }}, 14032 }, 14033 }, 14034 }, 14035 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14036 }, 14037 }, 14038 new: core.Pod{ 14039 Spec: core.PodSpec{ 14040 Affinity: &core.Affinity{ 14041 NodeAffinity: &core.NodeAffinity{ 14042 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14043 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14044 MatchFields: []core.NodeSelectorRequirement{{ 14045 Key: "metadata.name", 14046 Operator: core.NodeSelectorOpIn, 14047 Values: []string{"foo"}, 14048 }}, 14049 }}, 14050 }, 14051 }, 14052 }, 14053 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14054 }, 14055 }, 14056 opts: PodValidationOptions{ 14057 AllowMutableNodeSelectorAndNodeAffinity: true, 14058 }, 14059 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", 14060 test: "nodeAffinity deletion from MatchExpressions not allowed", 14061 }, { 14062 old: core.Pod{ 14063 Spec: core.PodSpec{ 14064 Affinity: &core.Affinity{ 14065 NodeAffinity: &core.NodeAffinity{ 14066 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14067 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14068 MatchExpressions: []core.NodeSelectorRequirement{{ 14069 Key: "expr", 14070 Operator: core.NodeSelectorOpIn, 14071 Values: []string{"foo"}, 14072 }}, 14073 MatchFields: []core.NodeSelectorRequirement{{ 14074 Key: "metadata.name", 14075 Operator: core.NodeSelectorOpIn, 14076 Values: []string{"foo"}, 14077 }}, 14078 }}, 14079 }, 14080 }, 14081 }, 14082 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14083 }, 14084 }, 14085 new: core.Pod{ 14086 Spec: core.PodSpec{ 14087 Affinity: &core.Affinity{ 14088 NodeAffinity: &core.NodeAffinity{ 14089 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14090 // Add 1 MatchExpression and 1 MatchField. 14091 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14092 MatchExpressions: []core.NodeSelectorRequirement{{ 14093 Key: "expr", 14094 Operator: core.NodeSelectorOpIn, 14095 Values: []string{"foo"}, 14096 }}, 14097 }}, 14098 }, 14099 }, 14100 }, 14101 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14102 }, 14103 }, 14104 opts: PodValidationOptions{ 14105 AllowMutableNodeSelectorAndNodeAffinity: true, 14106 }, 14107 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", 14108 test: "nodeAffinity deletion from MatchFields not allowed", 14109 }, { 14110 old: core.Pod{ 14111 Spec: core.PodSpec{ 14112 Affinity: &core.Affinity{ 14113 NodeAffinity: &core.NodeAffinity{ 14114 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14115 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14116 MatchExpressions: []core.NodeSelectorRequirement{{ 14117 Key: "expr", 14118 Operator: core.NodeSelectorOpIn, 14119 Values: []string{"foo"}, 14120 }}, 14121 MatchFields: []core.NodeSelectorRequirement{{ 14122 Key: "metadata.name", 14123 Operator: core.NodeSelectorOpIn, 14124 Values: []string{"foo"}, 14125 }}, 14126 }}, 14127 }, 14128 }, 14129 }, 14130 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14131 }, 14132 }, 14133 new: core.Pod{ 14134 Spec: core.PodSpec{ 14135 Affinity: &core.Affinity{ 14136 NodeAffinity: &core.NodeAffinity{ 14137 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14138 // Add 1 MatchExpression and 1 MatchField. 14139 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14140 MatchExpressions: []core.NodeSelectorRequirement{{ 14141 Key: "expr", 14142 Operator: core.NodeSelectorOpIn, 14143 Values: []string{"bar"}, 14144 }}, 14145 MatchFields: []core.NodeSelectorRequirement{{ 14146 Key: "metadata.name", 14147 Operator: core.NodeSelectorOpIn, 14148 Values: []string{"foo"}, 14149 }}, 14150 }}, 14151 }, 14152 }, 14153 }, 14154 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14155 }, 14156 }, 14157 opts: PodValidationOptions{ 14158 AllowMutableNodeSelectorAndNodeAffinity: true, 14159 }, 14160 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", 14161 test: "nodeAffinity modification of item in MatchExpressions not allowed", 14162 }, { 14163 old: core.Pod{ 14164 Spec: core.PodSpec{ 14165 Affinity: &core.Affinity{ 14166 NodeAffinity: &core.NodeAffinity{ 14167 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14168 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14169 MatchExpressions: []core.NodeSelectorRequirement{{ 14170 Key: "expr", 14171 Operator: core.NodeSelectorOpIn, 14172 Values: []string{"foo"}, 14173 }}, 14174 MatchFields: []core.NodeSelectorRequirement{{ 14175 Key: "metadata.name", 14176 Operator: core.NodeSelectorOpIn, 14177 Values: []string{"foo"}, 14178 }}, 14179 }}, 14180 }, 14181 }, 14182 }, 14183 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14184 }, 14185 }, 14186 new: core.Pod{ 14187 Spec: core.PodSpec{ 14188 Affinity: &core.Affinity{ 14189 NodeAffinity: &core.NodeAffinity{ 14190 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14191 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14192 MatchExpressions: []core.NodeSelectorRequirement{{ 14193 Key: "expr", 14194 Operator: core.NodeSelectorOpIn, 14195 Values: []string{"foo"}, 14196 }}, 14197 MatchFields: []core.NodeSelectorRequirement{{ 14198 Key: "metadata.name", 14199 Operator: core.NodeSelectorOpIn, 14200 Values: []string{"bar"}, 14201 }}, 14202 }}, 14203 }, 14204 }, 14205 }, 14206 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14207 }, 14208 }, 14209 opts: PodValidationOptions{ 14210 AllowMutableNodeSelectorAndNodeAffinity: true, 14211 }, 14212 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", 14213 test: "nodeAffinity modification of item in MatchFields not allowed", 14214 }, { 14215 old: core.Pod{ 14216 Spec: core.PodSpec{ 14217 Affinity: &core.Affinity{ 14218 NodeAffinity: &core.NodeAffinity{ 14219 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14220 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14221 MatchExpressions: []core.NodeSelectorRequirement{{ 14222 Key: "expr", 14223 Operator: core.NodeSelectorOpIn, 14224 Values: []string{"foo"}, 14225 }}, 14226 MatchFields: []core.NodeSelectorRequirement{{ 14227 Key: "metadata.name", 14228 Operator: core.NodeSelectorOpIn, 14229 Values: []string{"foo"}, 14230 }}, 14231 }}, 14232 }, 14233 }, 14234 }, 14235 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14236 }, 14237 }, 14238 new: core.Pod{ 14239 Spec: core.PodSpec{ 14240 Affinity: &core.Affinity{ 14241 NodeAffinity: &core.NodeAffinity{ 14242 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14243 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14244 MatchExpressions: []core.NodeSelectorRequirement{{ 14245 Key: "expr", 14246 Operator: core.NodeSelectorOpIn, 14247 Values: []string{"foo"}, 14248 }}, 14249 MatchFields: []core.NodeSelectorRequirement{{ 14250 Key: "metadata.name", 14251 Operator: core.NodeSelectorOpIn, 14252 Values: []string{"bar"}, 14253 }}, 14254 }, { 14255 MatchExpressions: []core.NodeSelectorRequirement{{ 14256 Key: "expr", 14257 Operator: core.NodeSelectorOpIn, 14258 Values: []string{"foo2"}, 14259 }}, 14260 MatchFields: []core.NodeSelectorRequirement{{ 14261 Key: "metadata.name", 14262 Operator: core.NodeSelectorOpIn, 14263 Values: []string{"bar2"}, 14264 }}, 14265 }}, 14266 }, 14267 }, 14268 }, 14269 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14270 }, 14271 }, 14272 opts: PodValidationOptions{ 14273 AllowMutableNodeSelectorAndNodeAffinity: true, 14274 }, 14275 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:", 14276 test: "nodeSelectorTerms addition on gated pod should fail", 14277 }, { 14278 old: core.Pod{ 14279 Spec: core.PodSpec{ 14280 Affinity: &core.Affinity{ 14281 NodeAffinity: &core.NodeAffinity{ 14282 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 14283 Weight: 1.0, 14284 Preference: core.NodeSelectorTerm{ 14285 MatchExpressions: []core.NodeSelectorRequirement{{ 14286 Key: "expr", 14287 Operator: core.NodeSelectorOpIn, 14288 Values: []string{"foo"}, 14289 }}, 14290 }, 14291 }}, 14292 }, 14293 }, 14294 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14295 }, 14296 }, 14297 new: core.Pod{ 14298 Spec: core.PodSpec{ 14299 Affinity: &core.Affinity{ 14300 NodeAffinity: &core.NodeAffinity{ 14301 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 14302 Weight: 1.0, 14303 Preference: core.NodeSelectorTerm{ 14304 MatchExpressions: []core.NodeSelectorRequirement{{ 14305 Key: "expr", 14306 Operator: core.NodeSelectorOpIn, 14307 Values: []string{"foo2"}, 14308 }}, 14309 }, 14310 }}, 14311 }, 14312 }, 14313 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14314 }, 14315 }, 14316 opts: PodValidationOptions{ 14317 AllowMutableNodeSelectorAndNodeAffinity: true, 14318 }, 14319 test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods", 14320 }, { 14321 old: core.Pod{ 14322 Spec: core.PodSpec{ 14323 Affinity: &core.Affinity{ 14324 NodeAffinity: &core.NodeAffinity{ 14325 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 14326 Weight: 1.0, 14327 Preference: core.NodeSelectorTerm{ 14328 MatchExpressions: []core.NodeSelectorRequirement{{ 14329 Key: "expr", 14330 Operator: core.NodeSelectorOpIn, 14331 Values: []string{"foo"}, 14332 }}, 14333 }, 14334 }}, 14335 }, 14336 }, 14337 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14338 }, 14339 }, 14340 new: core.Pod{ 14341 Spec: core.PodSpec{ 14342 Affinity: &core.Affinity{ 14343 NodeAffinity: &core.NodeAffinity{ 14344 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 14345 Weight: 1.0, 14346 Preference: core.NodeSelectorTerm{ 14347 MatchExpressions: []core.NodeSelectorRequirement{{ 14348 Key: "expr", 14349 Operator: core.NodeSelectorOpIn, 14350 Values: []string{"foo"}, 14351 }, { 14352 Key: "expr2", 14353 Operator: core.NodeSelectorOpIn, 14354 Values: []string{"foo2"}, 14355 }}, 14356 MatchFields: []core.NodeSelectorRequirement{{ 14357 Key: "metadata.name", 14358 Operator: core.NodeSelectorOpIn, 14359 Values: []string{"bar"}, 14360 }}, 14361 }, 14362 }}, 14363 }, 14364 }, 14365 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14366 }, 14367 }, 14368 opts: PodValidationOptions{ 14369 AllowMutableNodeSelectorAndNodeAffinity: true, 14370 }, 14371 test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods", 14372 }, { 14373 old: core.Pod{ 14374 Spec: core.PodSpec{ 14375 Affinity: &core.Affinity{ 14376 NodeAffinity: &core.NodeAffinity{ 14377 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 14378 Weight: 1.0, 14379 Preference: core.NodeSelectorTerm{ 14380 MatchExpressions: []core.NodeSelectorRequirement{{ 14381 Key: "expr", 14382 Operator: core.NodeSelectorOpIn, 14383 Values: []string{"foo"}, 14384 }}, 14385 }, 14386 }}, 14387 }, 14388 }, 14389 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14390 }, 14391 }, 14392 new: core.Pod{ 14393 Spec: core.PodSpec{ 14394 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14395 }, 14396 }, 14397 opts: PodValidationOptions{ 14398 AllowMutableNodeSelectorAndNodeAffinity: true, 14399 }, 14400 test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods", 14401 }, { 14402 old: core.Pod{ 14403 Spec: core.PodSpec{ 14404 Affinity: &core.Affinity{ 14405 NodeAffinity: &core.NodeAffinity{ 14406 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14407 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14408 MatchExpressions: []core.NodeSelectorRequirement{{ 14409 Key: "expr", 14410 Operator: core.NodeSelectorOpIn, 14411 Values: []string{"foo"}, 14412 }}, 14413 }}, 14414 }, 14415 }, 14416 }, 14417 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14418 }, 14419 }, 14420 new: core.Pod{ 14421 Spec: core.PodSpec{ 14422 Affinity: &core.Affinity{}, 14423 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14424 }, 14425 }, 14426 opts: PodValidationOptions{ 14427 AllowMutableNodeSelectorAndNodeAffinity: true, 14428 }, 14429 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:", 14430 test: "new node affinity is nil", 14431 }, { 14432 old: core.Pod{ 14433 Spec: core.PodSpec{ 14434 Affinity: &core.Affinity{ 14435 NodeAffinity: &core.NodeAffinity{ 14436 PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{ 14437 Weight: 1.0, 14438 Preference: core.NodeSelectorTerm{ 14439 MatchExpressions: []core.NodeSelectorRequirement{{ 14440 Key: "expr", 14441 Operator: core.NodeSelectorOpIn, 14442 Values: []string{"foo"}, 14443 }}, 14444 }, 14445 }}, 14446 }, 14447 }, 14448 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14449 }, 14450 }, 14451 new: core.Pod{ 14452 Spec: core.PodSpec{ 14453 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14454 }, 14455 }, 14456 opts: PodValidationOptions{ 14457 AllowMutableNodeSelectorAndNodeAffinity: true, 14458 }, 14459 test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods", 14460 }, { 14461 old: core.Pod{ 14462 Spec: core.PodSpec{ 14463 Affinity: &core.Affinity{ 14464 NodeAffinity: &core.NodeAffinity{ 14465 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14466 NodeSelectorTerms: []core.NodeSelectorTerm{ 14467 {}, 14468 }, 14469 }, 14470 }, 14471 }, 14472 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14473 }, 14474 }, 14475 new: core.Pod{ 14476 Spec: core.PodSpec{ 14477 Affinity: &core.Affinity{ 14478 NodeAffinity: &core.NodeAffinity{ 14479 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14480 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14481 MatchExpressions: []core.NodeSelectorRequirement{{ 14482 Key: "expr", 14483 Operator: core.NodeSelectorOpIn, 14484 Values: []string{"foo"}, 14485 }}, 14486 }}, 14487 }, 14488 }, 14489 }, 14490 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14491 }, 14492 }, 14493 opts: PodValidationOptions{ 14494 AllowMutableNodeSelectorAndNodeAffinity: true, 14495 }, 14496 err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:", 14497 test: "empty NodeSelectorTerm (selects nothing) cannot become populated (selects something)", 14498 }, { 14499 old: core.Pod{ 14500 Spec: core.PodSpec{ 14501 Affinity: nil, 14502 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14503 }, 14504 }, 14505 new: core.Pod{ 14506 Spec: core.PodSpec{ 14507 Affinity: &core.Affinity{ 14508 NodeAffinity: &core.NodeAffinity{ 14509 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14510 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14511 MatchExpressions: []core.NodeSelectorRequirement{{ 14512 Key: "expr", 14513 Operator: core.NodeSelectorOpIn, 14514 Values: []string{"foo"}, 14515 }}, 14516 }}, 14517 }, 14518 }, 14519 }, 14520 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14521 }, 14522 }, 14523 opts: PodValidationOptions{ 14524 AllowMutableNodeSelectorAndNodeAffinity: true, 14525 }, 14526 test: "nil affinity can be mutated for gated pods", 14527 }, 14528 { 14529 old: core.Pod{ 14530 Spec: core.PodSpec{ 14531 Affinity: nil, 14532 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14533 }, 14534 }, 14535 new: core.Pod{ 14536 Spec: core.PodSpec{ 14537 Affinity: &core.Affinity{ 14538 NodeAffinity: &core.NodeAffinity{ 14539 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14540 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14541 MatchExpressions: []core.NodeSelectorRequirement{{ 14542 Key: "expr", 14543 Operator: core.NodeSelectorOpIn, 14544 Values: []string{"foo"}, 14545 }}, 14546 }}, 14547 }, 14548 }, 14549 PodAffinity: &core.PodAffinity{ 14550 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 14551 { 14552 TopologyKey: "foo", 14553 LabelSelector: &metav1.LabelSelector{ 14554 MatchLabels: map[string]string{"foo": "bar"}, 14555 }, 14556 }, 14557 }, 14558 }, 14559 }, 14560 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14561 }, 14562 }, 14563 opts: PodValidationOptions{ 14564 AllowMutableNodeSelectorAndNodeAffinity: true, 14565 }, 14566 err: "pod updates may not change fields other than", 14567 test: "the podAffinity cannot be updated on gated pods", 14568 }, 14569 { 14570 old: core.Pod{ 14571 Spec: core.PodSpec{ 14572 Affinity: nil, 14573 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14574 }, 14575 }, 14576 new: core.Pod{ 14577 Spec: core.PodSpec{ 14578 Affinity: &core.Affinity{ 14579 NodeAffinity: &core.NodeAffinity{ 14580 RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{ 14581 NodeSelectorTerms: []core.NodeSelectorTerm{{ 14582 MatchExpressions: []core.NodeSelectorRequirement{{ 14583 Key: "expr", 14584 Operator: core.NodeSelectorOpIn, 14585 Values: []string{"foo"}, 14586 }}, 14587 }}, 14588 }, 14589 }, 14590 PodAntiAffinity: &core.PodAntiAffinity{ 14591 RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{ 14592 { 14593 TopologyKey: "foo", 14594 LabelSelector: &metav1.LabelSelector{ 14595 MatchLabels: map[string]string{"foo": "bar"}, 14596 }, 14597 }, 14598 }, 14599 }, 14600 }, 14601 SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}}, 14602 }, 14603 }, 14604 opts: PodValidationOptions{ 14605 AllowMutableNodeSelectorAndNodeAffinity: true, 14606 }, 14607 err: "pod updates may not change fields other than", 14608 test: "the podAntiAffinity cannot be updated on gated pods", 14609 }, 14610 } 14611 for _, test := range tests { 14612 test.new.ObjectMeta.ResourceVersion = "1" 14613 test.old.ObjectMeta.ResourceVersion = "1" 14614 14615 // set required fields if old and new match and have no opinion on the value 14616 if test.new.Name == "" && test.old.Name == "" { 14617 test.new.Name = "name" 14618 test.old.Name = "name" 14619 } 14620 if test.new.Namespace == "" && test.old.Namespace == "" { 14621 test.new.Namespace = "namespace" 14622 test.old.Namespace = "namespace" 14623 } 14624 if test.new.Spec.Containers == nil && test.old.Spec.Containers == nil { 14625 test.new.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}} 14626 test.old.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}} 14627 } 14628 if len(test.new.Spec.DNSPolicy) == 0 && len(test.old.Spec.DNSPolicy) == 0 { 14629 test.new.Spec.DNSPolicy = core.DNSClusterFirst 14630 test.old.Spec.DNSPolicy = core.DNSClusterFirst 14631 } 14632 if len(test.new.Spec.RestartPolicy) == 0 && len(test.old.Spec.RestartPolicy) == 0 { 14633 test.new.Spec.RestartPolicy = "Always" 14634 test.old.Spec.RestartPolicy = "Always" 14635 } 14636 14637 errs := ValidatePodUpdate(&test.new, &test.old, test.opts) 14638 if test.err == "" { 14639 if len(errs) != 0 { 14640 t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old) 14641 } 14642 } else { 14643 if len(errs) == 0 { 14644 t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old) 14645 } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) { 14646 t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr) 14647 } 14648 } 14649 } 14650 } 14651 14652 func TestValidatePodStatusUpdate(t *testing.T) { 14653 tests := []struct { 14654 new core.Pod 14655 old core.Pod 14656 err string 14657 test string 14658 }{{ 14659 core.Pod{ 14660 ObjectMeta: metav1.ObjectMeta{ 14661 Name: "foo", 14662 }, 14663 Spec: core.PodSpec{ 14664 NodeName: "node1", 14665 }, 14666 Status: core.PodStatus{ 14667 NominatedNodeName: "node1", 14668 }, 14669 }, 14670 core.Pod{ 14671 ObjectMeta: metav1.ObjectMeta{ 14672 Name: "foo", 14673 }, 14674 Spec: core.PodSpec{ 14675 NodeName: "node1", 14676 }, 14677 Status: core.PodStatus{}, 14678 }, 14679 "", 14680 "removed nominatedNodeName", 14681 }, { 14682 core.Pod{ 14683 ObjectMeta: metav1.ObjectMeta{ 14684 Name: "foo", 14685 }, 14686 Spec: core.PodSpec{ 14687 NodeName: "node1", 14688 }, 14689 }, 14690 core.Pod{ 14691 ObjectMeta: metav1.ObjectMeta{ 14692 Name: "foo", 14693 }, 14694 Spec: core.PodSpec{ 14695 NodeName: "node1", 14696 }, 14697 Status: core.PodStatus{ 14698 NominatedNodeName: "node1", 14699 }, 14700 }, 14701 "", 14702 "add valid nominatedNodeName", 14703 }, { 14704 core.Pod{ 14705 ObjectMeta: metav1.ObjectMeta{ 14706 Name: "foo", 14707 }, 14708 Spec: core.PodSpec{ 14709 NodeName: "node1", 14710 }, 14711 Status: core.PodStatus{ 14712 NominatedNodeName: "Node1", 14713 }, 14714 }, 14715 core.Pod{ 14716 ObjectMeta: metav1.ObjectMeta{ 14717 Name: "foo", 14718 }, 14719 Spec: core.PodSpec{ 14720 NodeName: "node1", 14721 }, 14722 }, 14723 "nominatedNodeName", 14724 "Add invalid nominatedNodeName", 14725 }, { 14726 core.Pod{ 14727 ObjectMeta: metav1.ObjectMeta{ 14728 Name: "foo", 14729 }, 14730 Spec: core.PodSpec{ 14731 NodeName: "node1", 14732 }, 14733 Status: core.PodStatus{ 14734 NominatedNodeName: "node1", 14735 }, 14736 }, 14737 core.Pod{ 14738 ObjectMeta: metav1.ObjectMeta{ 14739 Name: "foo", 14740 }, 14741 Spec: core.PodSpec{ 14742 NodeName: "node1", 14743 }, 14744 Status: core.PodStatus{ 14745 NominatedNodeName: "node2", 14746 }, 14747 }, 14748 "", 14749 "Update nominatedNodeName", 14750 }, { 14751 core.Pod{ 14752 ObjectMeta: metav1.ObjectMeta{ 14753 Name: "foo", 14754 }, 14755 Status: core.PodStatus{ 14756 InitContainerStatuses: []core.ContainerStatus{{ 14757 ContainerID: "docker://numbers", 14758 Image: "alpine", 14759 Name: "init", 14760 Ready: false, 14761 Started: proto.Bool(false), 14762 State: core.ContainerState{ 14763 Waiting: &core.ContainerStateWaiting{ 14764 Reason: "PodInitializing", 14765 }, 14766 }, 14767 }}, 14768 ContainerStatuses: []core.ContainerStatus{{ 14769 ContainerID: "docker://numbers", 14770 Image: "nginx:alpine", 14771 Name: "main", 14772 Ready: false, 14773 Started: proto.Bool(false), 14774 State: core.ContainerState{ 14775 Waiting: &core.ContainerStateWaiting{ 14776 Reason: "PodInitializing", 14777 }, 14778 }, 14779 }}, 14780 }, 14781 }, 14782 core.Pod{ 14783 ObjectMeta: metav1.ObjectMeta{ 14784 Name: "foo", 14785 }, 14786 }, 14787 "", 14788 "Container statuses pending", 14789 }, { 14790 core.Pod{ 14791 ObjectMeta: metav1.ObjectMeta{ 14792 Name: "foo", 14793 }, 14794 Status: core.PodStatus{ 14795 InitContainerStatuses: []core.ContainerStatus{{ 14796 ContainerID: "docker://numbers", 14797 Image: "alpine", 14798 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14799 Name: "init", 14800 Ready: true, 14801 State: core.ContainerState{ 14802 Terminated: &core.ContainerStateTerminated{ 14803 ContainerID: "docker://numbers", 14804 Reason: "Completed", 14805 }, 14806 }, 14807 }}, 14808 ContainerStatuses: []core.ContainerStatus{{ 14809 ContainerID: "docker://numbers", 14810 Image: "nginx:alpine", 14811 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14812 Name: "nginx", 14813 Ready: true, 14814 Started: proto.Bool(true), 14815 State: core.ContainerState{ 14816 Running: &core.ContainerStateRunning{ 14817 StartedAt: metav1.NewTime(time.Now()), 14818 }, 14819 }, 14820 }}, 14821 }, 14822 }, 14823 core.Pod{ 14824 ObjectMeta: metav1.ObjectMeta{ 14825 Name: "foo", 14826 }, 14827 Status: core.PodStatus{ 14828 InitContainerStatuses: []core.ContainerStatus{{ 14829 ContainerID: "docker://numbers", 14830 Image: "alpine", 14831 Name: "init", 14832 Ready: false, 14833 State: core.ContainerState{ 14834 Waiting: &core.ContainerStateWaiting{ 14835 Reason: "PodInitializing", 14836 }, 14837 }, 14838 }}, 14839 ContainerStatuses: []core.ContainerStatus{{ 14840 ContainerID: "docker://numbers", 14841 Image: "nginx:alpine", 14842 Name: "main", 14843 Ready: false, 14844 Started: proto.Bool(false), 14845 State: core.ContainerState{ 14846 Waiting: &core.ContainerStateWaiting{ 14847 Reason: "PodInitializing", 14848 }, 14849 }, 14850 }}, 14851 }, 14852 }, 14853 "", 14854 "Container statuses running", 14855 }, { 14856 core.Pod{ 14857 ObjectMeta: metav1.ObjectMeta{ 14858 Name: "foo", 14859 }, 14860 Status: core.PodStatus{ 14861 ContainerStatuses: []core.ContainerStatus{{ 14862 ContainerID: "docker://numbers", 14863 Image: "nginx:alpine", 14864 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14865 Name: "nginx", 14866 Ready: true, 14867 Started: proto.Bool(true), 14868 State: core.ContainerState{ 14869 Running: &core.ContainerStateRunning{ 14870 StartedAt: metav1.NewTime(time.Now()), 14871 }, 14872 }, 14873 }}, 14874 EphemeralContainerStatuses: []core.ContainerStatus{{ 14875 ContainerID: "docker://numbers", 14876 Image: "busybox", 14877 Name: "debug", 14878 Ready: false, 14879 State: core.ContainerState{ 14880 Waiting: &core.ContainerStateWaiting{ 14881 Reason: "PodInitializing", 14882 }, 14883 }, 14884 }}, 14885 }, 14886 }, 14887 core.Pod{ 14888 ObjectMeta: metav1.ObjectMeta{ 14889 Name: "foo", 14890 }, 14891 Status: core.PodStatus{ 14892 ContainerStatuses: []core.ContainerStatus{{ 14893 ContainerID: "docker://numbers", 14894 Image: "nginx:alpine", 14895 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14896 Name: "nginx", 14897 Ready: true, 14898 Started: proto.Bool(true), 14899 State: core.ContainerState{ 14900 Running: &core.ContainerStateRunning{ 14901 StartedAt: metav1.NewTime(time.Now()), 14902 }, 14903 }, 14904 }}, 14905 }, 14906 }, 14907 "", 14908 "Container statuses add ephemeral container", 14909 }, { 14910 core.Pod{ 14911 ObjectMeta: metav1.ObjectMeta{ 14912 Name: "foo", 14913 }, 14914 Status: core.PodStatus{ 14915 ContainerStatuses: []core.ContainerStatus{{ 14916 ContainerID: "docker://numbers", 14917 Image: "nginx:alpine", 14918 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14919 Name: "nginx", 14920 Ready: true, 14921 Started: proto.Bool(true), 14922 State: core.ContainerState{ 14923 Running: &core.ContainerStateRunning{ 14924 StartedAt: metav1.NewTime(time.Now()), 14925 }, 14926 }, 14927 }}, 14928 EphemeralContainerStatuses: []core.ContainerStatus{{ 14929 ContainerID: "docker://numbers", 14930 Image: "busybox", 14931 ImageID: "docker-pullable://busybox@sha256:d0gf00d", 14932 Name: "debug", 14933 Ready: false, 14934 State: core.ContainerState{ 14935 Running: &core.ContainerStateRunning{ 14936 StartedAt: metav1.NewTime(time.Now()), 14937 }, 14938 }, 14939 }}, 14940 }, 14941 }, 14942 core.Pod{ 14943 ObjectMeta: metav1.ObjectMeta{ 14944 Name: "foo", 14945 }, 14946 Status: core.PodStatus{ 14947 ContainerStatuses: []core.ContainerStatus{{ 14948 ContainerID: "docker://numbers", 14949 Image: "nginx:alpine", 14950 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14951 Name: "nginx", 14952 Ready: true, 14953 Started: proto.Bool(true), 14954 State: core.ContainerState{ 14955 Running: &core.ContainerStateRunning{ 14956 StartedAt: metav1.NewTime(time.Now()), 14957 }, 14958 }, 14959 }}, 14960 EphemeralContainerStatuses: []core.ContainerStatus{{ 14961 ContainerID: "docker://numbers", 14962 Image: "busybox", 14963 Name: "debug", 14964 Ready: false, 14965 State: core.ContainerState{ 14966 Waiting: &core.ContainerStateWaiting{ 14967 Reason: "PodInitializing", 14968 }, 14969 }, 14970 }}, 14971 }, 14972 }, 14973 "", 14974 "Container statuses ephemeral container running", 14975 }, { 14976 core.Pod{ 14977 ObjectMeta: metav1.ObjectMeta{ 14978 Name: "foo", 14979 }, 14980 Status: core.PodStatus{ 14981 ContainerStatuses: []core.ContainerStatus{{ 14982 ContainerID: "docker://numbers", 14983 Image: "nginx:alpine", 14984 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 14985 Name: "nginx", 14986 Ready: true, 14987 Started: proto.Bool(true), 14988 State: core.ContainerState{ 14989 Running: &core.ContainerStateRunning{ 14990 StartedAt: metav1.NewTime(time.Now()), 14991 }, 14992 }, 14993 }}, 14994 EphemeralContainerStatuses: []core.ContainerStatus{{ 14995 ContainerID: "docker://numbers", 14996 Image: "busybox", 14997 ImageID: "docker-pullable://busybox@sha256:d0gf00d", 14998 Name: "debug", 14999 Ready: false, 15000 State: core.ContainerState{ 15001 Terminated: &core.ContainerStateTerminated{ 15002 ContainerID: "docker://numbers", 15003 Reason: "Completed", 15004 StartedAt: metav1.NewTime(time.Now()), 15005 FinishedAt: metav1.NewTime(time.Now()), 15006 }, 15007 }, 15008 }}, 15009 }, 15010 }, 15011 core.Pod{ 15012 ObjectMeta: metav1.ObjectMeta{ 15013 Name: "foo", 15014 }, 15015 Status: core.PodStatus{ 15016 ContainerStatuses: []core.ContainerStatus{{ 15017 ContainerID: "docker://numbers", 15018 Image: "nginx:alpine", 15019 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15020 Name: "nginx", 15021 Ready: true, 15022 Started: proto.Bool(true), 15023 State: core.ContainerState{ 15024 Running: &core.ContainerStateRunning{ 15025 StartedAt: metav1.NewTime(time.Now()), 15026 }, 15027 }, 15028 }}, 15029 EphemeralContainerStatuses: []core.ContainerStatus{{ 15030 ContainerID: "docker://numbers", 15031 Image: "busybox", 15032 ImageID: "docker-pullable://busybox@sha256:d0gf00d", 15033 Name: "debug", 15034 Ready: false, 15035 State: core.ContainerState{ 15036 Running: &core.ContainerStateRunning{ 15037 StartedAt: metav1.NewTime(time.Now()), 15038 }, 15039 }, 15040 }}, 15041 }, 15042 }, 15043 "", 15044 "Container statuses ephemeral container exited", 15045 }, { 15046 core.Pod{ 15047 ObjectMeta: metav1.ObjectMeta{ 15048 Name: "foo", 15049 }, 15050 Status: core.PodStatus{ 15051 InitContainerStatuses: []core.ContainerStatus{{ 15052 ContainerID: "docker://numbers", 15053 Image: "alpine", 15054 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15055 Name: "init", 15056 Ready: true, 15057 State: core.ContainerState{ 15058 Terminated: &core.ContainerStateTerminated{ 15059 ContainerID: "docker://numbers", 15060 Reason: "Completed", 15061 }, 15062 }, 15063 }}, 15064 ContainerStatuses: []core.ContainerStatus{{ 15065 ContainerID: "docker://numbers", 15066 Image: "nginx:alpine", 15067 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15068 Name: "nginx", 15069 Ready: true, 15070 Started: proto.Bool(true), 15071 State: core.ContainerState{ 15072 Terminated: &core.ContainerStateTerminated{ 15073 ContainerID: "docker://numbers", 15074 Reason: "Completed", 15075 StartedAt: metav1.NewTime(time.Now()), 15076 FinishedAt: metav1.NewTime(time.Now()), 15077 }, 15078 }, 15079 }}, 15080 EphemeralContainerStatuses: []core.ContainerStatus{{ 15081 ContainerID: "docker://numbers", 15082 Image: "busybox", 15083 ImageID: "docker-pullable://busybox@sha256:d0gf00d", 15084 Name: "debug", 15085 Ready: false, 15086 State: core.ContainerState{ 15087 Terminated: &core.ContainerStateTerminated{ 15088 ContainerID: "docker://numbers", 15089 Reason: "Completed", 15090 StartedAt: metav1.NewTime(time.Now()), 15091 FinishedAt: metav1.NewTime(time.Now()), 15092 }, 15093 }, 15094 }}, 15095 }, 15096 }, 15097 core.Pod{ 15098 ObjectMeta: metav1.ObjectMeta{ 15099 Name: "foo", 15100 }, 15101 Status: core.PodStatus{ 15102 InitContainerStatuses: []core.ContainerStatus{{ 15103 ContainerID: "docker://numbers", 15104 Image: "alpine", 15105 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15106 Name: "init", 15107 Ready: true, 15108 State: core.ContainerState{ 15109 Terminated: &core.ContainerStateTerminated{ 15110 ContainerID: "docker://numbers", 15111 Reason: "Completed", 15112 }, 15113 }, 15114 }}, 15115 ContainerStatuses: []core.ContainerStatus{{ 15116 ContainerID: "docker://numbers", 15117 Image: "nginx:alpine", 15118 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15119 Name: "nginx", 15120 Ready: true, 15121 Started: proto.Bool(true), 15122 State: core.ContainerState{ 15123 Running: &core.ContainerStateRunning{ 15124 StartedAt: metav1.NewTime(time.Now()), 15125 }, 15126 }, 15127 }}, 15128 EphemeralContainerStatuses: []core.ContainerStatus{{ 15129 ContainerID: "docker://numbers", 15130 Image: "busybox", 15131 ImageID: "docker-pullable://busybox@sha256:d0gf00d", 15132 Name: "debug", 15133 Ready: false, 15134 State: core.ContainerState{ 15135 Running: &core.ContainerStateRunning{ 15136 StartedAt: metav1.NewTime(time.Now()), 15137 }, 15138 }, 15139 }}, 15140 }, 15141 }, 15142 "", 15143 "Container statuses all containers terminated", 15144 }, { 15145 core.Pod{ 15146 ObjectMeta: metav1.ObjectMeta{ 15147 Name: "foo", 15148 }, 15149 Status: core.PodStatus{ 15150 ResourceClaimStatuses: []core.PodResourceClaimStatus{ 15151 {Name: "no-such-claim", ResourceClaimName: utilpointer.String("my-claim")}, 15152 }, 15153 }, 15154 }, 15155 core.Pod{ 15156 ObjectMeta: metav1.ObjectMeta{ 15157 Name: "foo", 15158 }, 15159 }, 15160 "status.resourceClaimStatuses[0].name: Invalid value: \"no-such-claim\": must match the name of an entry in `spec.resourceClaims`", 15161 "Non-existent PodResourceClaim", 15162 }, { 15163 core.Pod{ 15164 ObjectMeta: metav1.ObjectMeta{ 15165 Name: "foo", 15166 }, 15167 Spec: core.PodSpec{ 15168 ResourceClaims: []core.PodResourceClaim{ 15169 {Name: "my-claim"}, 15170 }, 15171 }, 15172 Status: core.PodStatus{ 15173 ResourceClaimStatuses: []core.PodResourceClaimStatus{ 15174 {Name: "my-claim", ResourceClaimName: utilpointer.String("%$!#")}, 15175 }, 15176 }, 15177 }, 15178 core.Pod{ 15179 ObjectMeta: metav1.ObjectMeta{ 15180 Name: "foo", 15181 }, 15182 Spec: core.PodSpec{ 15183 ResourceClaims: []core.PodResourceClaim{ 15184 {Name: "my-claim"}, 15185 }, 15186 }, 15187 }, 15188 `status.resourceClaimStatuses[0].name: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`, 15189 "Invalid ResourceClaim name", 15190 }, { 15191 core.Pod{ 15192 ObjectMeta: metav1.ObjectMeta{ 15193 Name: "foo", 15194 }, 15195 Spec: core.PodSpec{ 15196 ResourceClaims: []core.PodResourceClaim{ 15197 {Name: "my-claim"}, 15198 {Name: "my-other-claim"}, 15199 }, 15200 }, 15201 Status: core.PodStatus{ 15202 ResourceClaimStatuses: []core.PodResourceClaimStatus{ 15203 {Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")}, 15204 {Name: "my-other-claim", ResourceClaimName: nil}, 15205 {Name: "my-other-claim", ResourceClaimName: nil}, 15206 }, 15207 }, 15208 }, 15209 core.Pod{ 15210 ObjectMeta: metav1.ObjectMeta{ 15211 Name: "foo", 15212 }, 15213 Spec: core.PodSpec{ 15214 ResourceClaims: []core.PodResourceClaim{ 15215 {Name: "my-claim"}, 15216 }, 15217 }, 15218 }, 15219 `status.resourceClaimStatuses[2].name: Duplicate value: "my-other-claim"`, 15220 "Duplicate ResourceClaimStatuses.Name", 15221 }, { 15222 core.Pod{ 15223 ObjectMeta: metav1.ObjectMeta{ 15224 Name: "foo", 15225 }, 15226 Spec: core.PodSpec{ 15227 ResourceClaims: []core.PodResourceClaim{ 15228 {Name: "my-claim"}, 15229 {Name: "my-other-claim"}, 15230 }, 15231 }, 15232 Status: core.PodStatus{ 15233 ResourceClaimStatuses: []core.PodResourceClaimStatus{ 15234 {Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")}, 15235 {Name: "my-other-claim", ResourceClaimName: nil}, 15236 }, 15237 }, 15238 }, 15239 core.Pod{ 15240 ObjectMeta: metav1.ObjectMeta{ 15241 Name: "foo", 15242 }, 15243 Spec: core.PodSpec{ 15244 ResourceClaims: []core.PodResourceClaim{ 15245 {Name: "my-claim"}, 15246 }, 15247 }, 15248 }, 15249 "", 15250 "ResourceClaimStatuses okay", 15251 }, { 15252 core.Pod{ 15253 ObjectMeta: metav1.ObjectMeta{ 15254 Name: "foo", 15255 }, 15256 Spec: core.PodSpec{ 15257 InitContainers: []core.Container{ 15258 { 15259 Name: "init", 15260 }, 15261 }, 15262 Containers: []core.Container{ 15263 { 15264 Name: "nginx", 15265 }, 15266 }, 15267 }, 15268 Status: core.PodStatus{ 15269 InitContainerStatuses: []core.ContainerStatus{{ 15270 ContainerID: "docker://numbers", 15271 Image: "alpine", 15272 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15273 Name: "init", 15274 Ready: true, 15275 State: core.ContainerState{ 15276 Running: &core.ContainerStateRunning{ 15277 StartedAt: metav1.NewTime(time.Now()), 15278 }, 15279 }, 15280 }}, 15281 ContainerStatuses: []core.ContainerStatus{{ 15282 ContainerID: "docker://numbers", 15283 Image: "nginx:alpine", 15284 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15285 Name: "nginx", 15286 Ready: true, 15287 Started: proto.Bool(true), 15288 State: core.ContainerState{ 15289 Running: &core.ContainerStateRunning{ 15290 StartedAt: metav1.NewTime(time.Now()), 15291 }, 15292 }, 15293 }}, 15294 }, 15295 }, 15296 core.Pod{ 15297 ObjectMeta: metav1.ObjectMeta{ 15298 Name: "foo", 15299 }, 15300 Spec: core.PodSpec{ 15301 InitContainers: []core.Container{ 15302 { 15303 Name: "init", 15304 }, 15305 }, 15306 Containers: []core.Container{ 15307 { 15308 Name: "nginx", 15309 }, 15310 }, 15311 RestartPolicy: core.RestartPolicyNever, 15312 }, 15313 Status: core.PodStatus{ 15314 InitContainerStatuses: []core.ContainerStatus{{ 15315 ContainerID: "docker://numbers", 15316 Image: "alpine", 15317 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15318 Name: "init", 15319 Ready: false, 15320 State: core.ContainerState{ 15321 Terminated: &core.ContainerStateTerminated{ 15322 ContainerID: "docker://numbers", 15323 Reason: "Completed", 15324 }, 15325 }, 15326 }}, 15327 ContainerStatuses: []core.ContainerStatus{{ 15328 ContainerID: "docker://numbers", 15329 Image: "nginx:alpine", 15330 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15331 Name: "nginx", 15332 Ready: true, 15333 Started: proto.Bool(true), 15334 State: core.ContainerState{ 15335 Running: &core.ContainerStateRunning{ 15336 StartedAt: metav1.NewTime(time.Now()), 15337 }, 15338 }, 15339 }}, 15340 }, 15341 }, 15342 `status.initContainerStatuses[0].state: Forbidden: may not be transitioned to non-terminated state`, 15343 "init container cannot restart if RestartPolicyNever", 15344 }, { 15345 core.Pod{ 15346 ObjectMeta: metav1.ObjectMeta{ 15347 Name: "foo", 15348 }, 15349 Spec: core.PodSpec{ 15350 InitContainers: []core.Container{ 15351 { 15352 Name: "restartable-init", 15353 RestartPolicy: &containerRestartPolicyAlways, 15354 }, 15355 }, 15356 Containers: []core.Container{ 15357 { 15358 Name: "nginx", 15359 }, 15360 }, 15361 RestartPolicy: core.RestartPolicyNever, 15362 }, 15363 Status: core.PodStatus{ 15364 InitContainerStatuses: []core.ContainerStatus{{ 15365 ContainerID: "docker://numbers", 15366 Image: "alpine", 15367 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15368 Name: "restartable-init", 15369 Ready: true, 15370 State: core.ContainerState{ 15371 Running: &core.ContainerStateRunning{ 15372 StartedAt: metav1.NewTime(time.Now()), 15373 }, 15374 }, 15375 }}, 15376 ContainerStatuses: []core.ContainerStatus{{ 15377 ContainerID: "docker://numbers", 15378 Image: "nginx:alpine", 15379 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15380 Name: "nginx", 15381 Ready: true, 15382 Started: proto.Bool(true), 15383 State: core.ContainerState{ 15384 Running: &core.ContainerStateRunning{ 15385 StartedAt: metav1.NewTime(time.Now()), 15386 }, 15387 }, 15388 }}, 15389 }, 15390 }, 15391 core.Pod{ 15392 ObjectMeta: metav1.ObjectMeta{ 15393 Name: "foo", 15394 }, 15395 Spec: core.PodSpec{ 15396 InitContainers: []core.Container{ 15397 { 15398 Name: "restartable-init", 15399 RestartPolicy: &containerRestartPolicyAlways, 15400 }, 15401 }, 15402 Containers: []core.Container{ 15403 { 15404 Name: "nginx", 15405 }, 15406 }, 15407 RestartPolicy: core.RestartPolicyNever, 15408 }, 15409 Status: core.PodStatus{ 15410 InitContainerStatuses: []core.ContainerStatus{{ 15411 ContainerID: "docker://numbers", 15412 Image: "alpine", 15413 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15414 Name: "restartable-init", 15415 Ready: false, 15416 State: core.ContainerState{ 15417 Terminated: &core.ContainerStateTerminated{ 15418 ContainerID: "docker://numbers", 15419 Reason: "Completed", 15420 }, 15421 }, 15422 }}, 15423 ContainerStatuses: []core.ContainerStatus{{ 15424 ContainerID: "docker://numbers", 15425 Image: "nginx:alpine", 15426 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15427 Name: "nginx", 15428 Ready: true, 15429 Started: proto.Bool(true), 15430 State: core.ContainerState{ 15431 Running: &core.ContainerStateRunning{ 15432 StartedAt: metav1.NewTime(time.Now()), 15433 }, 15434 }, 15435 }}, 15436 }, 15437 }, 15438 "", 15439 "restartable init container can restart if RestartPolicyNever", 15440 }, { 15441 core.Pod{ 15442 ObjectMeta: metav1.ObjectMeta{ 15443 Name: "foo", 15444 }, 15445 Spec: core.PodSpec{ 15446 InitContainers: []core.Container{ 15447 { 15448 Name: "restartable-init", 15449 RestartPolicy: &containerRestartPolicyAlways, 15450 }, 15451 }, 15452 Containers: []core.Container{ 15453 { 15454 Name: "nginx", 15455 }, 15456 }, 15457 RestartPolicy: core.RestartPolicyOnFailure, 15458 }, 15459 Status: core.PodStatus{ 15460 InitContainerStatuses: []core.ContainerStatus{{ 15461 ContainerID: "docker://numbers", 15462 Image: "alpine", 15463 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15464 Name: "restartable-init", 15465 Ready: true, 15466 State: core.ContainerState{ 15467 Running: &core.ContainerStateRunning{ 15468 StartedAt: metav1.NewTime(time.Now()), 15469 }, 15470 }, 15471 }}, 15472 ContainerStatuses: []core.ContainerStatus{{ 15473 ContainerID: "docker://numbers", 15474 Image: "nginx:alpine", 15475 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15476 Name: "nginx", 15477 Ready: true, 15478 Started: proto.Bool(true), 15479 State: core.ContainerState{ 15480 Running: &core.ContainerStateRunning{ 15481 StartedAt: metav1.NewTime(time.Now()), 15482 }, 15483 }, 15484 }}, 15485 }, 15486 }, 15487 core.Pod{ 15488 ObjectMeta: metav1.ObjectMeta{ 15489 Name: "foo", 15490 }, 15491 Spec: core.PodSpec{ 15492 InitContainers: []core.Container{ 15493 { 15494 Name: "restartable-init", 15495 RestartPolicy: &containerRestartPolicyAlways, 15496 }, 15497 }, 15498 Containers: []core.Container{ 15499 { 15500 Name: "nginx", 15501 }, 15502 }, 15503 RestartPolicy: core.RestartPolicyOnFailure, 15504 }, 15505 Status: core.PodStatus{ 15506 InitContainerStatuses: []core.ContainerStatus{{ 15507 ContainerID: "docker://numbers", 15508 Image: "alpine", 15509 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15510 Name: "restartable-init", 15511 Ready: false, 15512 State: core.ContainerState{ 15513 Terminated: &core.ContainerStateTerminated{ 15514 ContainerID: "docker://numbers", 15515 Reason: "Completed", 15516 }, 15517 }, 15518 }}, 15519 ContainerStatuses: []core.ContainerStatus{{ 15520 ContainerID: "docker://numbers", 15521 Image: "nginx:alpine", 15522 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15523 Name: "nginx", 15524 Ready: true, 15525 Started: proto.Bool(true), 15526 State: core.ContainerState{ 15527 Running: &core.ContainerStateRunning{ 15528 StartedAt: metav1.NewTime(time.Now()), 15529 }, 15530 }, 15531 }}, 15532 }, 15533 }, 15534 "", 15535 "restartable init container can restart if RestartPolicyOnFailure", 15536 }, { 15537 core.Pod{ 15538 ObjectMeta: metav1.ObjectMeta{ 15539 Name: "foo", 15540 }, 15541 Spec: core.PodSpec{ 15542 InitContainers: []core.Container{ 15543 { 15544 Name: "restartable-init", 15545 RestartPolicy: &containerRestartPolicyAlways, 15546 }, 15547 }, 15548 Containers: []core.Container{ 15549 { 15550 Name: "nginx", 15551 }, 15552 }, 15553 RestartPolicy: core.RestartPolicyAlways, 15554 }, 15555 Status: core.PodStatus{ 15556 InitContainerStatuses: []core.ContainerStatus{{ 15557 ContainerID: "docker://numbers", 15558 Image: "alpine", 15559 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15560 Name: "restartable-init", 15561 Ready: true, 15562 State: core.ContainerState{ 15563 Running: &core.ContainerStateRunning{ 15564 StartedAt: metav1.NewTime(time.Now()), 15565 }, 15566 }, 15567 }}, 15568 ContainerStatuses: []core.ContainerStatus{{ 15569 ContainerID: "docker://numbers", 15570 Image: "nginx:alpine", 15571 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15572 Name: "nginx", 15573 Ready: true, 15574 Started: proto.Bool(true), 15575 State: core.ContainerState{ 15576 Running: &core.ContainerStateRunning{ 15577 StartedAt: metav1.NewTime(time.Now()), 15578 }, 15579 }, 15580 }}, 15581 }, 15582 }, 15583 core.Pod{ 15584 ObjectMeta: metav1.ObjectMeta{ 15585 Name: "foo", 15586 }, 15587 Spec: core.PodSpec{ 15588 InitContainers: []core.Container{ 15589 { 15590 Name: "restartable-init", 15591 RestartPolicy: &containerRestartPolicyAlways, 15592 }, 15593 }, 15594 Containers: []core.Container{ 15595 { 15596 Name: "nginx", 15597 }, 15598 }, 15599 RestartPolicy: core.RestartPolicyAlways, 15600 }, 15601 Status: core.PodStatus{ 15602 InitContainerStatuses: []core.ContainerStatus{{ 15603 ContainerID: "docker://numbers", 15604 Image: "alpine", 15605 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15606 Name: "restartable-init", 15607 Ready: false, 15608 State: core.ContainerState{ 15609 Terminated: &core.ContainerStateTerminated{ 15610 ContainerID: "docker://numbers", 15611 Reason: "Completed", 15612 }, 15613 }, 15614 }}, 15615 ContainerStatuses: []core.ContainerStatus{{ 15616 ContainerID: "docker://numbers", 15617 Image: "nginx:alpine", 15618 ImageID: "docker-pullable://nginx@sha256:d0gf00d", 15619 Name: "nginx", 15620 Ready: true, 15621 Started: proto.Bool(true), 15622 State: core.ContainerState{ 15623 Running: &core.ContainerStateRunning{ 15624 StartedAt: metav1.NewTime(time.Now()), 15625 }, 15626 }, 15627 }}, 15628 }, 15629 }, 15630 "", 15631 "restartable init container can restart if RestartPolicyAlways", 15632 }, 15633 } 15634 15635 for _, test := range tests { 15636 test.new.ObjectMeta.ResourceVersion = "1" 15637 test.old.ObjectMeta.ResourceVersion = "1" 15638 errs := ValidatePodStatusUpdate(&test.new, &test.old, PodValidationOptions{}) 15639 if test.err == "" { 15640 if len(errs) != 0 { 15641 t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old) 15642 } 15643 } else { 15644 if len(errs) == 0 { 15645 t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old) 15646 } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) { 15647 t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr) 15648 } 15649 } 15650 } 15651 } 15652 15653 func makeValidService() core.Service { 15654 clusterInternalTrafficPolicy := core.ServiceInternalTrafficPolicyCluster 15655 return core.Service{ 15656 ObjectMeta: metav1.ObjectMeta{ 15657 Name: "valid", 15658 Namespace: "valid", 15659 Labels: map[string]string{}, 15660 Annotations: map[string]string{}, 15661 ResourceVersion: "1", 15662 }, 15663 Spec: core.ServiceSpec{ 15664 Selector: map[string]string{"key": "val"}, 15665 SessionAffinity: "None", 15666 Type: core.ServiceTypeClusterIP, 15667 Ports: []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt32(8675)}}, 15668 InternalTrafficPolicy: &clusterInternalTrafficPolicy, 15669 }, 15670 } 15671 } 15672 15673 func TestValidatePodEphemeralContainersUpdate(t *testing.T) { 15674 makePod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod { 15675 return &core.Pod{ 15676 ObjectMeta: metav1.ObjectMeta{ 15677 Annotations: map[string]string{}, 15678 Labels: map[string]string{}, 15679 Name: "pod", 15680 Namespace: "ns", 15681 ResourceVersion: "1", 15682 }, 15683 Spec: core.PodSpec{ 15684 Containers: []core.Container{{ 15685 Name: "cnt", 15686 Image: "image", 15687 ImagePullPolicy: "IfNotPresent", 15688 TerminationMessagePolicy: "File", 15689 }}, 15690 DNSPolicy: core.DNSClusterFirst, 15691 EphemeralContainers: ephemeralContainers, 15692 RestartPolicy: core.RestartPolicyOnFailure, 15693 }, 15694 } 15695 } 15696 15697 // Some tests use Windows host pods as an example of fields that might 15698 // conflict between an ephemeral container and the rest of the pod. 15699 capabilities.SetForTests(capabilities.Capabilities{ 15700 AllowPrivileged: true, 15701 }) 15702 makeWindowsHostPod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod { 15703 return &core.Pod{ 15704 ObjectMeta: metav1.ObjectMeta{ 15705 Annotations: map[string]string{}, 15706 Labels: map[string]string{}, 15707 Name: "pod", 15708 Namespace: "ns", 15709 ResourceVersion: "1", 15710 }, 15711 Spec: core.PodSpec{ 15712 Containers: []core.Container{{ 15713 Name: "cnt", 15714 Image: "image", 15715 ImagePullPolicy: "IfNotPresent", 15716 SecurityContext: &core.SecurityContext{ 15717 WindowsOptions: &core.WindowsSecurityContextOptions{ 15718 HostProcess: proto.Bool(true), 15719 }, 15720 }, 15721 TerminationMessagePolicy: "File", 15722 }}, 15723 DNSPolicy: core.DNSClusterFirst, 15724 EphemeralContainers: ephemeralContainers, 15725 RestartPolicy: core.RestartPolicyOnFailure, 15726 SecurityContext: &core.PodSecurityContext{ 15727 HostNetwork: true, 15728 WindowsOptions: &core.WindowsSecurityContextOptions{ 15729 HostProcess: proto.Bool(true), 15730 }, 15731 }, 15732 }, 15733 } 15734 } 15735 15736 tests := []struct { 15737 name string 15738 new, old *core.Pod 15739 err string 15740 }{{ 15741 "no ephemeral containers", 15742 makePod([]core.EphemeralContainer{}), 15743 makePod([]core.EphemeralContainer{}), 15744 "", 15745 }, { 15746 "No change in Ephemeral Containers", 15747 makePod([]core.EphemeralContainer{{ 15748 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15749 Name: "debugger", 15750 Image: "busybox", 15751 ImagePullPolicy: "IfNotPresent", 15752 TerminationMessagePolicy: "File", 15753 }, 15754 }, { 15755 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15756 Name: "debugger2", 15757 Image: "busybox", 15758 ImagePullPolicy: "IfNotPresent", 15759 TerminationMessagePolicy: "File", 15760 }, 15761 }}), 15762 makePod([]core.EphemeralContainer{{ 15763 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15764 Name: "debugger", 15765 Image: "busybox", 15766 ImagePullPolicy: "IfNotPresent", 15767 TerminationMessagePolicy: "File", 15768 }, 15769 }, { 15770 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15771 Name: "debugger2", 15772 Image: "busybox", 15773 ImagePullPolicy: "IfNotPresent", 15774 TerminationMessagePolicy: "File", 15775 }, 15776 }}), 15777 "", 15778 }, { 15779 "Ephemeral Container list order changes", 15780 makePod([]core.EphemeralContainer{{ 15781 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15782 Name: "debugger", 15783 Image: "busybox", 15784 ImagePullPolicy: "IfNotPresent", 15785 TerminationMessagePolicy: "File", 15786 }, 15787 }, { 15788 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15789 Name: "debugger2", 15790 Image: "busybox", 15791 ImagePullPolicy: "IfNotPresent", 15792 TerminationMessagePolicy: "File", 15793 }, 15794 }}), 15795 makePod([]core.EphemeralContainer{{ 15796 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15797 Name: "debugger2", 15798 Image: "busybox", 15799 ImagePullPolicy: "IfNotPresent", 15800 TerminationMessagePolicy: "File", 15801 }, 15802 }, { 15803 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15804 Name: "debugger", 15805 Image: "busybox", 15806 ImagePullPolicy: "IfNotPresent", 15807 TerminationMessagePolicy: "File", 15808 }, 15809 }}), 15810 "", 15811 }, { 15812 "Add an Ephemeral Container", 15813 makePod([]core.EphemeralContainer{{ 15814 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15815 Name: "debugger", 15816 Image: "busybox", 15817 ImagePullPolicy: "IfNotPresent", 15818 TerminationMessagePolicy: "File", 15819 }, 15820 }}), 15821 makePod([]core.EphemeralContainer{}), 15822 "", 15823 }, { 15824 "Add two Ephemeral Containers", 15825 makePod([]core.EphemeralContainer{{ 15826 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15827 Name: "debugger1", 15828 Image: "busybox", 15829 ImagePullPolicy: "IfNotPresent", 15830 TerminationMessagePolicy: "File", 15831 }, 15832 }, { 15833 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15834 Name: "debugger2", 15835 Image: "busybox", 15836 ImagePullPolicy: "IfNotPresent", 15837 TerminationMessagePolicy: "File", 15838 }, 15839 }}), 15840 makePod([]core.EphemeralContainer{}), 15841 "", 15842 }, { 15843 "Add to an existing Ephemeral Containers", 15844 makePod([]core.EphemeralContainer{{ 15845 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15846 Name: "debugger", 15847 Image: "busybox", 15848 ImagePullPolicy: "IfNotPresent", 15849 TerminationMessagePolicy: "File", 15850 }, 15851 }, { 15852 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15853 Name: "debugger2", 15854 Image: "busybox", 15855 ImagePullPolicy: "IfNotPresent", 15856 TerminationMessagePolicy: "File", 15857 }, 15858 }}), 15859 makePod([]core.EphemeralContainer{{ 15860 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15861 Name: "debugger", 15862 Image: "busybox", 15863 ImagePullPolicy: "IfNotPresent", 15864 TerminationMessagePolicy: "File", 15865 }, 15866 }}), 15867 "", 15868 }, { 15869 "Add to an existing Ephemeral Containers, list order changes", 15870 makePod([]core.EphemeralContainer{{ 15871 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15872 Name: "debugger3", 15873 Image: "busybox", 15874 ImagePullPolicy: "IfNotPresent", 15875 TerminationMessagePolicy: "File", 15876 }, 15877 }, { 15878 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15879 Name: "debugger2", 15880 Image: "busybox", 15881 ImagePullPolicy: "IfNotPresent", 15882 TerminationMessagePolicy: "File", 15883 }, 15884 }, { 15885 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15886 Name: "debugger", 15887 Image: "busybox", 15888 ImagePullPolicy: "IfNotPresent", 15889 TerminationMessagePolicy: "File", 15890 }, 15891 }}), 15892 makePod([]core.EphemeralContainer{{ 15893 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15894 Name: "debugger", 15895 Image: "busybox", 15896 ImagePullPolicy: "IfNotPresent", 15897 TerminationMessagePolicy: "File", 15898 }, 15899 }, { 15900 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15901 Name: "debugger2", 15902 Image: "busybox", 15903 ImagePullPolicy: "IfNotPresent", 15904 TerminationMessagePolicy: "File", 15905 }, 15906 }}), 15907 "", 15908 }, { 15909 "Remove an Ephemeral Container", 15910 makePod([]core.EphemeralContainer{}), 15911 makePod([]core.EphemeralContainer{{ 15912 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15913 Name: "debugger", 15914 Image: "busybox", 15915 ImagePullPolicy: "IfNotPresent", 15916 TerminationMessagePolicy: "File", 15917 }, 15918 }}), 15919 "may not be removed", 15920 }, { 15921 "Replace an Ephemeral Container", 15922 makePod([]core.EphemeralContainer{{ 15923 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15924 Name: "firstone", 15925 Image: "busybox", 15926 ImagePullPolicy: "IfNotPresent", 15927 TerminationMessagePolicy: "File", 15928 }, 15929 }}), 15930 makePod([]core.EphemeralContainer{{ 15931 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15932 Name: "thentheother", 15933 Image: "busybox", 15934 ImagePullPolicy: "IfNotPresent", 15935 TerminationMessagePolicy: "File", 15936 }, 15937 }}), 15938 "may not be removed", 15939 }, { 15940 "Change an Ephemeral Containers", 15941 makePod([]core.EphemeralContainer{{ 15942 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15943 Name: "debugger1", 15944 Image: "busybox", 15945 ImagePullPolicy: "IfNotPresent", 15946 TerminationMessagePolicy: "File", 15947 }, 15948 }, { 15949 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15950 Name: "debugger2", 15951 Image: "busybox", 15952 ImagePullPolicy: "IfNotPresent", 15953 TerminationMessagePolicy: "File", 15954 }, 15955 }}), 15956 makePod([]core.EphemeralContainer{{ 15957 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15958 Name: "debugger1", 15959 Image: "debian", 15960 ImagePullPolicy: "IfNotPresent", 15961 TerminationMessagePolicy: "File", 15962 }, 15963 }, { 15964 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15965 Name: "debugger2", 15966 Image: "busybox", 15967 ImagePullPolicy: "IfNotPresent", 15968 TerminationMessagePolicy: "File", 15969 }, 15970 }}), 15971 "may not be changed", 15972 }, { 15973 "Ephemeral container with potential conflict with regular containers, but conflict not present", 15974 makeWindowsHostPod([]core.EphemeralContainer{{ 15975 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15976 Name: "debugger1", 15977 Image: "image", 15978 ImagePullPolicy: "IfNotPresent", 15979 SecurityContext: &core.SecurityContext{ 15980 WindowsOptions: &core.WindowsSecurityContextOptions{ 15981 HostProcess: proto.Bool(true), 15982 }, 15983 }, 15984 TerminationMessagePolicy: "File", 15985 }, 15986 }}), 15987 makeWindowsHostPod(nil), 15988 "", 15989 }, { 15990 "Ephemeral container with potential conflict with regular containers, and conflict is present", 15991 makeWindowsHostPod([]core.EphemeralContainer{{ 15992 EphemeralContainerCommon: core.EphemeralContainerCommon{ 15993 Name: "debugger1", 15994 Image: "image", 15995 ImagePullPolicy: "IfNotPresent", 15996 SecurityContext: &core.SecurityContext{ 15997 WindowsOptions: &core.WindowsSecurityContextOptions{ 15998 HostProcess: proto.Bool(false), 15999 }, 16000 }, 16001 TerminationMessagePolicy: "File", 16002 }, 16003 }}), 16004 makeWindowsHostPod(nil), 16005 "spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical", 16006 }, { 16007 "Add ephemeral container to static pod", 16008 func() *core.Pod { 16009 p := makePod(nil) 16010 p.Spec.NodeName = "some-name" 16011 p.ObjectMeta.Annotations = map[string]string{ 16012 core.MirrorPodAnnotationKey: "foo", 16013 } 16014 p.Spec.EphemeralContainers = []core.EphemeralContainer{{ 16015 EphemeralContainerCommon: core.EphemeralContainerCommon{ 16016 Name: "debugger1", 16017 Image: "debian", 16018 ImagePullPolicy: "IfNotPresent", 16019 TerminationMessagePolicy: "File", 16020 }, 16021 }} 16022 return p 16023 }(), 16024 func() *core.Pod { 16025 p := makePod(nil) 16026 p.Spec.NodeName = "some-name" 16027 p.ObjectMeta.Annotations = map[string]string{ 16028 core.MirrorPodAnnotationKey: "foo", 16029 } 16030 return p 16031 }(), 16032 "Forbidden: static pods do not support ephemeral containers", 16033 }, 16034 } 16035 16036 for _, tc := range tests { 16037 errs := ValidatePodEphemeralContainersUpdate(tc.new, tc.old, PodValidationOptions{}) 16038 if tc.err == "" { 16039 if len(errs) != 0 { 16040 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)) 16041 } 16042 } else { 16043 if len(errs) == 0 { 16044 t.Errorf("unexpected valid for test: %s\nLocal diff of test objects (-old +new):\n%s", tc.name, cmp.Diff(tc.old, tc.new)) 16045 } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, tc.err) { 16046 t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", tc.name, tc.err, actualErr) 16047 } 16048 } 16049 } 16050 } 16051 16052 func TestValidateServiceCreate(t *testing.T) { 16053 requireDualStack := core.IPFamilyPolicyRequireDualStack 16054 singleStack := core.IPFamilyPolicySingleStack 16055 preferDualStack := core.IPFamilyPolicyPreferDualStack 16056 16057 testCases := []struct { 16058 name string 16059 tweakSvc func(svc *core.Service) // given a basic valid service, each test case can customize it 16060 numErrs int 16061 featureGates []featuregate.Feature 16062 }{{ 16063 name: "missing namespace", 16064 tweakSvc: func(s *core.Service) { 16065 s.Namespace = "" 16066 }, 16067 numErrs: 1, 16068 }, { 16069 name: "invalid namespace", 16070 tweakSvc: func(s *core.Service) { 16071 s.Namespace = "-123" 16072 }, 16073 numErrs: 1, 16074 }, { 16075 name: "missing name", 16076 tweakSvc: func(s *core.Service) { 16077 s.Name = "" 16078 }, 16079 numErrs: 1, 16080 }, { 16081 name: "invalid name", 16082 tweakSvc: func(s *core.Service) { 16083 s.Name = "-123" 16084 }, 16085 numErrs: 1, 16086 }, { 16087 name: "too long name", 16088 tweakSvc: func(s *core.Service) { 16089 s.Name = strings.Repeat("a", 64) 16090 }, 16091 numErrs: 1, 16092 }, { 16093 name: "invalid generateName", 16094 tweakSvc: func(s *core.Service) { 16095 s.GenerateName = "-123" 16096 }, 16097 numErrs: 1, 16098 }, { 16099 name: "too long generateName", 16100 tweakSvc: func(s *core.Service) { 16101 s.GenerateName = strings.Repeat("a", 64) 16102 }, 16103 numErrs: 1, 16104 }, { 16105 name: "invalid label", 16106 tweakSvc: func(s *core.Service) { 16107 s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar" 16108 }, 16109 numErrs: 1, 16110 }, { 16111 name: "invalid annotation", 16112 tweakSvc: func(s *core.Service) { 16113 s.Annotations["NoSpecialCharsLike=Equals"] = "bar" 16114 }, 16115 numErrs: 1, 16116 }, { 16117 name: "nil selector", 16118 tweakSvc: func(s *core.Service) { 16119 s.Spec.Selector = nil 16120 }, 16121 numErrs: 0, 16122 }, { 16123 name: "invalid selector", 16124 tweakSvc: func(s *core.Service) { 16125 s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar" 16126 }, 16127 numErrs: 1, 16128 }, { 16129 name: "missing session affinity", 16130 tweakSvc: func(s *core.Service) { 16131 s.Spec.SessionAffinity = "" 16132 }, 16133 numErrs: 1, 16134 }, { 16135 name: "missing type", 16136 tweakSvc: func(s *core.Service) { 16137 s.Spec.Type = "" 16138 }, 16139 numErrs: 1, 16140 }, { 16141 name: "missing ports", 16142 tweakSvc: func(s *core.Service) { 16143 s.Spec.Ports = nil 16144 }, 16145 numErrs: 1, 16146 }, { 16147 name: "missing ports but headless", 16148 tweakSvc: func(s *core.Service) { 16149 s.Spec.Ports = nil 16150 s.Spec.ClusterIP = core.ClusterIPNone 16151 s.Spec.ClusterIPs = []string{core.ClusterIPNone} 16152 }, 16153 numErrs: 0, 16154 }, { 16155 name: "empty port[0] name", 16156 tweakSvc: func(s *core.Service) { 16157 s.Spec.Ports[0].Name = "" 16158 }, 16159 numErrs: 0, 16160 }, { 16161 name: "empty port[1] name", 16162 tweakSvc: func(s *core.Service) { 16163 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)}) 16164 }, 16165 numErrs: 1, 16166 }, { 16167 name: "empty multi-port port[0] name", 16168 tweakSvc: func(s *core.Service) { 16169 s.Spec.Ports[0].Name = "" 16170 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)}) 16171 }, 16172 numErrs: 1, 16173 }, { 16174 name: "invalid port name", 16175 tweakSvc: func(s *core.Service) { 16176 s.Spec.Ports[0].Name = "INVALID" 16177 }, 16178 numErrs: 1, 16179 }, { 16180 name: "missing protocol", 16181 tweakSvc: func(s *core.Service) { 16182 s.Spec.Ports[0].Protocol = "" 16183 }, 16184 numErrs: 1, 16185 }, { 16186 name: "invalid protocol", 16187 tweakSvc: func(s *core.Service) { 16188 s.Spec.Ports[0].Protocol = "INVALID" 16189 }, 16190 numErrs: 1, 16191 }, { 16192 name: "invalid cluster ip", 16193 tweakSvc: func(s *core.Service) { 16194 s.Spec.ClusterIP = "invalid" 16195 s.Spec.ClusterIPs = []string{"invalid"} 16196 }, 16197 numErrs: 1, 16198 }, { 16199 name: "missing port", 16200 tweakSvc: func(s *core.Service) { 16201 s.Spec.Ports[0].Port = 0 16202 }, 16203 numErrs: 1, 16204 }, { 16205 name: "invalid port", 16206 tweakSvc: func(s *core.Service) { 16207 s.Spec.Ports[0].Port = 65536 16208 }, 16209 numErrs: 1, 16210 }, { 16211 name: "invalid TargetPort int", 16212 tweakSvc: func(s *core.Service) { 16213 s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536) 16214 }, 16215 numErrs: 1, 16216 }, { 16217 name: "valid port headless", 16218 tweakSvc: func(s *core.Service) { 16219 s.Spec.Ports[0].Port = 11722 16220 s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722) 16221 s.Spec.ClusterIP = core.ClusterIPNone 16222 s.Spec.ClusterIPs = []string{core.ClusterIPNone} 16223 }, 16224 numErrs: 0, 16225 }, { 16226 name: "invalid port headless 1", 16227 tweakSvc: func(s *core.Service) { 16228 s.Spec.Ports[0].Port = 11722 16229 s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721) 16230 s.Spec.ClusterIP = core.ClusterIPNone 16231 s.Spec.ClusterIPs = []string{core.ClusterIPNone} 16232 }, 16233 // in the v1 API, targetPorts on headless services were tolerated. 16234 // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. 16235 // numErrs: 1, 16236 numErrs: 0, 16237 }, { 16238 name: "invalid port headless 2", 16239 tweakSvc: func(s *core.Service) { 16240 s.Spec.Ports[0].Port = 11722 16241 s.Spec.Ports[0].TargetPort = intstr.FromString("target") 16242 s.Spec.ClusterIP = core.ClusterIPNone 16243 s.Spec.ClusterIPs = []string{core.ClusterIPNone} 16244 }, 16245 // in the v1 API, targetPorts on headless services were tolerated. 16246 // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. 16247 // numErrs: 1, 16248 numErrs: 0, 16249 }, { 16250 name: "invalid publicIPs localhost", 16251 tweakSvc: func(s *core.Service) { 16252 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16253 s.Spec.ExternalIPs = []string{"127.0.0.1"} 16254 }, 16255 numErrs: 1, 16256 }, { 16257 name: "invalid publicIPs unspecified", 16258 tweakSvc: func(s *core.Service) { 16259 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16260 s.Spec.ExternalIPs = []string{"0.0.0.0"} 16261 }, 16262 numErrs: 1, 16263 }, { 16264 name: "invalid publicIPs loopback", 16265 tweakSvc: func(s *core.Service) { 16266 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16267 s.Spec.ExternalIPs = []string{"127.0.0.1"} 16268 }, 16269 numErrs: 1, 16270 }, { 16271 name: "invalid publicIPs host", 16272 tweakSvc: func(s *core.Service) { 16273 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16274 s.Spec.ExternalIPs = []string{"myhost.mydomain"} 16275 }, 16276 numErrs: 1, 16277 }, { 16278 name: "valid publicIPs", 16279 tweakSvc: func(s *core.Service) { 16280 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16281 s.Spec.ExternalIPs = []string{"1.2.3.4"} 16282 }, 16283 numErrs: 0, 16284 }, { 16285 name: "dup port name", 16286 tweakSvc: func(s *core.Service) { 16287 s.Spec.Ports[0].Name = "p" 16288 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16289 }, 16290 numErrs: 1, 16291 }, { 16292 name: "valid load balancer protocol UDP 1", 16293 tweakSvc: func(s *core.Service) { 16294 s.Spec.Type = core.ServiceTypeLoadBalancer 16295 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16296 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16297 s.Spec.Ports[0].Protocol = "UDP" 16298 }, 16299 numErrs: 0, 16300 }, { 16301 name: "valid load balancer protocol UDP 2", 16302 tweakSvc: func(s *core.Service) { 16303 s.Spec.Type = core.ServiceTypeLoadBalancer 16304 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16305 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16306 s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)} 16307 }, 16308 numErrs: 0, 16309 }, { 16310 name: "load balancer with mix protocol", 16311 tweakSvc: func(s *core.Service) { 16312 s.Spec.Type = core.ServiceTypeLoadBalancer 16313 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16314 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16315 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}) 16316 }, 16317 numErrs: 0, 16318 }, { 16319 name: "valid 1", 16320 tweakSvc: func(s *core.Service) { 16321 // do nothing 16322 }, 16323 numErrs: 0, 16324 }, { 16325 name: "valid 2", 16326 tweakSvc: func(s *core.Service) { 16327 s.Spec.Ports[0].Protocol = "UDP" 16328 s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345) 16329 }, 16330 numErrs: 0, 16331 }, { 16332 name: "valid 3", 16333 tweakSvc: func(s *core.Service) { 16334 s.Spec.Ports[0].TargetPort = intstr.FromString("http") 16335 }, 16336 numErrs: 0, 16337 }, { 16338 name: "valid cluster ip - none ", 16339 tweakSvc: func(s *core.Service) { 16340 s.Spec.ClusterIP = core.ClusterIPNone 16341 s.Spec.ClusterIPs = []string{core.ClusterIPNone} 16342 }, 16343 numErrs: 0, 16344 }, { 16345 name: "valid cluster ip - empty", 16346 tweakSvc: func(s *core.Service) { 16347 s.Spec.ClusterIPs = nil 16348 s.Spec.Ports[0].TargetPort = intstr.FromString("http") 16349 }, 16350 numErrs: 0, 16351 }, { 16352 name: "valid type - clusterIP", 16353 tweakSvc: func(s *core.Service) { 16354 s.Spec.Type = core.ServiceTypeClusterIP 16355 }, 16356 numErrs: 0, 16357 }, { 16358 name: "valid type - loadbalancer", 16359 tweakSvc: func(s *core.Service) { 16360 s.Spec.Type = core.ServiceTypeLoadBalancer 16361 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16362 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16363 }, 16364 numErrs: 0, 16365 }, { 16366 name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false", 16367 tweakSvc: func(s *core.Service) { 16368 s.Spec.Type = core.ServiceTypeLoadBalancer 16369 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16370 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) 16371 }, 16372 numErrs: 0, 16373 }, { 16374 name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type", 16375 tweakSvc: func(s *core.Service) { 16376 s.Spec.Type = core.ServiceTypeLoadBalancer 16377 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16378 }, 16379 numErrs: 1, 16380 }, { 16381 name: "valid type loadbalancer 2 ports", 16382 tweakSvc: func(s *core.Service) { 16383 s.Spec.Type = core.ServiceTypeLoadBalancer 16384 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16385 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16386 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16387 }, 16388 numErrs: 0, 16389 }, { 16390 name: "valid external load balancer 2 ports", 16391 tweakSvc: func(s *core.Service) { 16392 s.Spec.Type = core.ServiceTypeLoadBalancer 16393 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16394 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16395 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16396 }, 16397 numErrs: 0, 16398 }, { 16399 name: "duplicate nodeports", 16400 tweakSvc: func(s *core.Service) { 16401 s.Spec.Type = core.ServiceTypeNodePort 16402 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16403 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) 16404 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) 16405 }, 16406 numErrs: 1, 16407 }, { 16408 name: "duplicate nodeports (different protocols)", 16409 tweakSvc: func(s *core.Service) { 16410 s.Spec.Type = core.ServiceTypeNodePort 16411 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16412 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) 16413 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) 16414 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)}) 16415 }, 16416 numErrs: 0, 16417 }, { 16418 name: "invalid duplicate ports (with same protocol)", 16419 tweakSvc: func(s *core.Service) { 16420 s.Spec.Type = core.ServiceTypeClusterIP 16421 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}) 16422 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)}) 16423 }, 16424 numErrs: 1, 16425 }, { 16426 name: "valid duplicate ports (with different protocols)", 16427 tweakSvc: func(s *core.Service) { 16428 s.Spec.Type = core.ServiceTypeClusterIP 16429 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)}) 16430 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)}) 16431 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)}) 16432 }, 16433 numErrs: 0, 16434 }, { 16435 name: "valid type - cluster", 16436 tweakSvc: func(s *core.Service) { 16437 s.Spec.Type = core.ServiceTypeClusterIP 16438 }, 16439 numErrs: 0, 16440 }, { 16441 name: "valid type - nodeport", 16442 tweakSvc: func(s *core.Service) { 16443 s.Spec.Type = core.ServiceTypeNodePort 16444 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16445 }, 16446 numErrs: 0, 16447 }, { 16448 name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true", 16449 tweakSvc: func(s *core.Service) { 16450 s.Spec.Type = core.ServiceTypeLoadBalancer 16451 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16452 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16453 }, 16454 numErrs: 0, 16455 }, { 16456 name: "valid type loadbalancer 2 ports", 16457 tweakSvc: func(s *core.Service) { 16458 s.Spec.Type = core.ServiceTypeLoadBalancer 16459 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16460 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16461 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16462 }, 16463 numErrs: 0, 16464 }, { 16465 name: "valid type loadbalancer with NodePort", 16466 tweakSvc: func(s *core.Service) { 16467 s.Spec.Type = core.ServiceTypeLoadBalancer 16468 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16469 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16470 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) 16471 }, 16472 numErrs: 0, 16473 }, { 16474 name: "valid type=NodePort service with NodePort", 16475 tweakSvc: func(s *core.Service) { 16476 s.Spec.Type = core.ServiceTypeNodePort 16477 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16478 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) 16479 }, 16480 numErrs: 0, 16481 }, { 16482 name: "valid type=NodePort service without NodePort", 16483 tweakSvc: func(s *core.Service) { 16484 s.Spec.Type = core.ServiceTypeNodePort 16485 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16486 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16487 }, 16488 numErrs: 0, 16489 }, { 16490 name: "valid cluster service without NodePort", 16491 tweakSvc: func(s *core.Service) { 16492 s.Spec.Type = core.ServiceTypeClusterIP 16493 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16494 }, 16495 numErrs: 0, 16496 }, { 16497 name: "invalid cluster service with NodePort", 16498 tweakSvc: func(s *core.Service) { 16499 s.Spec.Type = core.ServiceTypeClusterIP 16500 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)}) 16501 }, 16502 numErrs: 1, 16503 }, { 16504 name: "invalid public service with duplicate NodePort", 16505 tweakSvc: func(s *core.Service) { 16506 s.Spec.Type = core.ServiceTypeNodePort 16507 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16508 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) 16509 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)}) 16510 }, 16511 numErrs: 1, 16512 }, { 16513 name: "valid type=LoadBalancer", 16514 tweakSvc: func(s *core.Service) { 16515 s.Spec.Type = core.ServiceTypeLoadBalancer 16516 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16517 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16518 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16519 }, 16520 numErrs: 0, 16521 }, { 16522 // For now we open firewalls, and its insecure if we open 10250, remove this 16523 // when we have better protections in place. 16524 name: "invalid port type=LoadBalancer", 16525 tweakSvc: func(s *core.Service) { 16526 s.Spec.Type = core.ServiceTypeLoadBalancer 16527 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16528 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16529 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)}) 16530 }, 16531 numErrs: 1, 16532 }, { 16533 name: "valid LoadBalancer source range annotation", 16534 tweakSvc: func(s *core.Service) { 16535 s.Spec.Type = core.ServiceTypeLoadBalancer 16536 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16537 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16538 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16" 16539 }, 16540 numErrs: 0, 16541 }, { 16542 name: "empty LoadBalancer source range annotation", 16543 tweakSvc: func(s *core.Service) { 16544 s.Spec.Type = core.ServiceTypeLoadBalancer 16545 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16546 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16547 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "" 16548 }, 16549 numErrs: 0, 16550 }, { 16551 name: "invalid LoadBalancer source range annotation (hostname)", 16552 tweakSvc: func(s *core.Service) { 16553 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar" 16554 }, 16555 numErrs: 2, 16556 }, { 16557 name: "invalid LoadBalancer source range annotation (invalid CIDR)", 16558 tweakSvc: func(s *core.Service) { 16559 s.Spec.Type = core.ServiceTypeLoadBalancer 16560 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16561 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16562 s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33" 16563 }, 16564 numErrs: 1, 16565 }, { 16566 name: "invalid source range for non LoadBalancer type service", 16567 tweakSvc: func(s *core.Service) { 16568 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} 16569 }, 16570 numErrs: 1, 16571 }, { 16572 name: "valid LoadBalancer source range", 16573 tweakSvc: func(s *core.Service) { 16574 s.Spec.Type = core.ServiceTypeLoadBalancer 16575 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16576 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16577 s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"} 16578 }, 16579 numErrs: 0, 16580 }, { 16581 name: "empty LoadBalancer source range", 16582 tweakSvc: func(s *core.Service) { 16583 s.Spec.Type = core.ServiceTypeLoadBalancer 16584 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16585 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16586 s.Spec.LoadBalancerSourceRanges = []string{" "} 16587 }, 16588 numErrs: 1, 16589 }, { 16590 name: "invalid LoadBalancer source range", 16591 tweakSvc: func(s *core.Service) { 16592 s.Spec.Type = core.ServiceTypeLoadBalancer 16593 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16594 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16595 s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"} 16596 }, 16597 numErrs: 1, 16598 }, { 16599 name: "valid ExternalName", 16600 tweakSvc: func(s *core.Service) { 16601 s.Spec.Type = core.ServiceTypeExternalName 16602 s.Spec.ExternalName = "foo.bar.example.com" 16603 }, 16604 numErrs: 0, 16605 }, { 16606 name: "valid ExternalName (trailing dot)", 16607 tweakSvc: func(s *core.Service) { 16608 s.Spec.Type = core.ServiceTypeExternalName 16609 s.Spec.ExternalName = "foo.bar.example.com." 16610 }, 16611 numErrs: 0, 16612 }, { 16613 name: "invalid ExternalName clusterIP (valid IP)", 16614 tweakSvc: func(s *core.Service) { 16615 s.Spec.Type = core.ServiceTypeExternalName 16616 s.Spec.ClusterIP = "1.2.3.4" 16617 s.Spec.ClusterIPs = []string{"1.2.3.4"} 16618 s.Spec.ExternalName = "foo.bar.example.com" 16619 }, 16620 numErrs: 1, 16621 }, { 16622 name: "invalid ExternalName clusterIP (None)", 16623 tweakSvc: func(s *core.Service) { 16624 s.Spec.Type = core.ServiceTypeExternalName 16625 s.Spec.ClusterIP = "None" 16626 s.Spec.ClusterIPs = []string{"None"} 16627 s.Spec.ExternalName = "foo.bar.example.com" 16628 }, 16629 numErrs: 1, 16630 }, { 16631 name: "invalid ExternalName (not a DNS name)", 16632 tweakSvc: func(s *core.Service) { 16633 s.Spec.Type = core.ServiceTypeExternalName 16634 s.Spec.ExternalName = "-123" 16635 }, 16636 numErrs: 1, 16637 }, { 16638 name: "LoadBalancer type cannot have None ClusterIP", 16639 tweakSvc: func(s *core.Service) { 16640 s.Spec.ClusterIP = "None" 16641 s.Spec.ClusterIPs = []string{"None"} 16642 s.Spec.Type = core.ServiceTypeLoadBalancer 16643 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16644 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16645 }, 16646 numErrs: 1, 16647 }, { 16648 name: "invalid node port with clusterIP None", 16649 tweakSvc: func(s *core.Service) { 16650 s.Spec.Type = core.ServiceTypeNodePort 16651 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16652 s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) 16653 s.Spec.ClusterIP = "None" 16654 s.Spec.ClusterIPs = []string{"None"} 16655 }, 16656 numErrs: 1, 16657 }, 16658 // ESIPP section begins. 16659 { 16660 name: "invalid externalTraffic field", 16661 tweakSvc: func(s *core.Service) { 16662 s.Spec.Type = core.ServiceTypeLoadBalancer 16663 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16664 s.Spec.ExternalTrafficPolicy = "invalid" 16665 }, 16666 numErrs: 1, 16667 }, { 16668 name: "nil internalTraffic field when feature gate is on", 16669 tweakSvc: func(s *core.Service) { 16670 s.Spec.InternalTrafficPolicy = nil 16671 }, 16672 numErrs: 1, 16673 }, { 16674 name: "internalTrafficPolicy field nil when type is ExternalName", 16675 tweakSvc: func(s *core.Service) { 16676 s.Spec.InternalTrafficPolicy = nil 16677 s.Spec.Type = core.ServiceTypeExternalName 16678 s.Spec.ExternalName = "foo.bar.com" 16679 }, 16680 numErrs: 0, 16681 }, { 16682 // Typically this should fail validation, but in v1.22 we have existing clusters 16683 // that may have allowed internalTrafficPolicy when Type=ExternalName. 16684 // This test case ensures we don't break compatibility for internalTrafficPolicy 16685 // when Type=ExternalName 16686 name: "internalTrafficPolicy field is set when type is ExternalName", 16687 tweakSvc: func(s *core.Service) { 16688 cluster := core.ServiceInternalTrafficPolicyCluster 16689 s.Spec.InternalTrafficPolicy = &cluster 16690 s.Spec.Type = core.ServiceTypeExternalName 16691 s.Spec.ExternalName = "foo.bar.com" 16692 }, 16693 numErrs: 0, 16694 }, { 16695 name: "invalid internalTraffic field", 16696 tweakSvc: func(s *core.Service) { 16697 invalid := core.ServiceInternalTrafficPolicy("invalid") 16698 s.Spec.InternalTrafficPolicy = &invalid 16699 }, 16700 numErrs: 1, 16701 }, { 16702 name: "internalTrafficPolicy field set to Cluster", 16703 tweakSvc: func(s *core.Service) { 16704 cluster := core.ServiceInternalTrafficPolicyCluster 16705 s.Spec.InternalTrafficPolicy = &cluster 16706 }, 16707 numErrs: 0, 16708 }, { 16709 name: "internalTrafficPolicy field set to Local", 16710 tweakSvc: func(s *core.Service) { 16711 local := core.ServiceInternalTrafficPolicyLocal 16712 s.Spec.InternalTrafficPolicy = &local 16713 }, 16714 numErrs: 0, 16715 }, { 16716 name: "negative healthCheckNodePort field", 16717 tweakSvc: func(s *core.Service) { 16718 s.Spec.Type = core.ServiceTypeLoadBalancer 16719 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16720 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 16721 s.Spec.HealthCheckNodePort = -1 16722 }, 16723 numErrs: 1, 16724 }, { 16725 name: "negative healthCheckNodePort field", 16726 tweakSvc: func(s *core.Service) { 16727 s.Spec.Type = core.ServiceTypeLoadBalancer 16728 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16729 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 16730 s.Spec.HealthCheckNodePort = 31100 16731 }, 16732 numErrs: 0, 16733 }, 16734 // ESIPP section ends. 16735 { 16736 name: "invalid timeoutSeconds field", 16737 tweakSvc: func(s *core.Service) { 16738 s.Spec.Type = core.ServiceTypeClusterIP 16739 s.Spec.SessionAffinity = core.ServiceAffinityClientIP 16740 s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ 16741 ClientIP: &core.ClientIPConfig{ 16742 TimeoutSeconds: utilpointer.Int32(-1), 16743 }, 16744 } 16745 }, 16746 numErrs: 1, 16747 }, { 16748 name: "sessionAffinityConfig can't be set when session affinity is None", 16749 tweakSvc: func(s *core.Service) { 16750 s.Spec.Type = core.ServiceTypeLoadBalancer 16751 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 16752 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 16753 s.Spec.SessionAffinity = core.ServiceAffinityNone 16754 s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ 16755 ClientIP: &core.ClientIPConfig{ 16756 TimeoutSeconds: utilpointer.Int32(90), 16757 }, 16758 } 16759 }, 16760 numErrs: 1, 16761 }, 16762 /* ip families validation */ 16763 { 16764 name: "invalid, service with invalid ipFamilies", 16765 tweakSvc: func(s *core.Service) { 16766 invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family") 16767 s.Spec.IPFamilies = []core.IPFamily{invalidServiceIPFamily} 16768 }, 16769 numErrs: 1, 16770 }, { 16771 name: "invalid, service with invalid ipFamilies (2nd)", 16772 tweakSvc: func(s *core.Service) { 16773 invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family") 16774 s.Spec.IPFamilyPolicy = &requireDualStack 16775 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, invalidServiceIPFamily} 16776 }, 16777 numErrs: 1, 16778 }, { 16779 name: "IPFamilyPolicy(singleStack) is set for two families", 16780 tweakSvc: func(s *core.Service) { 16781 s.Spec.IPFamilyPolicy = &singleStack 16782 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16783 }, 16784 numErrs: 0, // this validated in alloc code. 16785 }, { 16786 name: "valid, IPFamilyPolicy(preferDualStack) is set for two families (note: alloc sets families)", 16787 tweakSvc: func(s *core.Service) { 16788 s.Spec.IPFamilyPolicy = &preferDualStack 16789 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16790 }, 16791 numErrs: 0, 16792 }, 16793 16794 { 16795 name: "invalid, service with 2+ ipFamilies", 16796 tweakSvc: func(s *core.Service) { 16797 s.Spec.IPFamilyPolicy = &requireDualStack 16798 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol, core.IPv4Protocol} 16799 }, 16800 numErrs: 1, 16801 }, { 16802 name: "invalid, service with same ip families", 16803 tweakSvc: func(s *core.Service) { 16804 s.Spec.IPFamilyPolicy = &requireDualStack 16805 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv6Protocol} 16806 }, 16807 numErrs: 1, 16808 }, { 16809 name: "valid, nil service ipFamilies", 16810 tweakSvc: func(s *core.Service) { 16811 s.Spec.IPFamilies = nil 16812 }, 16813 numErrs: 0, 16814 }, { 16815 name: "valid, service with valid ipFamilies (v4)", 16816 tweakSvc: func(s *core.Service) { 16817 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 16818 }, 16819 numErrs: 0, 16820 }, { 16821 name: "valid, service with valid ipFamilies (v6)", 16822 tweakSvc: func(s *core.Service) { 16823 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 16824 }, 16825 numErrs: 0, 16826 }, { 16827 name: "valid, service with valid ipFamilies(v4,v6)", 16828 tweakSvc: func(s *core.Service) { 16829 s.Spec.IPFamilyPolicy = &requireDualStack 16830 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16831 }, 16832 numErrs: 0, 16833 }, { 16834 name: "valid, service with valid ipFamilies(v6,v4)", 16835 tweakSvc: func(s *core.Service) { 16836 s.Spec.IPFamilyPolicy = &requireDualStack 16837 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} 16838 }, 16839 numErrs: 0, 16840 }, { 16841 name: "valid, service preferred dual stack with single family", 16842 tweakSvc: func(s *core.Service) { 16843 s.Spec.IPFamilyPolicy = &preferDualStack 16844 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 16845 }, 16846 numErrs: 0, 16847 }, 16848 /* cluster IPs. some tests are redundant */ 16849 { 16850 name: "invalid, garbage single ip", 16851 tweakSvc: func(s *core.Service) { 16852 s.Spec.ClusterIP = "garbage-ip" 16853 s.Spec.ClusterIPs = []string{"garbage-ip"} 16854 }, 16855 numErrs: 1, 16856 }, { 16857 name: "invalid, garbage ips", 16858 tweakSvc: func(s *core.Service) { 16859 s.Spec.IPFamilyPolicy = &requireDualStack 16860 s.Spec.ClusterIP = "garbage-ip" 16861 s.Spec.ClusterIPs = []string{"garbage-ip", "garbage-second-ip"} 16862 }, 16863 numErrs: 2, 16864 }, { 16865 name: "invalid, garbage first ip", 16866 tweakSvc: func(s *core.Service) { 16867 s.Spec.IPFamilyPolicy = &requireDualStack 16868 s.Spec.ClusterIP = "garbage-ip" 16869 s.Spec.ClusterIPs = []string{"garbage-ip", "2001::1"} 16870 }, 16871 numErrs: 1, 16872 }, { 16873 name: "invalid, garbage second ip", 16874 tweakSvc: func(s *core.Service) { 16875 s.Spec.IPFamilyPolicy = &requireDualStack 16876 s.Spec.ClusterIP = "2001::1" 16877 s.Spec.ClusterIPs = []string{"2001::1", "garbage-ip"} 16878 }, 16879 numErrs: 1, 16880 }, { 16881 name: "invalid, NONE + IP", 16882 tweakSvc: func(s *core.Service) { 16883 s.Spec.IPFamilyPolicy = &requireDualStack 16884 s.Spec.ClusterIP = "None" 16885 s.Spec.ClusterIPs = []string{"None", "2001::1"} 16886 }, 16887 numErrs: 1, 16888 }, { 16889 name: "invalid, IP + NONE", 16890 tweakSvc: func(s *core.Service) { 16891 s.Spec.IPFamilyPolicy = &requireDualStack 16892 s.Spec.ClusterIP = "2001::1" 16893 s.Spec.ClusterIPs = []string{"2001::1", "None"} 16894 }, 16895 numErrs: 1, 16896 }, { 16897 name: "invalid, EMPTY STRING + IP", 16898 tweakSvc: func(s *core.Service) { 16899 s.Spec.IPFamilyPolicy = &requireDualStack 16900 s.Spec.ClusterIP = "" 16901 s.Spec.ClusterIPs = []string{"", "2001::1"} 16902 }, 16903 numErrs: 2, 16904 }, { 16905 name: "invalid, IP + EMPTY STRING", 16906 tweakSvc: func(s *core.Service) { 16907 s.Spec.IPFamilyPolicy = &requireDualStack 16908 s.Spec.ClusterIP = "2001::1" 16909 s.Spec.ClusterIPs = []string{"2001::1", ""} 16910 }, 16911 numErrs: 1, 16912 }, { 16913 name: "invalid, same ip family (v6)", 16914 tweakSvc: func(s *core.Service) { 16915 s.Spec.IPFamilyPolicy = &requireDualStack 16916 s.Spec.ClusterIP = "2001::1" 16917 s.Spec.ClusterIPs = []string{"2001::1", "2001::4"} 16918 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16919 }, 16920 numErrs: 2, 16921 }, { 16922 name: "invalid, same ip family (v4)", 16923 tweakSvc: func(s *core.Service) { 16924 s.Spec.IPFamilyPolicy = &requireDualStack 16925 s.Spec.ClusterIP = "10.0.0.1" 16926 s.Spec.ClusterIPs = []string{"10.0.0.1", "10.0.0.10"} 16927 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16928 16929 }, 16930 numErrs: 2, 16931 }, { 16932 name: "invalid, more than two ips", 16933 tweakSvc: func(s *core.Service) { 16934 s.Spec.IPFamilyPolicy = &requireDualStack 16935 s.Spec.ClusterIP = "10.0.0.1" 16936 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1", "10.0.0.10"} 16937 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16938 }, 16939 numErrs: 1, 16940 }, { 16941 name: " multi ip, dualstack not set (request for downgrade)", 16942 tweakSvc: func(s *core.Service) { 16943 s.Spec.IPFamilyPolicy = &singleStack 16944 s.Spec.ClusterIP = "10.0.0.1" 16945 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} 16946 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16947 }, 16948 numErrs: 0, 16949 }, { 16950 name: "valid, headless-no-selector + multi family + gate off", 16951 tweakSvc: func(s *core.Service) { 16952 s.Spec.IPFamilyPolicy = &requireDualStack 16953 s.Spec.ClusterIP = "None" 16954 s.Spec.ClusterIPs = []string{"None"} 16955 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 16956 s.Spec.Selector = nil 16957 }, 16958 numErrs: 0, 16959 }, { 16960 name: "valid, multi ip, single ipfamilies preferDualStack", 16961 tweakSvc: func(s *core.Service) { 16962 s.Spec.IPFamilyPolicy = &preferDualStack 16963 s.Spec.ClusterIP = "10.0.0.1" 16964 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} 16965 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 16966 }, 16967 numErrs: 0, 16968 }, 16969 16970 { 16971 name: "valid, multi ip, single ipfamilies (must match when provided) + requireDualStack", 16972 tweakSvc: func(s *core.Service) { 16973 s.Spec.IPFamilyPolicy = &requireDualStack 16974 s.Spec.ClusterIP = "10.0.0.1" 16975 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} 16976 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 16977 }, 16978 numErrs: 0, 16979 }, { 16980 name: "invalid, families don't match (v4=>v6)", 16981 tweakSvc: func(s *core.Service) { 16982 s.Spec.ClusterIP = "10.0.0.1" 16983 s.Spec.ClusterIPs = []string{"10.0.0.1"} 16984 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 16985 }, 16986 numErrs: 1, 16987 }, { 16988 name: "invalid, families don't match (v6=>v4)", 16989 tweakSvc: func(s *core.Service) { 16990 s.Spec.ClusterIP = "2001::1" 16991 s.Spec.ClusterIPs = []string{"2001::1"} 16992 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 16993 }, 16994 numErrs: 1, 16995 }, { 16996 name: "valid. no field set", 16997 tweakSvc: func(s *core.Service) { 16998 }, 16999 numErrs: 0, 17000 }, 17001 17002 { 17003 name: "valid, single ip", 17004 tweakSvc: func(s *core.Service) { 17005 s.Spec.IPFamilyPolicy = &singleStack 17006 s.Spec.ClusterIP = "10.0.0.1" 17007 s.Spec.ClusterIPs = []string{"10.0.0.1"} 17008 }, 17009 numErrs: 0, 17010 }, { 17011 name: "valid, single family", 17012 tweakSvc: func(s *core.Service) { 17013 s.Spec.IPFamilyPolicy = &singleStack 17014 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 17015 17016 }, 17017 numErrs: 0, 17018 }, { 17019 name: "valid, single ip + single family", 17020 tweakSvc: func(s *core.Service) { 17021 s.Spec.IPFamilyPolicy = &singleStack 17022 s.Spec.ClusterIP = "2001::1" 17023 s.Spec.ClusterIPs = []string{"2001::1"} 17024 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 17025 17026 }, 17027 numErrs: 0, 17028 }, { 17029 name: "valid, single ip + single family (dual stack requested)", 17030 tweakSvc: func(s *core.Service) { 17031 s.Spec.IPFamilyPolicy = &preferDualStack 17032 s.Spec.ClusterIP = "2001::1" 17033 s.Spec.ClusterIPs = []string{"2001::1"} 17034 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 17035 17036 }, 17037 numErrs: 0, 17038 }, { 17039 name: "valid, single ip, multi ipfamilies", 17040 tweakSvc: func(s *core.Service) { 17041 s.Spec.IPFamilyPolicy = &requireDualStack 17042 s.Spec.ClusterIP = "10.0.0.1" 17043 s.Spec.ClusterIPs = []string{"10.0.0.1"} 17044 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 17045 }, 17046 numErrs: 0, 17047 }, { 17048 name: "valid, multi ips, multi ipfamilies (4,6)", 17049 tweakSvc: func(s *core.Service) { 17050 s.Spec.IPFamilyPolicy = &requireDualStack 17051 s.Spec.ClusterIP = "10.0.0.1" 17052 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} 17053 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 17054 }, 17055 numErrs: 0, 17056 }, { 17057 name: "valid, ips, multi ipfamilies (6,4)", 17058 tweakSvc: func(s *core.Service) { 17059 s.Spec.IPFamilyPolicy = &requireDualStack 17060 s.Spec.ClusterIP = "2001::1" 17061 s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"} 17062 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} 17063 }, 17064 numErrs: 0, 17065 }, { 17066 name: "valid, multi ips (6,4)", 17067 tweakSvc: func(s *core.Service) { 17068 s.Spec.IPFamilyPolicy = &requireDualStack 17069 s.Spec.ClusterIP = "2001::1" 17070 s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"} 17071 }, 17072 numErrs: 0, 17073 }, { 17074 name: "valid, multi ipfamilies (6,4)", 17075 tweakSvc: func(s *core.Service) { 17076 s.Spec.IPFamilyPolicy = &requireDualStack 17077 s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} 17078 }, 17079 numErrs: 0, 17080 }, { 17081 name: "valid, multi ips (4,6)", 17082 tweakSvc: func(s *core.Service) { 17083 s.Spec.IPFamilyPolicy = &requireDualStack 17084 s.Spec.ClusterIP = "10.0.0.1" 17085 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} 17086 }, 17087 numErrs: 0, 17088 }, { 17089 name: "valid, multi ipfamilies (4,6)", 17090 tweakSvc: func(s *core.Service) { 17091 s.Spec.IPFamilyPolicy = &requireDualStack 17092 s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 17093 }, 17094 numErrs: 0, 17095 }, { 17096 name: "valid, dual stack", 17097 tweakSvc: func(s *core.Service) { 17098 s.Spec.IPFamilyPolicy = &requireDualStack 17099 }, 17100 numErrs: 0, 17101 }, 17102 17103 { 17104 name: `valid appProtocol`, 17105 tweakSvc: func(s *core.Service) { 17106 s.Spec.Ports = []core.ServicePort{{ 17107 Port: 12345, 17108 TargetPort: intstr.FromInt32(12345), 17109 Protocol: "TCP", 17110 AppProtocol: utilpointer.String("HTTP"), 17111 }} 17112 }, 17113 numErrs: 0, 17114 }, { 17115 name: `valid custom appProtocol`, 17116 tweakSvc: func(s *core.Service) { 17117 s.Spec.Ports = []core.ServicePort{{ 17118 Port: 12345, 17119 TargetPort: intstr.FromInt32(12345), 17120 Protocol: "TCP", 17121 AppProtocol: utilpointer.String("example.com/protocol"), 17122 }} 17123 }, 17124 numErrs: 0, 17125 }, { 17126 name: `invalid appProtocol`, 17127 tweakSvc: func(s *core.Service) { 17128 s.Spec.Ports = []core.ServicePort{{ 17129 Port: 12345, 17130 TargetPort: intstr.FromInt32(12345), 17131 Protocol: "TCP", 17132 AppProtocol: utilpointer.String("example.com/protocol_with{invalid}[characters]"), 17133 }} 17134 }, 17135 numErrs: 1, 17136 }, 17137 17138 { 17139 name: "invalid cluster ip != clusterIP in multi ip service", 17140 tweakSvc: func(s *core.Service) { 17141 s.Spec.IPFamilyPolicy = &requireDualStack 17142 s.Spec.ClusterIP = "10.0.0.10" 17143 s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"} 17144 }, 17145 numErrs: 1, 17146 }, { 17147 name: "invalid cluster ip != clusterIP in single ip service", 17148 tweakSvc: func(s *core.Service) { 17149 s.Spec.ClusterIP = "10.0.0.10" 17150 s.Spec.ClusterIPs = []string{"10.0.0.1"} 17151 }, 17152 numErrs: 1, 17153 }, { 17154 name: "Use AllocateLoadBalancerNodePorts when type is not LoadBalancer", 17155 tweakSvc: func(s *core.Service) { 17156 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 17157 }, 17158 numErrs: 1, 17159 }, { 17160 name: "valid LoadBalancerClass when type is LoadBalancer", 17161 tweakSvc: func(s *core.Service) { 17162 s.Spec.Type = core.ServiceTypeLoadBalancer 17163 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 17164 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 17165 s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 17166 }, 17167 numErrs: 0, 17168 }, { 17169 name: "invalid LoadBalancerClass", 17170 tweakSvc: func(s *core.Service) { 17171 s.Spec.Type = core.ServiceTypeLoadBalancer 17172 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 17173 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 17174 s.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerClass") 17175 }, 17176 numErrs: 1, 17177 }, { 17178 name: "invalid: set LoadBalancerClass when type is not LoadBalancer", 17179 tweakSvc: func(s *core.Service) { 17180 s.Spec.Type = core.ServiceTypeClusterIP 17181 s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 17182 }, 17183 numErrs: 1, 17184 }, { 17185 name: "topology annotations are mismatched", 17186 tweakSvc: func(s *core.Service) { 17187 s.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original" 17188 s.Annotations[core.AnnotationTopologyMode] = "different" 17189 }, 17190 numErrs: 1, 17191 }, 17192 } 17193 17194 for _, tc := range testCases { 17195 t.Run(tc.name, func(t *testing.T) { 17196 for i := range tc.featureGates { 17197 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, tc.featureGates[i], true)() 17198 } 17199 svc := makeValidService() 17200 tc.tweakSvc(&svc) 17201 errs := ValidateServiceCreate(&svc) 17202 if len(errs) != tc.numErrs { 17203 t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate()) 17204 } 17205 }) 17206 } 17207 } 17208 17209 func TestValidateServiceExternalTrafficPolicy(t *testing.T) { 17210 testCases := []struct { 17211 name string 17212 tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it. 17213 numErrs int 17214 }{{ 17215 name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set", 17216 tweakSvc: func(s *core.Service) { 17217 s.Spec.Type = core.ServiceTypeLoadBalancer 17218 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 17219 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 17220 s.Spec.HealthCheckNodePort = 34567 17221 }, 17222 numErrs: 0, 17223 }, { 17224 name: "valid nodePort service with externalTrafficPolicy set", 17225 tweakSvc: func(s *core.Service) { 17226 s.Spec.Type = core.ServiceTypeNodePort 17227 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 17228 }, 17229 numErrs: 0, 17230 }, { 17231 name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set", 17232 tweakSvc: func(s *core.Service) { 17233 s.Spec.Type = core.ServiceTypeClusterIP 17234 }, 17235 numErrs: 0, 17236 }, { 17237 name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local", 17238 tweakSvc: func(s *core.Service) { 17239 s.Spec.Type = core.ServiceTypeLoadBalancer 17240 s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 17241 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 17242 s.Spec.HealthCheckNodePort = 34567 17243 }, 17244 numErrs: 1, 17245 }, { 17246 name: "cannot set healthCheckNodePort field on nodePort service", 17247 tweakSvc: func(s *core.Service) { 17248 s.Spec.Type = core.ServiceTypeNodePort 17249 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 17250 s.Spec.HealthCheckNodePort = 34567 17251 }, 17252 numErrs: 1, 17253 }, { 17254 name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service", 17255 tweakSvc: func(s *core.Service) { 17256 s.Spec.Type = core.ServiceTypeClusterIP 17257 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 17258 s.Spec.HealthCheckNodePort = 34567 17259 }, 17260 numErrs: 2, 17261 }, { 17262 name: "cannot set externalTrafficPolicy field on ExternalName service", 17263 tweakSvc: func(s *core.Service) { 17264 s.Spec.Type = core.ServiceTypeExternalName 17265 s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal 17266 }, 17267 numErrs: 1, 17268 }, { 17269 name: "externalTrafficPolicy is required on NodePort service", 17270 tweakSvc: func(s *core.Service) { 17271 s.Spec.Type = core.ServiceTypeNodePort 17272 }, 17273 numErrs: 1, 17274 }, { 17275 name: "externalTrafficPolicy is required on LoadBalancer service", 17276 tweakSvc: func(s *core.Service) { 17277 s.Spec.Type = core.ServiceTypeLoadBalancer 17278 }, 17279 numErrs: 1, 17280 }, { 17281 name: "externalTrafficPolicy is required on ClusterIP service with externalIPs", 17282 tweakSvc: func(s *core.Service) { 17283 s.Spec.Type = core.ServiceTypeClusterIP 17284 s.Spec.ExternalIPs = []string{"1.2.3,4"} 17285 }, 17286 numErrs: 1, 17287 }, 17288 } 17289 17290 for _, tc := range testCases { 17291 svc := makeValidService() 17292 tc.tweakSvc(&svc) 17293 errs := validateServiceExternalTrafficPolicy(&svc) 17294 if len(errs) != tc.numErrs { 17295 t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate()) 17296 } 17297 } 17298 } 17299 17300 func TestValidateReplicationControllerStatus(t *testing.T) { 17301 tests := []struct { 17302 name string 17303 17304 replicas int32 17305 fullyLabeledReplicas int32 17306 readyReplicas int32 17307 availableReplicas int32 17308 observedGeneration int64 17309 17310 expectedErr bool 17311 }{{ 17312 name: "valid status", 17313 replicas: 3, 17314 fullyLabeledReplicas: 3, 17315 readyReplicas: 2, 17316 availableReplicas: 1, 17317 observedGeneration: 2, 17318 expectedErr: false, 17319 }, { 17320 name: "invalid replicas", 17321 replicas: -1, 17322 fullyLabeledReplicas: 3, 17323 readyReplicas: 2, 17324 availableReplicas: 1, 17325 observedGeneration: 2, 17326 expectedErr: true, 17327 }, { 17328 name: "invalid fullyLabeledReplicas", 17329 replicas: 3, 17330 fullyLabeledReplicas: -1, 17331 readyReplicas: 2, 17332 availableReplicas: 1, 17333 observedGeneration: 2, 17334 expectedErr: true, 17335 }, { 17336 name: "invalid readyReplicas", 17337 replicas: 3, 17338 fullyLabeledReplicas: 3, 17339 readyReplicas: -1, 17340 availableReplicas: 1, 17341 observedGeneration: 2, 17342 expectedErr: true, 17343 }, { 17344 name: "invalid availableReplicas", 17345 replicas: 3, 17346 fullyLabeledReplicas: 3, 17347 readyReplicas: 3, 17348 availableReplicas: -1, 17349 observedGeneration: 2, 17350 expectedErr: true, 17351 }, { 17352 name: "invalid observedGeneration", 17353 replicas: 3, 17354 fullyLabeledReplicas: 3, 17355 readyReplicas: 3, 17356 availableReplicas: 3, 17357 observedGeneration: -1, 17358 expectedErr: true, 17359 }, { 17360 name: "fullyLabeledReplicas greater than replicas", 17361 replicas: 3, 17362 fullyLabeledReplicas: 4, 17363 readyReplicas: 3, 17364 availableReplicas: 3, 17365 observedGeneration: 1, 17366 expectedErr: true, 17367 }, { 17368 name: "readyReplicas greater than replicas", 17369 replicas: 3, 17370 fullyLabeledReplicas: 3, 17371 readyReplicas: 4, 17372 availableReplicas: 3, 17373 observedGeneration: 1, 17374 expectedErr: true, 17375 }, { 17376 name: "availableReplicas greater than replicas", 17377 replicas: 3, 17378 fullyLabeledReplicas: 3, 17379 readyReplicas: 3, 17380 availableReplicas: 4, 17381 observedGeneration: 1, 17382 expectedErr: true, 17383 }, { 17384 name: "availableReplicas greater than readyReplicas", 17385 replicas: 3, 17386 fullyLabeledReplicas: 3, 17387 readyReplicas: 2, 17388 availableReplicas: 3, 17389 observedGeneration: 1, 17390 expectedErr: true, 17391 }, 17392 } 17393 17394 for _, test := range tests { 17395 status := core.ReplicationControllerStatus{ 17396 Replicas: test.replicas, 17397 FullyLabeledReplicas: test.fullyLabeledReplicas, 17398 ReadyReplicas: test.readyReplicas, 17399 AvailableReplicas: test.availableReplicas, 17400 ObservedGeneration: test.observedGeneration, 17401 } 17402 17403 if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr { 17404 t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr) 17405 } 17406 } 17407 } 17408 17409 func TestValidateReplicationControllerStatusUpdate(t *testing.T) { 17410 validSelector := map[string]string{"a": "b"} 17411 validPodTemplate := core.PodTemplate{ 17412 Template: core.PodTemplateSpec{ 17413 ObjectMeta: metav1.ObjectMeta{ 17414 Labels: validSelector, 17415 }, 17416 Spec: core.PodSpec{ 17417 RestartPolicy: core.RestartPolicyAlways, 17418 DNSPolicy: core.DNSClusterFirst, 17419 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17420 }, 17421 }, 17422 } 17423 type rcUpdateTest struct { 17424 old core.ReplicationController 17425 update core.ReplicationController 17426 } 17427 successCases := []rcUpdateTest{{ 17428 old: core.ReplicationController{ 17429 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17430 Spec: core.ReplicationControllerSpec{ 17431 Selector: validSelector, 17432 Template: &validPodTemplate.Template, 17433 }, 17434 Status: core.ReplicationControllerStatus{ 17435 Replicas: 2, 17436 }, 17437 }, 17438 update: core.ReplicationController{ 17439 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17440 Spec: core.ReplicationControllerSpec{ 17441 Replicas: 3, 17442 Selector: validSelector, 17443 Template: &validPodTemplate.Template, 17444 }, 17445 Status: core.ReplicationControllerStatus{ 17446 Replicas: 4, 17447 }, 17448 }, 17449 }, 17450 } 17451 for _, successCase := range successCases { 17452 successCase.old.ObjectMeta.ResourceVersion = "1" 17453 successCase.update.ObjectMeta.ResourceVersion = "1" 17454 if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 { 17455 t.Errorf("expected success: %v", errs) 17456 } 17457 } 17458 errorCases := map[string]rcUpdateTest{ 17459 "negative replicas": { 17460 old: core.ReplicationController{ 17461 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 17462 Spec: core.ReplicationControllerSpec{ 17463 Selector: validSelector, 17464 Template: &validPodTemplate.Template, 17465 }, 17466 Status: core.ReplicationControllerStatus{ 17467 Replicas: 3, 17468 }, 17469 }, 17470 update: core.ReplicationController{ 17471 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17472 Spec: core.ReplicationControllerSpec{ 17473 Replicas: 2, 17474 Selector: validSelector, 17475 Template: &validPodTemplate.Template, 17476 }, 17477 Status: core.ReplicationControllerStatus{ 17478 Replicas: -3, 17479 }, 17480 }, 17481 }, 17482 } 17483 for testName, errorCase := range errorCases { 17484 if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { 17485 t.Errorf("expected failure: %s", testName) 17486 } 17487 } 17488 17489 } 17490 17491 func TestValidateReplicationControllerUpdate(t *testing.T) { 17492 validSelector := map[string]string{"a": "b"} 17493 validPodTemplate := core.PodTemplate{ 17494 Template: core.PodTemplateSpec{ 17495 ObjectMeta: metav1.ObjectMeta{ 17496 Labels: validSelector, 17497 }, 17498 Spec: core.PodSpec{ 17499 RestartPolicy: core.RestartPolicyAlways, 17500 DNSPolicy: core.DNSClusterFirst, 17501 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17502 }, 17503 }, 17504 } 17505 readWriteVolumePodTemplate := core.PodTemplate{ 17506 Template: core.PodTemplateSpec{ 17507 ObjectMeta: metav1.ObjectMeta{ 17508 Labels: validSelector, 17509 }, 17510 Spec: core.PodSpec{ 17511 RestartPolicy: core.RestartPolicyAlways, 17512 DNSPolicy: core.DNSClusterFirst, 17513 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17514 Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, 17515 }, 17516 }, 17517 } 17518 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 17519 invalidPodTemplate := core.PodTemplate{ 17520 Template: core.PodTemplateSpec{ 17521 Spec: core.PodSpec{ 17522 RestartPolicy: core.RestartPolicyAlways, 17523 DNSPolicy: core.DNSClusterFirst, 17524 }, 17525 ObjectMeta: metav1.ObjectMeta{ 17526 Labels: invalidSelector, 17527 }, 17528 }, 17529 } 17530 type rcUpdateTest struct { 17531 old core.ReplicationController 17532 update core.ReplicationController 17533 } 17534 successCases := []rcUpdateTest{{ 17535 old: core.ReplicationController{ 17536 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17537 Spec: core.ReplicationControllerSpec{ 17538 Selector: validSelector, 17539 Template: &validPodTemplate.Template, 17540 }, 17541 }, 17542 update: core.ReplicationController{ 17543 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17544 Spec: core.ReplicationControllerSpec{ 17545 Replicas: 3, 17546 Selector: validSelector, 17547 Template: &validPodTemplate.Template, 17548 }, 17549 }, 17550 }, { 17551 old: core.ReplicationController{ 17552 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17553 Spec: core.ReplicationControllerSpec{ 17554 Selector: validSelector, 17555 Template: &validPodTemplate.Template, 17556 }, 17557 }, 17558 update: core.ReplicationController{ 17559 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17560 Spec: core.ReplicationControllerSpec{ 17561 Replicas: 1, 17562 Selector: validSelector, 17563 Template: &readWriteVolumePodTemplate.Template, 17564 }, 17565 }, 17566 }, 17567 } 17568 for _, successCase := range successCases { 17569 successCase.old.ObjectMeta.ResourceVersion = "1" 17570 successCase.update.ObjectMeta.ResourceVersion = "1" 17571 if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old, PodValidationOptions{}); len(errs) != 0 { 17572 t.Errorf("expected success: %v", errs) 17573 } 17574 } 17575 errorCases := map[string]rcUpdateTest{ 17576 "more than one read/write": { 17577 old: core.ReplicationController{ 17578 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 17579 Spec: core.ReplicationControllerSpec{ 17580 Selector: validSelector, 17581 Template: &validPodTemplate.Template, 17582 }, 17583 }, 17584 update: core.ReplicationController{ 17585 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17586 Spec: core.ReplicationControllerSpec{ 17587 Replicas: 2, 17588 Selector: validSelector, 17589 Template: &readWriteVolumePodTemplate.Template, 17590 }, 17591 }, 17592 }, 17593 "invalid selector": { 17594 old: core.ReplicationController{ 17595 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 17596 Spec: core.ReplicationControllerSpec{ 17597 Selector: validSelector, 17598 Template: &validPodTemplate.Template, 17599 }, 17600 }, 17601 update: core.ReplicationController{ 17602 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17603 Spec: core.ReplicationControllerSpec{ 17604 Replicas: 2, 17605 Selector: invalidSelector, 17606 Template: &validPodTemplate.Template, 17607 }, 17608 }, 17609 }, 17610 "invalid pod": { 17611 old: core.ReplicationController{ 17612 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 17613 Spec: core.ReplicationControllerSpec{ 17614 Selector: validSelector, 17615 Template: &validPodTemplate.Template, 17616 }, 17617 }, 17618 update: core.ReplicationController{ 17619 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17620 Spec: core.ReplicationControllerSpec{ 17621 Replicas: 2, 17622 Selector: validSelector, 17623 Template: &invalidPodTemplate.Template, 17624 }, 17625 }, 17626 }, 17627 "negative replicas": { 17628 old: core.ReplicationController{ 17629 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17630 Spec: core.ReplicationControllerSpec{ 17631 Selector: validSelector, 17632 Template: &validPodTemplate.Template, 17633 }, 17634 }, 17635 update: core.ReplicationController{ 17636 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17637 Spec: core.ReplicationControllerSpec{ 17638 Replicas: -1, 17639 Selector: validSelector, 17640 Template: &validPodTemplate.Template, 17641 }, 17642 }, 17643 }, 17644 } 17645 for testName, errorCase := range errorCases { 17646 if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old, PodValidationOptions{}); len(errs) == 0 { 17647 t.Errorf("expected failure: %s", testName) 17648 } 17649 } 17650 } 17651 17652 func TestValidateReplicationController(t *testing.T) { 17653 validSelector := map[string]string{"a": "b"} 17654 validPodTemplate := core.PodTemplate{ 17655 Template: core.PodTemplateSpec{ 17656 ObjectMeta: metav1.ObjectMeta{ 17657 Labels: validSelector, 17658 }, 17659 Spec: core.PodSpec{ 17660 RestartPolicy: core.RestartPolicyAlways, 17661 DNSPolicy: core.DNSClusterFirst, 17662 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17663 }, 17664 }, 17665 } 17666 readWriteVolumePodTemplate := core.PodTemplate{ 17667 Template: core.PodTemplateSpec{ 17668 ObjectMeta: metav1.ObjectMeta{ 17669 Labels: validSelector, 17670 }, 17671 Spec: core.PodSpec{ 17672 Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, 17673 RestartPolicy: core.RestartPolicyAlways, 17674 DNSPolicy: core.DNSClusterFirst, 17675 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17676 }, 17677 }, 17678 } 17679 hostnetPodTemplate := core.PodTemplate{ 17680 Template: core.PodTemplateSpec{ 17681 ObjectMeta: metav1.ObjectMeta{ 17682 Labels: validSelector, 17683 }, 17684 Spec: core.PodSpec{ 17685 SecurityContext: &core.PodSecurityContext{ 17686 HostNetwork: true, 17687 }, 17688 RestartPolicy: core.RestartPolicyAlways, 17689 DNSPolicy: core.DNSClusterFirst, 17690 Containers: []core.Container{{ 17691 Name: "abc", 17692 Image: "image", 17693 ImagePullPolicy: "IfNotPresent", 17694 TerminationMessagePolicy: "File", 17695 Ports: []core.ContainerPort{{ 17696 ContainerPort: 12345, 17697 Protocol: core.ProtocolTCP, 17698 }}, 17699 }}, 17700 }, 17701 }, 17702 } 17703 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 17704 invalidPodTemplate := core.PodTemplate{ 17705 Template: core.PodTemplateSpec{ 17706 Spec: core.PodSpec{ 17707 RestartPolicy: core.RestartPolicyAlways, 17708 DNSPolicy: core.DNSClusterFirst, 17709 }, 17710 ObjectMeta: metav1.ObjectMeta{ 17711 Labels: invalidSelector, 17712 }, 17713 }, 17714 } 17715 successCases := []core.ReplicationController{{ 17716 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17717 Spec: core.ReplicationControllerSpec{ 17718 Selector: validSelector, 17719 Template: &validPodTemplate.Template, 17720 }, 17721 }, { 17722 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, 17723 Spec: core.ReplicationControllerSpec{ 17724 Selector: validSelector, 17725 Template: &validPodTemplate.Template, 17726 }, 17727 }, { 17728 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, 17729 Spec: core.ReplicationControllerSpec{ 17730 Replicas: 1, 17731 Selector: validSelector, 17732 Template: &readWriteVolumePodTemplate.Template, 17733 }, 17734 }, { 17735 ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault}, 17736 Spec: core.ReplicationControllerSpec{ 17737 Replicas: 1, 17738 Selector: validSelector, 17739 Template: &hostnetPodTemplate.Template, 17740 }, 17741 }} 17742 for _, successCase := range successCases { 17743 if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 { 17744 t.Errorf("expected success: %v", errs) 17745 } 17746 } 17747 17748 errorCases := map[string]core.ReplicationController{ 17749 "zero-length ID": { 17750 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 17751 Spec: core.ReplicationControllerSpec{ 17752 Selector: validSelector, 17753 Template: &validPodTemplate.Template, 17754 }, 17755 }, 17756 "missing-namespace": { 17757 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, 17758 Spec: core.ReplicationControllerSpec{ 17759 Selector: validSelector, 17760 Template: &validPodTemplate.Template, 17761 }, 17762 }, 17763 "empty selector": { 17764 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17765 Spec: core.ReplicationControllerSpec{ 17766 Template: &validPodTemplate.Template, 17767 }, 17768 }, 17769 "selector_doesnt_match": { 17770 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17771 Spec: core.ReplicationControllerSpec{ 17772 Selector: map[string]string{"foo": "bar"}, 17773 Template: &validPodTemplate.Template, 17774 }, 17775 }, 17776 "invalid manifest": { 17777 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17778 Spec: core.ReplicationControllerSpec{ 17779 Selector: validSelector, 17780 }, 17781 }, 17782 "read-write persistent disk with > 1 pod": { 17783 ObjectMeta: metav1.ObjectMeta{Name: "abc"}, 17784 Spec: core.ReplicationControllerSpec{ 17785 Replicas: 2, 17786 Selector: validSelector, 17787 Template: &readWriteVolumePodTemplate.Template, 17788 }, 17789 }, 17790 "negative_replicas": { 17791 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 17792 Spec: core.ReplicationControllerSpec{ 17793 Replicas: -1, 17794 Selector: validSelector, 17795 }, 17796 }, 17797 "invalid_label": { 17798 ObjectMeta: metav1.ObjectMeta{ 17799 Name: "abc-123", 17800 Namespace: metav1.NamespaceDefault, 17801 Labels: map[string]string{ 17802 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 17803 }, 17804 }, 17805 Spec: core.ReplicationControllerSpec{ 17806 Selector: validSelector, 17807 Template: &validPodTemplate.Template, 17808 }, 17809 }, 17810 "invalid_label 2": { 17811 ObjectMeta: metav1.ObjectMeta{ 17812 Name: "abc-123", 17813 Namespace: metav1.NamespaceDefault, 17814 Labels: map[string]string{ 17815 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 17816 }, 17817 }, 17818 Spec: core.ReplicationControllerSpec{ 17819 Template: &invalidPodTemplate.Template, 17820 }, 17821 }, 17822 "invalid_annotation": { 17823 ObjectMeta: metav1.ObjectMeta{ 17824 Name: "abc-123", 17825 Namespace: metav1.NamespaceDefault, 17826 Annotations: map[string]string{ 17827 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 17828 }, 17829 }, 17830 Spec: core.ReplicationControllerSpec{ 17831 Selector: validSelector, 17832 Template: &validPodTemplate.Template, 17833 }, 17834 }, 17835 "invalid restart policy 1": { 17836 ObjectMeta: metav1.ObjectMeta{ 17837 Name: "abc-123", 17838 Namespace: metav1.NamespaceDefault, 17839 }, 17840 Spec: core.ReplicationControllerSpec{ 17841 Selector: validSelector, 17842 Template: &core.PodTemplateSpec{ 17843 Spec: core.PodSpec{ 17844 RestartPolicy: core.RestartPolicyOnFailure, 17845 DNSPolicy: core.DNSClusterFirst, 17846 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17847 }, 17848 ObjectMeta: metav1.ObjectMeta{ 17849 Labels: validSelector, 17850 }, 17851 }, 17852 }, 17853 }, 17854 "invalid restart policy 2": { 17855 ObjectMeta: metav1.ObjectMeta{ 17856 Name: "abc-123", 17857 Namespace: metav1.NamespaceDefault, 17858 }, 17859 Spec: core.ReplicationControllerSpec{ 17860 Selector: validSelector, 17861 Template: &core.PodTemplateSpec{ 17862 Spec: core.PodSpec{ 17863 RestartPolicy: core.RestartPolicyNever, 17864 DNSPolicy: core.DNSClusterFirst, 17865 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17866 }, 17867 ObjectMeta: metav1.ObjectMeta{ 17868 Labels: validSelector, 17869 }, 17870 }, 17871 }, 17872 }, 17873 "template may not contain ephemeral containers": { 17874 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, 17875 Spec: core.ReplicationControllerSpec{ 17876 Replicas: 1, 17877 Selector: validSelector, 17878 Template: &core.PodTemplateSpec{ 17879 ObjectMeta: metav1.ObjectMeta{ 17880 Labels: validSelector, 17881 }, 17882 Spec: core.PodSpec{ 17883 RestartPolicy: core.RestartPolicyAlways, 17884 DNSPolicy: core.DNSClusterFirst, 17885 Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 17886 EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}}, 17887 }, 17888 }, 17889 }, 17890 }, 17891 } 17892 for k, v := range errorCases { 17893 errs := ValidateReplicationController(&v, PodValidationOptions{}) 17894 if len(errs) == 0 { 17895 t.Errorf("expected failure for %s", k) 17896 } 17897 for i := range errs { 17898 field := errs[i].Field 17899 if !strings.HasPrefix(field, "spec.template.") && 17900 field != "metadata.name" && 17901 field != "metadata.namespace" && 17902 field != "spec.selector" && 17903 field != "spec.template" && 17904 field != "GCEPersistentDisk.ReadOnly" && 17905 field != "spec.replicas" && 17906 field != "spec.template.labels" && 17907 field != "metadata.annotations" && 17908 field != "metadata.labels" && 17909 field != "status.replicas" { 17910 t.Errorf("%s: missing prefix for: %v", k, errs[i]) 17911 } 17912 } 17913 } 17914 } 17915 17916 func TestValidateNode(t *testing.T) { 17917 validSelector := map[string]string{"a": "b"} 17918 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 17919 successCases := []core.Node{{ 17920 ObjectMeta: metav1.ObjectMeta{ 17921 Name: "abc", 17922 Labels: validSelector, 17923 }, 17924 Status: core.NodeStatus{ 17925 Addresses: []core.NodeAddress{ 17926 {Type: core.NodeExternalIP, Address: "something"}, 17927 }, 17928 Capacity: core.ResourceList{ 17929 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 17930 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 17931 core.ResourceName("my.org/gpu"): resource.MustParse("10"), 17932 core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), 17933 core.ResourceName("hugepages-1Gi"): resource.MustParse("0"), 17934 }, 17935 }, 17936 }, { 17937 ObjectMeta: metav1.ObjectMeta{ 17938 Name: "abc", 17939 }, 17940 Status: core.NodeStatus{ 17941 Addresses: []core.NodeAddress{ 17942 {Type: core.NodeExternalIP, Address: "something"}, 17943 }, 17944 Capacity: core.ResourceList{ 17945 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 17946 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 17947 }, 17948 }, 17949 }, { 17950 ObjectMeta: metav1.ObjectMeta{ 17951 Name: "abc", 17952 Labels: validSelector, 17953 }, 17954 Status: core.NodeStatus{ 17955 Addresses: []core.NodeAddress{ 17956 {Type: core.NodeExternalIP, Address: "something"}, 17957 }, 17958 Capacity: core.ResourceList{ 17959 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 17960 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 17961 core.ResourceName("my.org/gpu"): resource.MustParse("10"), 17962 core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"), 17963 core.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"), 17964 }, 17965 }, 17966 }, { 17967 ObjectMeta: metav1.ObjectMeta{ 17968 Name: "dedicated-node1", 17969 }, 17970 Status: core.NodeStatus{ 17971 Addresses: []core.NodeAddress{ 17972 {Type: core.NodeExternalIP, Address: "something"}, 17973 }, 17974 Capacity: core.ResourceList{ 17975 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 17976 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 17977 }, 17978 }, 17979 Spec: core.NodeSpec{ 17980 // Add a valid taint to a node 17981 Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}}, 17982 }, 17983 }, { 17984 ObjectMeta: metav1.ObjectMeta{ 17985 Name: "abc", 17986 Annotations: map[string]string{ 17987 core.PreferAvoidPodsAnnotationKey: ` 17988 { 17989 "preferAvoidPods": [ 17990 { 17991 "podSignature": { 17992 "podController": { 17993 "apiVersion": "v1", 17994 "kind": "ReplicationController", 17995 "name": "foo", 17996 "uid": "abcdef123456", 17997 "controller": true 17998 } 17999 }, 18000 "reason": "some reason", 18001 "message": "some message" 18002 } 18003 ] 18004 }`, 18005 }, 18006 }, 18007 Status: core.NodeStatus{ 18008 Addresses: []core.NodeAddress{ 18009 {Type: core.NodeExternalIP, Address: "something"}, 18010 }, 18011 Capacity: core.ResourceList{ 18012 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18013 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18014 }, 18015 }, 18016 }, { 18017 ObjectMeta: metav1.ObjectMeta{ 18018 Name: "abc", 18019 }, 18020 Status: core.NodeStatus{ 18021 Addresses: []core.NodeAddress{ 18022 {Type: core.NodeExternalIP, Address: "something"}, 18023 }, 18024 Capacity: core.ResourceList{ 18025 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18026 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18027 }, 18028 }, 18029 Spec: core.NodeSpec{ 18030 PodCIDRs: []string{"192.168.0.0/16"}, 18031 }, 18032 }, 18033 } 18034 for _, successCase := range successCases { 18035 if errs := ValidateNode(&successCase); len(errs) != 0 { 18036 t.Errorf("expected success: %v", errs) 18037 } 18038 } 18039 18040 errorCases := map[string]core.Node{ 18041 "zero-length Name": { 18042 ObjectMeta: metav1.ObjectMeta{ 18043 Name: "", 18044 Labels: validSelector, 18045 }, 18046 Status: core.NodeStatus{ 18047 Addresses: []core.NodeAddress{}, 18048 Capacity: core.ResourceList{ 18049 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18050 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 18051 }, 18052 }, 18053 }, 18054 "invalid-labels": { 18055 ObjectMeta: metav1.ObjectMeta{ 18056 Name: "abc-123", 18057 Labels: invalidSelector, 18058 }, 18059 Status: core.NodeStatus{ 18060 Capacity: core.ResourceList{ 18061 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18062 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 18063 }, 18064 }, 18065 }, 18066 "missing-taint-key": { 18067 ObjectMeta: metav1.ObjectMeta{ 18068 Name: "dedicated-node1", 18069 }, 18070 Spec: core.NodeSpec{ 18071 // Add a taint with an empty key to a node 18072 Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}}, 18073 }, 18074 }, 18075 "bad-taint-key": { 18076 ObjectMeta: metav1.ObjectMeta{ 18077 Name: "dedicated-node1", 18078 }, 18079 Spec: core.NodeSpec{ 18080 // Add a taint with an invalid key to a node 18081 Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}}, 18082 }, 18083 }, 18084 "bad-taint-value": { 18085 ObjectMeta: metav1.ObjectMeta{ 18086 Name: "dedicated-node2", 18087 }, 18088 Status: core.NodeStatus{ 18089 Addresses: []core.NodeAddress{ 18090 {Type: core.NodeExternalIP, Address: "something"}, 18091 }, 18092 Capacity: core.ResourceList{ 18093 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18094 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18095 }, 18096 }, 18097 Spec: core.NodeSpec{ 18098 // Add a taint with a bad value to a node 18099 Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}}, 18100 }, 18101 }, 18102 "missing-taint-effect": { 18103 ObjectMeta: metav1.ObjectMeta{ 18104 Name: "dedicated-node3", 18105 }, 18106 Status: core.NodeStatus{ 18107 Addresses: []core.NodeAddress{ 18108 {Type: core.NodeExternalIP, Address: "something"}, 18109 }, 18110 Capacity: core.ResourceList{ 18111 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18112 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18113 }, 18114 }, 18115 Spec: core.NodeSpec{ 18116 // Add a taint with an empty effect to a node 18117 Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}}, 18118 }, 18119 }, 18120 "invalid-taint-effect": { 18121 ObjectMeta: metav1.ObjectMeta{ 18122 Name: "dedicated-node3", 18123 }, 18124 Status: core.NodeStatus{ 18125 Addresses: []core.NodeAddress{ 18126 {Type: core.NodeExternalIP, Address: "something"}, 18127 }, 18128 Capacity: core.ResourceList{ 18129 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18130 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18131 }, 18132 }, 18133 Spec: core.NodeSpec{ 18134 // Add a taint with NoExecute effect to a node 18135 Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}}, 18136 }, 18137 }, 18138 "duplicated-taints-with-same-key-effect": { 18139 ObjectMeta: metav1.ObjectMeta{ 18140 Name: "dedicated-node1", 18141 }, 18142 Spec: core.NodeSpec{ 18143 // Add two taints to the node with the same key and effect; should be rejected. 18144 Taints: []core.Taint{ 18145 {Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"}, 18146 {Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"}, 18147 }, 18148 }, 18149 }, 18150 "missing-podSignature": { 18151 ObjectMeta: metav1.ObjectMeta{ 18152 Name: "abc-123", 18153 Annotations: map[string]string{ 18154 core.PreferAvoidPodsAnnotationKey: ` 18155 { 18156 "preferAvoidPods": [ 18157 { 18158 "reason": "some reason", 18159 "message": "some message" 18160 } 18161 ] 18162 }`, 18163 }, 18164 }, 18165 Status: core.NodeStatus{ 18166 Addresses: []core.NodeAddress{}, 18167 Capacity: core.ResourceList{ 18168 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18169 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18170 }, 18171 }, 18172 }, 18173 "invalid-podController": { 18174 ObjectMeta: metav1.ObjectMeta{ 18175 Name: "abc-123", 18176 Annotations: map[string]string{ 18177 core.PreferAvoidPodsAnnotationKey: ` 18178 { 18179 "preferAvoidPods": [ 18180 { 18181 "podSignature": { 18182 "podController": { 18183 "apiVersion": "v1", 18184 "kind": "ReplicationController", 18185 "name": "foo", 18186 "uid": "abcdef123456", 18187 "controller": false 18188 } 18189 }, 18190 "reason": "some reason", 18191 "message": "some message" 18192 } 18193 ] 18194 }`, 18195 }, 18196 }, 18197 Status: core.NodeStatus{ 18198 Addresses: []core.NodeAddress{}, 18199 Capacity: core.ResourceList{ 18200 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18201 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18202 }, 18203 }, 18204 }, 18205 "invalid-pod-cidr": { 18206 ObjectMeta: metav1.ObjectMeta{ 18207 Name: "abc", 18208 }, 18209 Status: core.NodeStatus{ 18210 Addresses: []core.NodeAddress{ 18211 {Type: core.NodeExternalIP, Address: "something"}, 18212 }, 18213 Capacity: core.ResourceList{ 18214 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18215 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18216 }, 18217 }, 18218 Spec: core.NodeSpec{ 18219 PodCIDRs: []string{"192.168.0.0"}, 18220 }, 18221 }, 18222 "duplicate-pod-cidr": { 18223 ObjectMeta: metav1.ObjectMeta{ 18224 Name: "abc", 18225 }, 18226 Status: core.NodeStatus{ 18227 Addresses: []core.NodeAddress{ 18228 {Type: core.NodeExternalIP, Address: "something"}, 18229 }, 18230 Capacity: core.ResourceList{ 18231 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18232 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 18233 }, 18234 }, 18235 Spec: core.NodeSpec{ 18236 PodCIDRs: []string{"10.0.0.1/16", "10.0.0.1/16"}, 18237 }, 18238 }, 18239 } 18240 for k, v := range errorCases { 18241 errs := ValidateNode(&v) 18242 if len(errs) == 0 { 18243 t.Errorf("expected failure for %s", k) 18244 } 18245 for i := range errs { 18246 field := errs[i].Field 18247 expectedFields := map[string]bool{ 18248 "metadata.name": true, 18249 "metadata.labels": true, 18250 "metadata.annotations": true, 18251 "metadata.namespace": true, 18252 "spec.externalID": true, 18253 "spec.taints[0].key": true, 18254 "spec.taints[0].value": true, 18255 "spec.taints[0].effect": true, 18256 "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature": true, 18257 "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true, 18258 } 18259 if val, ok := expectedFields[field]; ok { 18260 if !val { 18261 t.Errorf("%s: missing prefix for: %v", k, errs[i]) 18262 } 18263 } 18264 } 18265 } 18266 } 18267 18268 func TestValidateNodeUpdate(t *testing.T) { 18269 tests := []struct { 18270 oldNode core.Node 18271 node core.Node 18272 valid bool 18273 }{ 18274 {core.Node{}, core.Node{}, true}, 18275 {core.Node{ 18276 ObjectMeta: metav1.ObjectMeta{ 18277 Name: "foo"}}, 18278 core.Node{ 18279 ObjectMeta: metav1.ObjectMeta{ 18280 Name: "bar"}, 18281 }, false}, 18282 {core.Node{ 18283 ObjectMeta: metav1.ObjectMeta{ 18284 Name: "foo", 18285 Labels: map[string]string{"foo": "bar"}, 18286 }, 18287 }, core.Node{ 18288 ObjectMeta: metav1.ObjectMeta{ 18289 Name: "foo", 18290 Labels: map[string]string{"foo": "baz"}, 18291 }, 18292 }, true}, 18293 {core.Node{ 18294 ObjectMeta: metav1.ObjectMeta{ 18295 Name: "foo", 18296 }, 18297 }, core.Node{ 18298 ObjectMeta: metav1.ObjectMeta{ 18299 Name: "foo", 18300 Labels: map[string]string{"foo": "baz"}, 18301 }, 18302 }, true}, 18303 {core.Node{ 18304 ObjectMeta: metav1.ObjectMeta{ 18305 Name: "foo", 18306 Labels: map[string]string{"bar": "foo"}, 18307 }, 18308 }, core.Node{ 18309 ObjectMeta: metav1.ObjectMeta{ 18310 Name: "foo", 18311 Labels: map[string]string{"foo": "baz"}, 18312 }, 18313 }, true}, 18314 {core.Node{ 18315 ObjectMeta: metav1.ObjectMeta{ 18316 Name: "foo", 18317 }, 18318 Spec: core.NodeSpec{ 18319 PodCIDRs: []string{}, 18320 }, 18321 }, core.Node{ 18322 ObjectMeta: metav1.ObjectMeta{ 18323 Name: "foo", 18324 }, 18325 Spec: core.NodeSpec{ 18326 PodCIDRs: []string{"192.168.0.0/16"}, 18327 }, 18328 }, true}, 18329 {core.Node{ 18330 ObjectMeta: metav1.ObjectMeta{ 18331 Name: "foo", 18332 }, 18333 Spec: core.NodeSpec{ 18334 PodCIDRs: []string{"192.123.0.0/16"}, 18335 }, 18336 }, core.Node{ 18337 ObjectMeta: metav1.ObjectMeta{ 18338 Name: "foo", 18339 }, 18340 Spec: core.NodeSpec{ 18341 PodCIDRs: []string{"192.168.0.0/16"}, 18342 }, 18343 }, false}, 18344 {core.Node{ 18345 ObjectMeta: metav1.ObjectMeta{ 18346 Name: "foo", 18347 }, 18348 Status: core.NodeStatus{ 18349 Capacity: core.ResourceList{ 18350 core.ResourceCPU: resource.MustParse("10000"), 18351 core.ResourceMemory: resource.MustParse("100"), 18352 }, 18353 }, 18354 }, core.Node{ 18355 ObjectMeta: metav1.ObjectMeta{ 18356 Name: "foo", 18357 }, 18358 Status: core.NodeStatus{ 18359 Capacity: core.ResourceList{ 18360 core.ResourceCPU: resource.MustParse("100"), 18361 core.ResourceMemory: resource.MustParse("10000"), 18362 }, 18363 }, 18364 }, true}, 18365 {core.Node{ 18366 ObjectMeta: metav1.ObjectMeta{ 18367 Name: "foo", 18368 Labels: map[string]string{"bar": "foo"}, 18369 }, 18370 Status: core.NodeStatus{ 18371 Capacity: core.ResourceList{ 18372 core.ResourceCPU: resource.MustParse("10000"), 18373 core.ResourceMemory: resource.MustParse("100"), 18374 }, 18375 }, 18376 }, core.Node{ 18377 ObjectMeta: metav1.ObjectMeta{ 18378 Name: "foo", 18379 Labels: map[string]string{"bar": "fooobaz"}, 18380 }, 18381 Status: core.NodeStatus{ 18382 Capacity: core.ResourceList{ 18383 core.ResourceCPU: resource.MustParse("100"), 18384 core.ResourceMemory: resource.MustParse("10000"), 18385 }, 18386 }, 18387 }, true}, 18388 {core.Node{ 18389 ObjectMeta: metav1.ObjectMeta{ 18390 Name: "foo", 18391 Labels: map[string]string{"bar": "foo"}, 18392 }, 18393 Status: core.NodeStatus{ 18394 Addresses: []core.NodeAddress{ 18395 {Type: core.NodeExternalIP, Address: "1.2.3.4"}, 18396 }, 18397 }, 18398 }, core.Node{ 18399 ObjectMeta: metav1.ObjectMeta{ 18400 Name: "foo", 18401 Labels: map[string]string{"bar": "fooobaz"}, 18402 }, 18403 }, true}, 18404 {core.Node{ 18405 ObjectMeta: metav1.ObjectMeta{ 18406 Name: "foo", 18407 Labels: map[string]string{"foo": "baz"}, 18408 }, 18409 }, core.Node{ 18410 ObjectMeta: metav1.ObjectMeta{ 18411 Name: "foo", 18412 Labels: map[string]string{"Foo": "baz"}, 18413 }, 18414 }, true}, 18415 {core.Node{ 18416 ObjectMeta: metav1.ObjectMeta{ 18417 Name: "foo", 18418 }, 18419 Spec: core.NodeSpec{ 18420 Unschedulable: false, 18421 }, 18422 }, core.Node{ 18423 ObjectMeta: metav1.ObjectMeta{ 18424 Name: "foo", 18425 }, 18426 Spec: core.NodeSpec{ 18427 Unschedulable: true, 18428 }, 18429 }, true}, 18430 {core.Node{ 18431 ObjectMeta: metav1.ObjectMeta{ 18432 Name: "foo", 18433 }, 18434 Spec: core.NodeSpec{ 18435 Unschedulable: false, 18436 }, 18437 }, core.Node{ 18438 ObjectMeta: metav1.ObjectMeta{ 18439 Name: "foo", 18440 }, 18441 Status: core.NodeStatus{ 18442 Addresses: []core.NodeAddress{ 18443 {Type: core.NodeExternalIP, Address: "1.1.1.1"}, 18444 {Type: core.NodeExternalIP, Address: "1.1.1.1"}, 18445 }, 18446 }, 18447 }, false}, 18448 {core.Node{ 18449 ObjectMeta: metav1.ObjectMeta{ 18450 Name: "foo", 18451 }, 18452 Spec: core.NodeSpec{ 18453 Unschedulable: false, 18454 }, 18455 }, core.Node{ 18456 ObjectMeta: metav1.ObjectMeta{ 18457 Name: "foo", 18458 }, 18459 Status: core.NodeStatus{ 18460 Addresses: []core.NodeAddress{ 18461 {Type: core.NodeExternalIP, Address: "1.1.1.1"}, 18462 {Type: core.NodeInternalIP, Address: "10.1.1.1"}, 18463 }, 18464 }, 18465 }, true}, 18466 {core.Node{ 18467 ObjectMeta: metav1.ObjectMeta{ 18468 Name: "foo", 18469 }, 18470 }, core.Node{ 18471 ObjectMeta: metav1.ObjectMeta{ 18472 Name: "foo", 18473 Annotations: map[string]string{ 18474 core.PreferAvoidPodsAnnotationKey: ` 18475 { 18476 "preferAvoidPods": [ 18477 { 18478 "podSignature": { 18479 "podController": { 18480 "apiVersion": "v1", 18481 "kind": "ReplicationController", 18482 "name": "foo", 18483 "uid": "abcdef123456", 18484 "controller": true 18485 } 18486 }, 18487 "reason": "some reason", 18488 "message": "some message" 18489 } 18490 ] 18491 }`, 18492 }, 18493 }, 18494 Spec: core.NodeSpec{ 18495 Unschedulable: false, 18496 }, 18497 }, true}, 18498 {core.Node{ 18499 ObjectMeta: metav1.ObjectMeta{ 18500 Name: "foo", 18501 }, 18502 }, core.Node{ 18503 ObjectMeta: metav1.ObjectMeta{ 18504 Name: "foo", 18505 Annotations: map[string]string{ 18506 core.PreferAvoidPodsAnnotationKey: ` 18507 { 18508 "preferAvoidPods": [ 18509 { 18510 "reason": "some reason", 18511 "message": "some message" 18512 } 18513 ] 18514 }`, 18515 }, 18516 }, 18517 }, false}, 18518 {core.Node{ 18519 ObjectMeta: metav1.ObjectMeta{ 18520 Name: "foo", 18521 }, 18522 }, core.Node{ 18523 ObjectMeta: metav1.ObjectMeta{ 18524 Name: "foo", 18525 Annotations: map[string]string{ 18526 core.PreferAvoidPodsAnnotationKey: ` 18527 { 18528 "preferAvoidPods": [ 18529 { 18530 "podSignature": { 18531 "podController": { 18532 "apiVersion": "v1", 18533 "kind": "ReplicationController", 18534 "name": "foo", 18535 "uid": "abcdef123456", 18536 "controller": false 18537 } 18538 }, 18539 "reason": "some reason", 18540 "message": "some message" 18541 } 18542 ] 18543 }`, 18544 }, 18545 }, 18546 }, false}, 18547 {core.Node{ 18548 ObjectMeta: metav1.ObjectMeta{ 18549 Name: "valid-extended-resources", 18550 }, 18551 }, core.Node{ 18552 ObjectMeta: metav1.ObjectMeta{ 18553 Name: "valid-extended-resources", 18554 }, 18555 Status: core.NodeStatus{ 18556 Capacity: core.ResourceList{ 18557 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18558 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 18559 core.ResourceName("example.com/a"): resource.MustParse("5"), 18560 core.ResourceName("example.com/b"): resource.MustParse("10"), 18561 }, 18562 }, 18563 }, true}, 18564 {core.Node{ 18565 ObjectMeta: metav1.ObjectMeta{ 18566 Name: "invalid-fractional-extended-capacity", 18567 }, 18568 }, core.Node{ 18569 ObjectMeta: metav1.ObjectMeta{ 18570 Name: "invalid-fractional-extended-capacity", 18571 }, 18572 Status: core.NodeStatus{ 18573 Capacity: core.ResourceList{ 18574 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18575 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 18576 core.ResourceName("example.com/a"): resource.MustParse("500m"), 18577 }, 18578 }, 18579 }, false}, 18580 {core.Node{ 18581 ObjectMeta: metav1.ObjectMeta{ 18582 Name: "invalid-fractional-extended-allocatable", 18583 }, 18584 }, core.Node{ 18585 ObjectMeta: metav1.ObjectMeta{ 18586 Name: "invalid-fractional-extended-allocatable", 18587 }, 18588 Status: core.NodeStatus{ 18589 Capacity: core.ResourceList{ 18590 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18591 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 18592 core.ResourceName("example.com/a"): resource.MustParse("5"), 18593 }, 18594 Allocatable: core.ResourceList{ 18595 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 18596 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 18597 core.ResourceName("example.com/a"): resource.MustParse("4.5"), 18598 }, 18599 }, 18600 }, false}, 18601 {core.Node{ 18602 ObjectMeta: metav1.ObjectMeta{ 18603 Name: "update-provider-id-when-not-set", 18604 }, 18605 }, core.Node{ 18606 ObjectMeta: metav1.ObjectMeta{ 18607 Name: "update-provider-id-when-not-set", 18608 }, 18609 Spec: core.NodeSpec{ 18610 ProviderID: "provider:///new", 18611 }, 18612 }, true}, 18613 {core.Node{ 18614 ObjectMeta: metav1.ObjectMeta{ 18615 Name: "update-provider-id-when-set", 18616 }, 18617 Spec: core.NodeSpec{ 18618 ProviderID: "provider:///old", 18619 }, 18620 }, core.Node{ 18621 ObjectMeta: metav1.ObjectMeta{ 18622 Name: "update-provider-id-when-set", 18623 }, 18624 Spec: core.NodeSpec{ 18625 ProviderID: "provider:///new", 18626 }, 18627 }, false}, 18628 {core.Node{ 18629 ObjectMeta: metav1.ObjectMeta{ 18630 Name: "pod-cidrs-as-is", 18631 }, 18632 Spec: core.NodeSpec{ 18633 PodCIDRs: []string{"192.168.0.0/16"}, 18634 }, 18635 }, core.Node{ 18636 ObjectMeta: metav1.ObjectMeta{ 18637 Name: "pod-cidrs-as-is", 18638 }, 18639 Spec: core.NodeSpec{ 18640 PodCIDRs: []string{"192.168.0.0/16"}, 18641 }, 18642 }, true}, 18643 {core.Node{ 18644 ObjectMeta: metav1.ObjectMeta{ 18645 Name: "pod-cidrs-as-is-2", 18646 }, 18647 Spec: core.NodeSpec{ 18648 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"}, 18649 }, 18650 }, core.Node{ 18651 ObjectMeta: metav1.ObjectMeta{ 18652 Name: "pod-cidrs-as-is-2", 18653 }, 18654 Spec: core.NodeSpec{ 18655 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"}, 18656 }, 18657 }, true}, 18658 {core.Node{ 18659 ObjectMeta: metav1.ObjectMeta{ 18660 Name: "pod-cidrs-not-same-length", 18661 }, 18662 Spec: core.NodeSpec{ 18663 PodCIDRs: []string{"192.168.0.0/16", "192.167.0.0/16", "2000::/10"}, 18664 }, 18665 }, core.Node{ 18666 ObjectMeta: metav1.ObjectMeta{ 18667 Name: "pod-cidrs-not-same-length", 18668 }, 18669 Spec: core.NodeSpec{ 18670 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"}, 18671 }, 18672 }, false}, 18673 {core.Node{ 18674 ObjectMeta: metav1.ObjectMeta{ 18675 Name: "pod-cidrs-not-same", 18676 }, 18677 Spec: core.NodeSpec{ 18678 PodCIDRs: []string{"192.168.0.0/16", "2000::/10"}, 18679 }, 18680 }, core.Node{ 18681 ObjectMeta: metav1.ObjectMeta{ 18682 Name: "pod-cidrs-not-same", 18683 }, 18684 Spec: core.NodeSpec{ 18685 PodCIDRs: []string{"2000::/10", "192.168.0.0/16"}, 18686 }, 18687 }, false}, 18688 } 18689 for i, test := range tests { 18690 test.oldNode.ObjectMeta.ResourceVersion = "1" 18691 test.node.ObjectMeta.ResourceVersion = "1" 18692 errs := ValidateNodeUpdate(&test.node, &test.oldNode) 18693 if test.valid && len(errs) > 0 { 18694 t.Errorf("%d: Unexpected error: %v", i, errs) 18695 t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta) 18696 } 18697 if !test.valid && len(errs) == 0 { 18698 t.Errorf("%d: Unexpected non-error", i) 18699 } 18700 } 18701 } 18702 18703 func TestValidateServiceUpdate(t *testing.T) { 18704 requireDualStack := core.IPFamilyPolicyRequireDualStack 18705 preferDualStack := core.IPFamilyPolicyPreferDualStack 18706 singleStack := core.IPFamilyPolicySingleStack 18707 testCases := []struct { 18708 name string 18709 tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them 18710 numErrs int 18711 }{{ 18712 name: "no change", 18713 tweakSvc: func(oldSvc, newSvc *core.Service) { 18714 // do nothing 18715 }, 18716 numErrs: 0, 18717 }, { 18718 name: "change name", 18719 tweakSvc: func(oldSvc, newSvc *core.Service) { 18720 newSvc.Name += "2" 18721 }, 18722 numErrs: 1, 18723 }, { 18724 name: "change namespace", 18725 tweakSvc: func(oldSvc, newSvc *core.Service) { 18726 newSvc.Namespace += "2" 18727 }, 18728 numErrs: 1, 18729 }, { 18730 name: "change label valid", 18731 tweakSvc: func(oldSvc, newSvc *core.Service) { 18732 newSvc.Labels["key"] = "other-value" 18733 }, 18734 numErrs: 0, 18735 }, { 18736 name: "add label", 18737 tweakSvc: func(oldSvc, newSvc *core.Service) { 18738 newSvc.Labels["key2"] = "value2" 18739 }, 18740 numErrs: 0, 18741 }, { 18742 name: "change cluster IP", 18743 tweakSvc: func(oldSvc, newSvc *core.Service) { 18744 oldSvc.Spec.ClusterIP = "1.2.3.4" 18745 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18746 18747 newSvc.Spec.ClusterIP = "8.6.7.5" 18748 newSvc.Spec.ClusterIPs = []string{"8.6.7.5"} 18749 }, 18750 numErrs: 1, 18751 }, { 18752 name: "remove cluster IP", 18753 tweakSvc: func(oldSvc, newSvc *core.Service) { 18754 oldSvc.Spec.ClusterIP = "1.2.3.4" 18755 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18756 18757 newSvc.Spec.ClusterIP = "" 18758 newSvc.Spec.ClusterIPs = nil 18759 }, 18760 numErrs: 1, 18761 }, { 18762 name: "change affinity", 18763 tweakSvc: func(oldSvc, newSvc *core.Service) { 18764 newSvc.Spec.SessionAffinity = "ClientIP" 18765 newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{ 18766 ClientIP: &core.ClientIPConfig{ 18767 TimeoutSeconds: utilpointer.Int32(90), 18768 }, 18769 } 18770 }, 18771 numErrs: 0, 18772 }, { 18773 name: "remove affinity", 18774 tweakSvc: func(oldSvc, newSvc *core.Service) { 18775 newSvc.Spec.SessionAffinity = "" 18776 }, 18777 numErrs: 1, 18778 }, { 18779 name: "change type", 18780 tweakSvc: func(oldSvc, newSvc *core.Service) { 18781 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18782 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18783 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18784 }, 18785 numErrs: 0, 18786 }, { 18787 name: "remove type", 18788 tweakSvc: func(oldSvc, newSvc *core.Service) { 18789 newSvc.Spec.Type = "" 18790 }, 18791 numErrs: 1, 18792 }, { 18793 name: "change type -> nodeport", 18794 tweakSvc: func(oldSvc, newSvc *core.Service) { 18795 newSvc.Spec.Type = core.ServiceTypeNodePort 18796 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18797 }, 18798 numErrs: 0, 18799 }, { 18800 name: "add loadBalancerSourceRanges", 18801 tweakSvc: func(oldSvc, newSvc *core.Service) { 18802 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 18803 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18804 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18805 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18806 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18807 newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} 18808 }, 18809 numErrs: 0, 18810 }, { 18811 name: "update loadBalancerSourceRanges", 18812 tweakSvc: func(oldSvc, newSvc *core.Service) { 18813 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 18814 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18815 oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"} 18816 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18817 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18818 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18819 newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"} 18820 }, 18821 numErrs: 0, 18822 }, { 18823 name: "LoadBalancer type cannot have None ClusterIP", 18824 tweakSvc: func(oldSvc, newSvc *core.Service) { 18825 newSvc.Spec.ClusterIP = "None" 18826 newSvc.Spec.ClusterIPs = []string{"None"} 18827 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18828 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18829 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18830 }, 18831 numErrs: 1, 18832 }, { 18833 name: "`None` ClusterIP can NOT be changed", 18834 tweakSvc: func(oldSvc, newSvc *core.Service) { 18835 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18836 newSvc.Spec.Type = core.ServiceTypeClusterIP 18837 18838 oldSvc.Spec.ClusterIP = "None" 18839 oldSvc.Spec.ClusterIPs = []string{"None"} 18840 18841 newSvc.Spec.ClusterIP = "1.2.3.4" 18842 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18843 }, 18844 numErrs: 1, 18845 }, { 18846 name: "`None` ClusterIP can NOT be removed", 18847 tweakSvc: func(oldSvc, newSvc *core.Service) { 18848 oldSvc.Spec.ClusterIP = "None" 18849 oldSvc.Spec.ClusterIPs = []string{"None"} 18850 18851 newSvc.Spec.ClusterIP = "" 18852 newSvc.Spec.ClusterIPs = nil 18853 }, 18854 numErrs: 1, 18855 }, { 18856 name: "ClusterIP can NOT be changed to None", 18857 tweakSvc: func(oldSvc, newSvc *core.Service) { 18858 oldSvc.Spec.ClusterIP = "1.2.3.4" 18859 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18860 18861 newSvc.Spec.ClusterIP = "None" 18862 newSvc.Spec.ClusterIPs = []string{"None"} 18863 }, 18864 numErrs: 1, 18865 }, 18866 18867 { 18868 name: "Service with ClusterIP type cannot change its set ClusterIP", 18869 tweakSvc: func(oldSvc, newSvc *core.Service) { 18870 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18871 newSvc.Spec.Type = core.ServiceTypeClusterIP 18872 18873 oldSvc.Spec.ClusterIP = "1.2.3.4" 18874 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18875 18876 newSvc.Spec.ClusterIP = "1.2.3.5" 18877 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18878 }, 18879 numErrs: 1, 18880 }, { 18881 name: "Service with ClusterIP type can change its empty ClusterIP", 18882 tweakSvc: func(oldSvc, newSvc *core.Service) { 18883 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18884 newSvc.Spec.Type = core.ServiceTypeClusterIP 18885 18886 oldSvc.Spec.ClusterIP = "" 18887 oldSvc.Spec.ClusterIPs = nil 18888 newSvc.Spec.ClusterIP = "1.2.3.5" 18889 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18890 }, 18891 numErrs: 0, 18892 }, { 18893 name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort", 18894 tweakSvc: func(oldSvc, newSvc *core.Service) { 18895 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18896 newSvc.Spec.Type = core.ServiceTypeNodePort 18897 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18898 18899 oldSvc.Spec.ClusterIP = "1.2.3.4" 18900 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18901 18902 newSvc.Spec.ClusterIP = "1.2.3.5" 18903 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18904 }, 18905 numErrs: 1, 18906 }, { 18907 name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort", 18908 tweakSvc: func(oldSvc, newSvc *core.Service) { 18909 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18910 newSvc.Spec.Type = core.ServiceTypeNodePort 18911 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18912 18913 oldSvc.Spec.ClusterIP = "" 18914 oldSvc.Spec.ClusterIPs = nil 18915 18916 newSvc.Spec.ClusterIP = "1.2.3.5" 18917 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18918 }, 18919 numErrs: 0, 18920 }, { 18921 name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer", 18922 tweakSvc: func(oldSvc, newSvc *core.Service) { 18923 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18924 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18925 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18926 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18927 18928 oldSvc.Spec.ClusterIP = "1.2.3.4" 18929 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18930 18931 newSvc.Spec.ClusterIP = "1.2.3.5" 18932 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18933 }, 18934 numErrs: 1, 18935 }, { 18936 name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer", 18937 tweakSvc: func(oldSvc, newSvc *core.Service) { 18938 oldSvc.Spec.Type = core.ServiceTypeClusterIP 18939 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18940 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18941 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18942 18943 oldSvc.Spec.ClusterIP = "" 18944 oldSvc.Spec.ClusterIPs = nil 18945 18946 newSvc.Spec.ClusterIP = "1.2.3.5" 18947 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18948 }, 18949 numErrs: 0, 18950 }, { 18951 name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from true to false", 18952 tweakSvc: func(oldSvc, newSvc *core.Service) { 18953 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 18954 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18955 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18956 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18957 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) 18958 }, 18959 numErrs: 0, 18960 }, { 18961 name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from false to true", 18962 tweakSvc: func(oldSvc, newSvc *core.Service) { 18963 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 18964 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) 18965 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 18966 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18967 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 18968 }, 18969 numErrs: 0, 18970 }, { 18971 name: "Service with NodePort type cannot change its set ClusterIP", 18972 tweakSvc: func(oldSvc, newSvc *core.Service) { 18973 oldSvc.Spec.Type = core.ServiceTypeNodePort 18974 newSvc.Spec.Type = core.ServiceTypeNodePort 18975 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18976 18977 oldSvc.Spec.ClusterIP = "1.2.3.4" 18978 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 18979 18980 newSvc.Spec.ClusterIP = "1.2.3.5" 18981 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18982 }, 18983 numErrs: 1, 18984 }, { 18985 name: "Service with NodePort type can change its empty ClusterIP", 18986 tweakSvc: func(oldSvc, newSvc *core.Service) { 18987 oldSvc.Spec.Type = core.ServiceTypeNodePort 18988 newSvc.Spec.Type = core.ServiceTypeNodePort 18989 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 18990 18991 oldSvc.Spec.ClusterIP = "" 18992 oldSvc.Spec.ClusterIPs = nil 18993 18994 newSvc.Spec.ClusterIP = "1.2.3.5" 18995 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 18996 }, 18997 numErrs: 0, 18998 }, { 18999 name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP", 19000 tweakSvc: func(oldSvc, newSvc *core.Service) { 19001 oldSvc.Spec.Type = core.ServiceTypeNodePort 19002 newSvc.Spec.Type = core.ServiceTypeClusterIP 19003 19004 oldSvc.Spec.ClusterIP = "1.2.3.4" 19005 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19006 19007 newSvc.Spec.ClusterIP = "1.2.3.5" 19008 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19009 }, 19010 numErrs: 1, 19011 }, { 19012 name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP", 19013 tweakSvc: func(oldSvc, newSvc *core.Service) { 19014 oldSvc.Spec.Type = core.ServiceTypeNodePort 19015 newSvc.Spec.Type = core.ServiceTypeClusterIP 19016 19017 oldSvc.Spec.ClusterIP = "" 19018 oldSvc.Spec.ClusterIPs = nil 19019 19020 newSvc.Spec.ClusterIP = "1.2.3.5" 19021 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19022 }, 19023 numErrs: 0, 19024 }, { 19025 name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer", 19026 tweakSvc: func(oldSvc, newSvc *core.Service) { 19027 oldSvc.Spec.Type = core.ServiceTypeNodePort 19028 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19029 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19030 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19031 19032 oldSvc.Spec.ClusterIP = "1.2.3.4" 19033 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19034 19035 newSvc.Spec.ClusterIP = "1.2.3.5" 19036 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19037 }, 19038 numErrs: 1, 19039 }, { 19040 name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer", 19041 tweakSvc: func(oldSvc, newSvc *core.Service) { 19042 oldSvc.Spec.Type = core.ServiceTypeNodePort 19043 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19044 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19045 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19046 19047 oldSvc.Spec.ClusterIP = "" 19048 oldSvc.Spec.ClusterIPs = nil 19049 19050 newSvc.Spec.ClusterIP = "1.2.3.5" 19051 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19052 }, 19053 numErrs: 0, 19054 }, { 19055 name: "Service with LoadBalancer type cannot change its set ClusterIP", 19056 tweakSvc: func(oldSvc, newSvc *core.Service) { 19057 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19058 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19059 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19060 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19061 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19062 19063 oldSvc.Spec.ClusterIP = "1.2.3.4" 19064 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19065 19066 newSvc.Spec.ClusterIP = "1.2.3.5" 19067 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19068 }, 19069 numErrs: 1, 19070 }, { 19071 name: "Service with LoadBalancer type can change its empty ClusterIP", 19072 tweakSvc: func(oldSvc, newSvc *core.Service) { 19073 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19074 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19075 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19076 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19077 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19078 19079 oldSvc.Spec.ClusterIP = "" 19080 oldSvc.Spec.ClusterIPs = nil 19081 19082 newSvc.Spec.ClusterIP = "1.2.3.5" 19083 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19084 }, 19085 numErrs: 0, 19086 }, { 19087 name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP", 19088 tweakSvc: func(oldSvc, newSvc *core.Service) { 19089 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19090 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19091 newSvc.Spec.Type = core.ServiceTypeClusterIP 19092 19093 oldSvc.Spec.ClusterIP = "1.2.3.4" 19094 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19095 19096 newSvc.Spec.ClusterIP = "1.2.3.5" 19097 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19098 }, 19099 numErrs: 1, 19100 }, { 19101 name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP", 19102 tweakSvc: func(oldSvc, newSvc *core.Service) { 19103 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19104 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19105 newSvc.Spec.Type = core.ServiceTypeClusterIP 19106 19107 oldSvc.Spec.ClusterIP = "" 19108 oldSvc.Spec.ClusterIPs = nil 19109 19110 newSvc.Spec.ClusterIP = "1.2.3.5" 19111 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19112 }, 19113 numErrs: 0, 19114 }, { 19115 name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort", 19116 tweakSvc: func(oldSvc, newSvc *core.Service) { 19117 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19118 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19119 newSvc.Spec.Type = core.ServiceTypeNodePort 19120 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19121 19122 oldSvc.Spec.ClusterIP = "1.2.3.4" 19123 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19124 19125 newSvc.Spec.ClusterIP = "1.2.3.5" 19126 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19127 }, 19128 numErrs: 1, 19129 }, { 19130 name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort", 19131 tweakSvc: func(oldSvc, newSvc *core.Service) { 19132 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19133 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19134 newSvc.Spec.Type = core.ServiceTypeNodePort 19135 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19136 19137 oldSvc.Spec.ClusterIP = "" 19138 oldSvc.Spec.ClusterIPs = nil 19139 19140 newSvc.Spec.ClusterIP = "1.2.3.5" 19141 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19142 }, 19143 numErrs: 0, 19144 }, { 19145 name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP", 19146 tweakSvc: func(oldSvc, newSvc *core.Service) { 19147 oldSvc.Spec.Type = core.ServiceTypeExternalName 19148 newSvc.Spec.Type = core.ServiceTypeClusterIP 19149 19150 oldSvc.Spec.ClusterIP = "" 19151 oldSvc.Spec.ClusterIPs = nil 19152 19153 newSvc.Spec.ClusterIP = "1.2.3.5" 19154 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19155 }, 19156 numErrs: 0, 19157 }, { 19158 name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP", 19159 tweakSvc: func(oldSvc, newSvc *core.Service) { 19160 oldSvc.Spec.Type = core.ServiceTypeExternalName 19161 newSvc.Spec.Type = core.ServiceTypeClusterIP 19162 19163 oldSvc.Spec.ClusterIP = "1.2.3.4" 19164 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19165 19166 newSvc.Spec.ClusterIP = "1.2.3.5" 19167 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19168 }, 19169 numErrs: 0, 19170 }, { 19171 name: "invalid node port with clusterIP None", 19172 tweakSvc: func(oldSvc, newSvc *core.Service) { 19173 oldSvc.Spec.Type = core.ServiceTypeNodePort 19174 newSvc.Spec.Type = core.ServiceTypeNodePort 19175 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19176 19177 oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) 19178 newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)}) 19179 19180 oldSvc.Spec.ClusterIP = "" 19181 oldSvc.Spec.ClusterIPs = nil 19182 19183 newSvc.Spec.ClusterIP = "None" 19184 newSvc.Spec.ClusterIPs = []string{"None"} 19185 }, 19186 numErrs: 1, 19187 }, 19188 /* Service IP Family */ 19189 { 19190 name: "convert from ExternalName", 19191 tweakSvc: func(oldSvc, newSvc *core.Service) { 19192 oldSvc.Spec.Type = core.ServiceTypeExternalName 19193 newSvc.Spec.Type = core.ServiceTypeClusterIP 19194 }, 19195 numErrs: 0, 19196 }, { 19197 name: "invalid: convert to ExternalName", 19198 tweakSvc: func(oldSvc, newSvc *core.Service) { 19199 singleStack := core.IPFamilyPolicySingleStack 19200 19201 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19202 oldSvc.Spec.ClusterIP = "10.0.0.10" 19203 oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"} 19204 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19205 oldSvc.Spec.IPFamilyPolicy = &singleStack 19206 19207 newSvc.Spec.Type = core.ServiceTypeExternalName 19208 newSvc.Spec.ExternalName = "foo" 19209 /* 19210 not removing these fields is a validation error 19211 strategy takes care of resetting Families & Policy if ClusterIPs 19212 were reset. But it does not get called in validation testing. 19213 */ 19214 newSvc.Spec.ClusterIP = "10.0.0.10" 19215 newSvc.Spec.ClusterIPs = []string{"10.0.0.10"} 19216 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19217 newSvc.Spec.IPFamilyPolicy = &singleStack 19218 19219 }, 19220 numErrs: 3, 19221 }, { 19222 name: "valid: convert to ExternalName", 19223 tweakSvc: func(oldSvc, newSvc *core.Service) { 19224 singleStack := core.IPFamilyPolicySingleStack 19225 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19226 oldSvc.Spec.ClusterIP = "10.0.0.10" 19227 oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"} 19228 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19229 oldSvc.Spec.IPFamilyPolicy = &singleStack 19230 19231 newSvc.Spec.Type = core.ServiceTypeExternalName 19232 newSvc.Spec.ExternalName = "foo" 19233 }, 19234 numErrs: 0, 19235 }, 19236 19237 { 19238 name: "same ServiceIPFamily", 19239 tweakSvc: func(oldSvc, newSvc *core.Service) { 19240 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19241 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19242 19243 newSvc.Spec.Type = core.ServiceTypeClusterIP 19244 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19245 }, 19246 numErrs: 0, 19247 }, { 19248 name: "same ServiceIPFamily, change IPFamilyPolicy to singleStack", 19249 tweakSvc: func(oldSvc, newSvc *core.Service) { 19250 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19251 oldSvc.Spec.IPFamilyPolicy = nil 19252 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19253 19254 newSvc.Spec.IPFamilyPolicy = &singleStack 19255 newSvc.Spec.Type = core.ServiceTypeClusterIP 19256 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19257 }, 19258 numErrs: 0, 19259 }, { 19260 name: "same ServiceIPFamily, change IPFamilyPolicy singleStack => requireDualStack", 19261 tweakSvc: func(oldSvc, newSvc *core.Service) { 19262 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19263 oldSvc.Spec.IPFamilyPolicy = &singleStack 19264 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19265 19266 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19267 newSvc.Spec.Type = core.ServiceTypeClusterIP 19268 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19269 }, 19270 numErrs: 0, 19271 }, 19272 19273 { 19274 name: "add a new ServiceIPFamily", 19275 tweakSvc: func(oldSvc, newSvc *core.Service) { 19276 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19277 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19278 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19279 19280 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19281 newSvc.Spec.Type = core.ServiceTypeClusterIP 19282 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19283 }, 19284 numErrs: 0, 19285 }, 19286 19287 { 19288 name: "ExternalName while changing Service IPFamily", 19289 tweakSvc: func(oldSvc, newSvc *core.Service) { 19290 oldSvc.Spec.ExternalName = "somename" 19291 oldSvc.Spec.Type = core.ServiceTypeExternalName 19292 19293 newSvc.Spec.ExternalName = "somename" 19294 newSvc.Spec.Type = core.ServiceTypeExternalName 19295 }, 19296 numErrs: 0, 19297 }, { 19298 name: "setting ipfamily from nil to v4", 19299 tweakSvc: func(oldSvc, newSvc *core.Service) { 19300 oldSvc.Spec.IPFamilies = nil 19301 19302 newSvc.Spec.ExternalName = "somename" 19303 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19304 }, 19305 numErrs: 0, 19306 }, { 19307 name: "setting ipfamily from nil to v6", 19308 tweakSvc: func(oldSvc, newSvc *core.Service) { 19309 oldSvc.Spec.IPFamilies = nil 19310 19311 newSvc.Spec.ExternalName = "somename" 19312 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 19313 }, 19314 numErrs: 0, 19315 }, { 19316 name: "change primary ServiceIPFamily", 19317 tweakSvc: func(oldSvc, newSvc *core.Service) { 19318 oldSvc.Spec.ClusterIP = "1.2.3.4" 19319 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19320 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19321 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19322 19323 newSvc.Spec.Type = core.ServiceTypeClusterIP 19324 newSvc.Spec.ClusterIP = "1.2.3.4" 19325 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19326 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 19327 }, 19328 numErrs: 2, 19329 }, 19330 /* upgrade + downgrade from/to dualstack tests */ 19331 { 19332 name: "valid: upgrade to dual stack with requiredDualStack", 19333 tweakSvc: func(oldSvc, newSvc *core.Service) { 19334 oldSvc.Spec.ClusterIP = "1.2.3.4" 19335 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19336 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19337 oldSvc.Spec.IPFamilyPolicy = &singleStack 19338 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19339 19340 newSvc.Spec.Type = core.ServiceTypeClusterIP 19341 newSvc.Spec.ClusterIP = "1.2.3.4" 19342 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19343 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19344 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19345 }, 19346 numErrs: 0, 19347 }, { 19348 name: "valid: upgrade to dual stack with preferDualStack", 19349 tweakSvc: func(oldSvc, newSvc *core.Service) { 19350 oldSvc.Spec.ClusterIP = "1.2.3.4" 19351 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19352 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19353 oldSvc.Spec.IPFamilyPolicy = &singleStack 19354 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19355 19356 newSvc.Spec.Type = core.ServiceTypeClusterIP 19357 newSvc.Spec.ClusterIP = "1.2.3.4" 19358 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19359 newSvc.Spec.IPFamilyPolicy = &preferDualStack 19360 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19361 }, 19362 numErrs: 0, 19363 }, 19364 19365 { 19366 name: "valid: upgrade to dual stack, no specific secondary ip", 19367 tweakSvc: func(oldSvc, newSvc *core.Service) { 19368 oldSvc.Spec.ClusterIP = "1.2.3.4" 19369 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19370 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19371 oldSvc.Spec.IPFamilyPolicy = &singleStack 19372 19373 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19374 19375 newSvc.Spec.Type = core.ServiceTypeClusterIP 19376 newSvc.Spec.ClusterIP = "1.2.3.4" 19377 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19378 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19379 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19380 }, 19381 numErrs: 0, 19382 }, { 19383 name: "valid: upgrade to dual stack, with specific secondary ip", 19384 tweakSvc: func(oldSvc, newSvc *core.Service) { 19385 oldSvc.Spec.ClusterIP = "1.2.3.4" 19386 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19387 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19388 oldSvc.Spec.IPFamilyPolicy = &singleStack 19389 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19390 19391 newSvc.Spec.Type = core.ServiceTypeClusterIP 19392 newSvc.Spec.ClusterIP = "1.2.3.4" 19393 newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19394 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19395 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19396 }, 19397 numErrs: 0, 19398 }, { 19399 name: "valid: downgrade from dual to single", 19400 tweakSvc: func(oldSvc, newSvc *core.Service) { 19401 oldSvc.Spec.ClusterIP = "1.2.3.4" 19402 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19403 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19404 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19405 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19406 19407 newSvc.Spec.Type = core.ServiceTypeClusterIP 19408 newSvc.Spec.ClusterIP = "1.2.3.4" 19409 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19410 newSvc.Spec.IPFamilyPolicy = &singleStack 19411 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19412 }, 19413 numErrs: 0, 19414 }, { 19415 name: "valid: change families for a headless service", 19416 tweakSvc: func(oldSvc, newSvc *core.Service) { 19417 oldSvc.Spec.ClusterIP = "None" 19418 oldSvc.Spec.ClusterIPs = []string{"None"} 19419 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19420 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19421 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19422 19423 newSvc.Spec.Type = core.ServiceTypeClusterIP 19424 newSvc.Spec.ClusterIP = "None" 19425 newSvc.Spec.ClusterIPs = []string{"None"} 19426 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19427 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} 19428 }, 19429 numErrs: 0, 19430 }, { 19431 name: "valid: upgrade a headless service", 19432 tweakSvc: func(oldSvc, newSvc *core.Service) { 19433 oldSvc.Spec.ClusterIP = "None" 19434 oldSvc.Spec.ClusterIPs = []string{"None"} 19435 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19436 oldSvc.Spec.IPFamilyPolicy = &singleStack 19437 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19438 19439 newSvc.Spec.Type = core.ServiceTypeClusterIP 19440 newSvc.Spec.ClusterIP = "None" 19441 newSvc.Spec.ClusterIPs = []string{"None"} 19442 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19443 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} 19444 }, 19445 numErrs: 0, 19446 }, { 19447 name: "valid: downgrade a headless service", 19448 tweakSvc: func(oldSvc, newSvc *core.Service) { 19449 oldSvc.Spec.ClusterIP = "None" 19450 oldSvc.Spec.ClusterIPs = []string{"None"} 19451 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19452 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19453 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19454 19455 newSvc.Spec.Type = core.ServiceTypeClusterIP 19456 newSvc.Spec.ClusterIP = "None" 19457 newSvc.Spec.ClusterIPs = []string{"None"} 19458 newSvc.Spec.IPFamilyPolicy = &singleStack 19459 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol} 19460 }, 19461 numErrs: 0, 19462 }, 19463 19464 { 19465 name: "invalid flip families", 19466 tweakSvc: func(oldSvc, newSvc *core.Service) { 19467 oldSvc.Spec.ClusterIP = "1.2.3.40" 19468 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19469 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19470 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19471 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19472 19473 newSvc.Spec.Type = core.ServiceTypeClusterIP 19474 newSvc.Spec.ClusterIP = "2001::1" 19475 newSvc.Spec.ClusterIPs = []string{"2001::1", "1.2.3.5"} 19476 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19477 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol} 19478 }, 19479 numErrs: 4, 19480 }, { 19481 name: "invalid change first ip, in dualstack service", 19482 tweakSvc: func(oldSvc, newSvc *core.Service) { 19483 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19484 oldSvc.Spec.ClusterIP = "1.2.3.4" 19485 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19486 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19487 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19488 19489 newSvc.Spec.Type = core.ServiceTypeClusterIP 19490 newSvc.Spec.ClusterIP = "1.2.3.5" 19491 newSvc.Spec.ClusterIPs = []string{"1.2.3.5", "2001::1"} 19492 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19493 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19494 }, 19495 numErrs: 1, 19496 }, { 19497 name: "invalid, change second ip in dualstack service", 19498 tweakSvc: func(oldSvc, newSvc *core.Service) { 19499 oldSvc.Spec.ClusterIP = "1.2.3.4" 19500 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19501 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19502 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19503 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19504 19505 newSvc.Spec.Type = core.ServiceTypeClusterIP 19506 newSvc.Spec.ClusterIP = "1.2.3.4" 19507 newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2002::1"} 19508 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19509 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19510 }, 19511 numErrs: 1, 19512 }, { 19513 name: "downgrade keeping the families", 19514 tweakSvc: func(oldSvc, newSvc *core.Service) { 19515 oldSvc.Spec.ClusterIP = "1.2.3.4" 19516 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19517 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19518 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19519 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19520 19521 newSvc.Spec.Type = core.ServiceTypeClusterIP 19522 newSvc.Spec.ClusterIP = "1.2.3.4" 19523 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19524 newSvc.Spec.IPFamilyPolicy = &singleStack 19525 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19526 }, 19527 numErrs: 0, // families and ips are trimmed in strategy 19528 }, { 19529 name: "invalid, downgrade without changing to singleStack", 19530 tweakSvc: func(oldSvc, newSvc *core.Service) { 19531 oldSvc.Spec.ClusterIP = "1.2.3.4" 19532 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19533 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19534 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19535 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19536 19537 newSvc.Spec.Type = core.ServiceTypeClusterIP 19538 newSvc.Spec.ClusterIP = "1.2.3.4" 19539 newSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19540 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19541 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19542 }, 19543 numErrs: 2, 19544 }, { 19545 name: "invalid, downgrade and change primary ip", 19546 tweakSvc: func(oldSvc, newSvc *core.Service) { 19547 oldSvc.Spec.ClusterIP = "1.2.3.4" 19548 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"} 19549 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19550 oldSvc.Spec.IPFamilyPolicy = &requireDualStack 19551 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19552 19553 newSvc.Spec.Type = core.ServiceTypeClusterIP 19554 newSvc.Spec.ClusterIP = "1.2.3.5" 19555 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19556 newSvc.Spec.IPFamilyPolicy = &singleStack 19557 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19558 }, 19559 numErrs: 1, 19560 }, { 19561 name: "invalid: upgrade to dual stack and change primary", 19562 tweakSvc: func(oldSvc, newSvc *core.Service) { 19563 oldSvc.Spec.ClusterIP = "1.2.3.4" 19564 oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"} 19565 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19566 oldSvc.Spec.IPFamilyPolicy = &singleStack 19567 19568 oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol} 19569 19570 newSvc.Spec.Type = core.ServiceTypeClusterIP 19571 newSvc.Spec.ClusterIP = "1.2.3.5" 19572 newSvc.Spec.ClusterIPs = []string{"1.2.3.5"} 19573 newSvc.Spec.IPFamilyPolicy = &requireDualStack 19574 newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol} 19575 }, 19576 numErrs: 1, 19577 }, { 19578 name: "update to valid app protocol", 19579 tweakSvc: func(oldSvc, newSvc *core.Service) { 19580 oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}} 19581 newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("https")}} 19582 }, 19583 numErrs: 0, 19584 }, { 19585 name: "update to invalid app protocol", 19586 tweakSvc: func(oldSvc, newSvc *core.Service) { 19587 oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}} 19588 newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("~https")}} 19589 }, 19590 numErrs: 1, 19591 }, { 19592 name: "Set AllocateLoadBalancerNodePorts when type is not LoadBalancer", 19593 tweakSvc: func(oldSvc, newSvc *core.Service) { 19594 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19595 }, 19596 numErrs: 1, 19597 }, { 19598 name: "update LoadBalancer type of service without change LoadBalancerClass", 19599 tweakSvc: func(oldSvc, newSvc *core.Service) { 19600 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19601 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19602 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old") 19603 19604 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19605 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19606 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19607 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old") 19608 }, 19609 numErrs: 0, 19610 }, { 19611 name: "invalid: change LoadBalancerClass when update service", 19612 tweakSvc: func(oldSvc, newSvc *core.Service) { 19613 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19614 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19615 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old") 19616 19617 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19618 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19619 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19620 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new") 19621 }, 19622 numErrs: 1, 19623 }, { 19624 name: "invalid: unset LoadBalancerClass when update service", 19625 tweakSvc: func(oldSvc, newSvc *core.Service) { 19626 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19627 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19628 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old") 19629 19630 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19631 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19632 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19633 newSvc.Spec.LoadBalancerClass = nil 19634 }, 19635 numErrs: 1, 19636 }, { 19637 name: "invalid: set LoadBalancerClass when update service", 19638 tweakSvc: func(oldSvc, newSvc *core.Service) { 19639 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19640 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19641 oldSvc.Spec.LoadBalancerClass = nil 19642 19643 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19644 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19645 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19646 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new") 19647 }, 19648 numErrs: 1, 19649 }, { 19650 name: "update to LoadBalancer type of service with valid LoadBalancerClass", 19651 tweakSvc: func(oldSvc, newSvc *core.Service) { 19652 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19653 19654 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19655 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19656 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19657 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19658 }, 19659 numErrs: 0, 19660 }, { 19661 name: "update to LoadBalancer type of service without LoadBalancerClass", 19662 tweakSvc: func(oldSvc, newSvc *core.Service) { 19663 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19664 19665 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19666 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19667 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19668 newSvc.Spec.LoadBalancerClass = nil 19669 }, 19670 numErrs: 0, 19671 }, { 19672 name: "invalid: set invalid LoadBalancerClass when update service to LoadBalancer", 19673 tweakSvc: func(oldSvc, newSvc *core.Service) { 19674 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19675 19676 newSvc.Spec.Type = core.ServiceTypeLoadBalancer 19677 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19678 newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19679 newSvc.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerclass") 19680 }, 19681 numErrs: 2, 19682 }, { 19683 name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service", 19684 tweakSvc: func(oldSvc, newSvc *core.Service) { 19685 oldSvc.Spec.Type = core.ServiceTypeClusterIP 19686 19687 newSvc.Spec.Type = core.ServiceTypeClusterIP 19688 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19689 }, 19690 numErrs: 2, 19691 }, { 19692 name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service", 19693 tweakSvc: func(oldSvc, newSvc *core.Service) { 19694 oldSvc.Spec.Type = core.ServiceTypeExternalName 19695 19696 newSvc.Spec.Type = core.ServiceTypeExternalName 19697 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19698 }, 19699 numErrs: 3, 19700 }, { 19701 name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service", 19702 tweakSvc: func(oldSvc, newSvc *core.Service) { 19703 oldSvc.Spec.Type = core.ServiceTypeNodePort 19704 19705 newSvc.Spec.Type = core.ServiceTypeNodePort 19706 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19707 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19708 }, 19709 numErrs: 2, 19710 }, { 19711 name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service", 19712 tweakSvc: func(oldSvc, newSvc *core.Service) { 19713 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19714 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19715 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19716 19717 newSvc.Spec.Type = core.ServiceTypeClusterIP 19718 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19719 }, 19720 numErrs: 2, 19721 }, { 19722 name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service", 19723 tweakSvc: func(oldSvc, newSvc *core.Service) { 19724 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19725 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19726 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19727 19728 newSvc.Spec.Type = core.ServiceTypeExternalName 19729 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19730 }, 19731 numErrs: 3, 19732 }, { 19733 name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service", 19734 tweakSvc: func(oldSvc, newSvc *core.Service) { 19735 oldSvc.Spec.Type = core.ServiceTypeLoadBalancer 19736 oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) 19737 oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19738 19739 newSvc.Spec.Type = core.ServiceTypeNodePort 19740 newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster 19741 newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class") 19742 }, 19743 numErrs: 2, 19744 }, { 19745 name: "update internalTrafficPolicy from Cluster to Local", 19746 tweakSvc: func(oldSvc, newSvc *core.Service) { 19747 cluster := core.ServiceInternalTrafficPolicyCluster 19748 oldSvc.Spec.InternalTrafficPolicy = &cluster 19749 19750 local := core.ServiceInternalTrafficPolicyLocal 19751 newSvc.Spec.InternalTrafficPolicy = &local 19752 }, 19753 numErrs: 0, 19754 }, { 19755 name: "update internalTrafficPolicy from Local to Cluster", 19756 tweakSvc: func(oldSvc, newSvc *core.Service) { 19757 local := core.ServiceInternalTrafficPolicyLocal 19758 oldSvc.Spec.InternalTrafficPolicy = &local 19759 19760 cluster := core.ServiceInternalTrafficPolicyCluster 19761 newSvc.Spec.InternalTrafficPolicy = &cluster 19762 }, 19763 numErrs: 0, 19764 }, { 19765 name: "topology annotations are mismatched", 19766 tweakSvc: func(oldSvc, newSvc *core.Service) { 19767 newSvc.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original" 19768 newSvc.Annotations[core.AnnotationTopologyMode] = "different" 19769 }, 19770 numErrs: 1, 19771 }, 19772 } 19773 19774 for _, tc := range testCases { 19775 t.Run(tc.name, func(t *testing.T) { 19776 oldSvc := makeValidService() 19777 newSvc := makeValidService() 19778 tc.tweakSvc(&oldSvc, &newSvc) 19779 errs := ValidateServiceUpdate(&newSvc, &oldSvc) 19780 if len(errs) != tc.numErrs { 19781 t.Errorf("Expected %d errors, got %d: %v", tc.numErrs, len(errs), errs.ToAggregate()) 19782 } 19783 }) 19784 } 19785 } 19786 19787 func TestValidateResourceNames(t *testing.T) { 19788 table := []struct { 19789 input core.ResourceName 19790 success bool 19791 expect string 19792 }{ 19793 {"memory", true, ""}, 19794 {"cpu", true, ""}, 19795 {"storage", true, ""}, 19796 {"requests.cpu", true, ""}, 19797 {"requests.memory", true, ""}, 19798 {"requests.storage", true, ""}, 19799 {"limits.cpu", true, ""}, 19800 {"limits.memory", true, ""}, 19801 {"network", false, ""}, 19802 {"disk", false, ""}, 19803 {"", false, ""}, 19804 {".", false, ""}, 19805 {"..", false, ""}, 19806 {"my.favorite.app.co/12345", true, ""}, 19807 {"my.favorite.app.co/_12345", false, ""}, 19808 {"my.favorite.app.co/12345_", false, ""}, 19809 {"kubernetes.io/..", false, ""}, 19810 {core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)), true, ""}, 19811 {core.ResourceName("kubernetes.io/" + strings.Repeat("a", 64)), false, ""}, 19812 {"kubernetes.io//", false, ""}, 19813 {"kubernetes.io", false, ""}, 19814 {"kubernetes.io/will/not/work/", false, ""}, 19815 } 19816 for k, item := range table { 19817 err := validateResourceName(item.input, field.NewPath("field")) 19818 if len(err) != 0 && item.success { 19819 t.Errorf("expected no failure for input %q", item.input) 19820 } else if len(err) == 0 && !item.success { 19821 t.Errorf("expected failure for input %q", item.input) 19822 for i := range err { 19823 detail := err[i].Detail 19824 if detail != "" && !strings.Contains(detail, item.expect) { 19825 t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail) 19826 } 19827 } 19828 } 19829 } 19830 } 19831 19832 func getResourceList(cpu, memory string) core.ResourceList { 19833 res := core.ResourceList{} 19834 if cpu != "" { 19835 res[core.ResourceCPU] = resource.MustParse(cpu) 19836 } 19837 if memory != "" { 19838 res[core.ResourceMemory] = resource.MustParse(memory) 19839 } 19840 return res 19841 } 19842 19843 func getStorageResourceList(storage string) core.ResourceList { 19844 res := core.ResourceList{} 19845 if storage != "" { 19846 res[core.ResourceStorage] = resource.MustParse(storage) 19847 } 19848 return res 19849 } 19850 19851 func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList { 19852 res := core.ResourceList{} 19853 if ephemeralStorage != "" { 19854 res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage) 19855 } 19856 return res 19857 } 19858 19859 func TestValidateLimitRangeForLocalStorage(t *testing.T) { 19860 testCases := []struct { 19861 name string 19862 spec core.LimitRangeSpec 19863 }{{ 19864 name: "all-fields-valid", 19865 spec: core.LimitRangeSpec{ 19866 Limits: []core.LimitRangeItem{{ 19867 Type: core.LimitTypePod, 19868 Max: getLocalStorageResourceList("10000Mi"), 19869 Min: getLocalStorageResourceList("100Mi"), 19870 MaxLimitRequestRatio: getLocalStorageResourceList(""), 19871 }, { 19872 Type: core.LimitTypeContainer, 19873 Max: getLocalStorageResourceList("10000Mi"), 19874 Min: getLocalStorageResourceList("100Mi"), 19875 Default: getLocalStorageResourceList("500Mi"), 19876 DefaultRequest: getLocalStorageResourceList("200Mi"), 19877 MaxLimitRequestRatio: getLocalStorageResourceList(""), 19878 }}, 19879 }, 19880 }, 19881 } 19882 19883 for _, testCase := range testCases { 19884 limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec} 19885 if errs := ValidateLimitRange(limitRange); len(errs) != 0 { 19886 t.Errorf("Case %v, unexpected error: %v", testCase.name, errs) 19887 } 19888 } 19889 } 19890 19891 func TestValidateLimitRange(t *testing.T) { 19892 successCases := []struct { 19893 name string 19894 spec core.LimitRangeSpec 19895 }{{ 19896 name: "all-fields-valid", 19897 spec: core.LimitRangeSpec{ 19898 Limits: []core.LimitRangeItem{{ 19899 Type: core.LimitTypePod, 19900 Max: getResourceList("100m", "10000Mi"), 19901 Min: getResourceList("5m", "100Mi"), 19902 MaxLimitRequestRatio: getResourceList("10", ""), 19903 }, { 19904 Type: core.LimitTypeContainer, 19905 Max: getResourceList("100m", "10000Mi"), 19906 Min: getResourceList("5m", "100Mi"), 19907 Default: getResourceList("50m", "500Mi"), 19908 DefaultRequest: getResourceList("10m", "200Mi"), 19909 MaxLimitRequestRatio: getResourceList("10", ""), 19910 }, { 19911 Type: core.LimitTypePersistentVolumeClaim, 19912 Max: getStorageResourceList("10Gi"), 19913 Min: getStorageResourceList("5Gi"), 19914 }}, 19915 }, 19916 }, { 19917 name: "pvc-min-only", 19918 spec: core.LimitRangeSpec{ 19919 Limits: []core.LimitRangeItem{{ 19920 Type: core.LimitTypePersistentVolumeClaim, 19921 Min: getStorageResourceList("5Gi"), 19922 }}, 19923 }, 19924 }, { 19925 name: "pvc-max-only", 19926 spec: core.LimitRangeSpec{ 19927 Limits: []core.LimitRangeItem{{ 19928 Type: core.LimitTypePersistentVolumeClaim, 19929 Max: getStorageResourceList("10Gi"), 19930 }}, 19931 }, 19932 }, { 19933 name: "all-fields-valid-big-numbers", 19934 spec: core.LimitRangeSpec{ 19935 Limits: []core.LimitRangeItem{{ 19936 Type: core.LimitTypeContainer, 19937 Max: getResourceList("100m", "10000T"), 19938 Min: getResourceList("5m", "100Mi"), 19939 Default: getResourceList("50m", "500Mi"), 19940 DefaultRequest: getResourceList("10m", "200Mi"), 19941 MaxLimitRequestRatio: getResourceList("10", ""), 19942 }}, 19943 }, 19944 }, { 19945 name: "thirdparty-fields-all-valid-standard-container-resources", 19946 spec: core.LimitRangeSpec{ 19947 Limits: []core.LimitRangeItem{{ 19948 Type: "thirdparty.com/foo", 19949 Max: getResourceList("100m", "10000T"), 19950 Min: getResourceList("5m", "100Mi"), 19951 Default: getResourceList("50m", "500Mi"), 19952 DefaultRequest: getResourceList("10m", "200Mi"), 19953 MaxLimitRequestRatio: getResourceList("10", ""), 19954 }}, 19955 }, 19956 }, { 19957 name: "thirdparty-fields-all-valid-storage-resources", 19958 spec: core.LimitRangeSpec{ 19959 Limits: []core.LimitRangeItem{{ 19960 Type: "thirdparty.com/foo", 19961 Max: getStorageResourceList("10000T"), 19962 Min: getStorageResourceList("100Mi"), 19963 Default: getStorageResourceList("500Mi"), 19964 DefaultRequest: getStorageResourceList("200Mi"), 19965 MaxLimitRequestRatio: getStorageResourceList(""), 19966 }}, 19967 }, 19968 }, 19969 } 19970 19971 for _, successCase := range successCases { 19972 limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec} 19973 if errs := ValidateLimitRange(limitRange); len(errs) != 0 { 19974 t.Errorf("Case %v, unexpected error: %v", successCase.name, errs) 19975 } 19976 } 19977 19978 errorCases := map[string]struct { 19979 R core.LimitRange 19980 D string 19981 }{ 19982 "zero-length-name": { 19983 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}}, 19984 "name or generateName is required", 19985 }, 19986 "zero-length-namespace": { 19987 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}}, 19988 "", 19989 }, 19990 "invalid-name": { 19991 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}}, 19992 dnsSubdomainLabelErrMsg, 19993 }, 19994 "invalid-namespace": { 19995 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}}, 19996 dnsLabelErrMsg, 19997 }, 19998 "duplicate-limit-type": { 19999 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20000 Limits: []core.LimitRangeItem{{ 20001 Type: core.LimitTypePod, 20002 Max: getResourceList("100m", "10000m"), 20003 Min: getResourceList("0m", "100m"), 20004 }, { 20005 Type: core.LimitTypePod, 20006 Min: getResourceList("0m", "100m"), 20007 }}, 20008 }}, 20009 "", 20010 }, 20011 "default-limit-type-pod": { 20012 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20013 Limits: []core.LimitRangeItem{{ 20014 Type: core.LimitTypePod, 20015 Max: getResourceList("100m", "10000m"), 20016 Min: getResourceList("0m", "100m"), 20017 Default: getResourceList("10m", "100m"), 20018 }}, 20019 }}, 20020 "may not be specified when `type` is 'Pod'", 20021 }, 20022 "default-request-limit-type-pod": { 20023 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20024 Limits: []core.LimitRangeItem{{ 20025 Type: core.LimitTypePod, 20026 Max: getResourceList("100m", "10000m"), 20027 Min: getResourceList("0m", "100m"), 20028 DefaultRequest: getResourceList("10m", "100m"), 20029 }}, 20030 }}, 20031 "may not be specified when `type` is 'Pod'", 20032 }, 20033 "min value 100m is greater than max value 10m": { 20034 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20035 Limits: []core.LimitRangeItem{{ 20036 Type: core.LimitTypePod, 20037 Max: getResourceList("10m", ""), 20038 Min: getResourceList("100m", ""), 20039 }}, 20040 }}, 20041 "min value 100m is greater than max value 10m", 20042 }, 20043 "invalid spec default outside range": { 20044 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20045 Limits: []core.LimitRangeItem{{ 20046 Type: core.LimitTypeContainer, 20047 Max: getResourceList("1", ""), 20048 Min: getResourceList("100m", ""), 20049 Default: getResourceList("2000m", ""), 20050 }}, 20051 }}, 20052 "default value 2 is greater than max value 1", 20053 }, 20054 "invalid spec default request outside range": { 20055 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20056 Limits: []core.LimitRangeItem{{ 20057 Type: core.LimitTypeContainer, 20058 Max: getResourceList("1", ""), 20059 Min: getResourceList("100m", ""), 20060 DefaultRequest: getResourceList("2000m", ""), 20061 }}, 20062 }}, 20063 "default request value 2 is greater than max value 1", 20064 }, 20065 "invalid spec default request more than default": { 20066 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20067 Limits: []core.LimitRangeItem{{ 20068 Type: core.LimitTypeContainer, 20069 Max: getResourceList("2", ""), 20070 Min: getResourceList("100m", ""), 20071 Default: getResourceList("500m", ""), 20072 DefaultRequest: getResourceList("800m", ""), 20073 }}, 20074 }}, 20075 "default request value 800m is greater than default limit value 500m", 20076 }, 20077 "invalid spec maxLimitRequestRatio less than 1": { 20078 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20079 Limits: []core.LimitRangeItem{{ 20080 Type: core.LimitTypePod, 20081 MaxLimitRequestRatio: getResourceList("800m", ""), 20082 }}, 20083 }}, 20084 "ratio 800m is less than 1", 20085 }, 20086 "invalid spec maxLimitRequestRatio greater than max/min": { 20087 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20088 Limits: []core.LimitRangeItem{{ 20089 Type: core.LimitTypeContainer, 20090 Max: getResourceList("", "2Gi"), 20091 Min: getResourceList("", "512Mi"), 20092 MaxLimitRequestRatio: getResourceList("", "10"), 20093 }}, 20094 }}, 20095 "ratio 10 is greater than max/min = 4.000000", 20096 }, 20097 "invalid non standard limit type": { 20098 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20099 Limits: []core.LimitRangeItem{{ 20100 Type: "foo", 20101 Max: getStorageResourceList("10000T"), 20102 Min: getStorageResourceList("100Mi"), 20103 Default: getStorageResourceList("500Mi"), 20104 DefaultRequest: getStorageResourceList("200Mi"), 20105 MaxLimitRequestRatio: getStorageResourceList(""), 20106 }}, 20107 }}, 20108 "must be a standard limit type or fully qualified", 20109 }, 20110 "min and max values missing, one required": { 20111 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20112 Limits: []core.LimitRangeItem{{ 20113 Type: core.LimitTypePersistentVolumeClaim, 20114 }}, 20115 }}, 20116 "either minimum or maximum storage value is required, but neither was provided", 20117 }, 20118 "invalid min greater than max": { 20119 core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{ 20120 Limits: []core.LimitRangeItem{{ 20121 Type: core.LimitTypePersistentVolumeClaim, 20122 Min: getStorageResourceList("10Gi"), 20123 Max: getStorageResourceList("1Gi"), 20124 }}, 20125 }}, 20126 "min value 10Gi is greater than max value 1Gi", 20127 }, 20128 } 20129 20130 for k, v := range errorCases { 20131 errs := ValidateLimitRange(&v.R) 20132 if len(errs) == 0 { 20133 t.Errorf("expected failure for %s", k) 20134 } 20135 for i := range errs { 20136 detail := errs[i].Detail 20137 if !strings.Contains(detail, v.D) { 20138 t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail) 20139 } 20140 } 20141 } 20142 20143 } 20144 20145 func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) { 20146 validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ 20147 AccessModes: []core.PersistentVolumeAccessMode{ 20148 core.ReadWriteOnce, 20149 core.ReadOnlyMany, 20150 }, 20151 Resources: core.VolumeResourceRequirements{ 20152 Requests: core.ResourceList{ 20153 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20154 }, 20155 }, 20156 }) 20157 validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20158 AccessModes: []core.PersistentVolumeAccessMode{ 20159 core.ReadWriteOnce, 20160 core.ReadOnlyMany, 20161 }, 20162 Resources: core.VolumeResourceRequirements{ 20163 Requests: core.ResourceList{ 20164 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20165 }, 20166 }, 20167 }, core.PersistentVolumeClaimStatus{ 20168 Phase: core.ClaimPending, 20169 Conditions: []core.PersistentVolumeClaimCondition{ 20170 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue}, 20171 }, 20172 }) 20173 validAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20174 AccessModes: []core.PersistentVolumeAccessMode{ 20175 core.ReadWriteOnce, 20176 core.ReadOnlyMany, 20177 }, 20178 Resources: core.VolumeResourceRequirements{ 20179 Requests: core.ResourceList{ 20180 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20181 }, 20182 }, 20183 }, core.PersistentVolumeClaimStatus{ 20184 Phase: core.ClaimPending, 20185 Conditions: []core.PersistentVolumeClaimCondition{ 20186 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue}, 20187 }, 20188 AllocatedResources: core.ResourceList{ 20189 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20190 }, 20191 }) 20192 20193 invalidAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20194 AccessModes: []core.PersistentVolumeAccessMode{ 20195 core.ReadWriteOnce, 20196 core.ReadOnlyMany, 20197 }, 20198 Resources: core.VolumeResourceRequirements{ 20199 Requests: core.ResourceList{ 20200 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20201 }, 20202 }, 20203 }, core.PersistentVolumeClaimStatus{ 20204 Phase: core.ClaimPending, 20205 Conditions: []core.PersistentVolumeClaimCondition{ 20206 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue}, 20207 }, 20208 AllocatedResources: core.ResourceList{ 20209 core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"), 20210 }, 20211 }) 20212 20213 noStoraegeClaimStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20214 AccessModes: []core.PersistentVolumeAccessMode{ 20215 core.ReadWriteOnce, 20216 }, 20217 Resources: core.VolumeResourceRequirements{ 20218 Requests: core.ResourceList{ 20219 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20220 }, 20221 }, 20222 }, core.PersistentVolumeClaimStatus{ 20223 Phase: core.ClaimPending, 20224 AllocatedResources: core.ResourceList{ 20225 core.ResourceName(core.ResourceCPU): resource.MustParse("10G"), 20226 }, 20227 }) 20228 progressResizeStatus := core.PersistentVolumeClaimControllerResizeInProgress 20229 20230 invalidResizeStatus := core.ClaimResourceStatus("foo") 20231 validResizeKeyCustom := core.ResourceName("example.com/foo") 20232 invalidNativeResizeKey := core.ResourceName("kubernetes.io/foo") 20233 20234 validResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20235 AccessModes: []core.PersistentVolumeAccessMode{ 20236 core.ReadWriteOnce, 20237 }, 20238 }, core.PersistentVolumeClaimStatus{ 20239 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20240 core.ResourceStorage: progressResizeStatus, 20241 }, 20242 }) 20243 20244 validResizeStatusControllerResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20245 AccessModes: []core.PersistentVolumeAccessMode{ 20246 core.ReadWriteOnce, 20247 }, 20248 }, core.PersistentVolumeClaimStatus{ 20249 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20250 core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed, 20251 }, 20252 }) 20253 20254 validNodeResizePending := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20255 AccessModes: []core.PersistentVolumeAccessMode{ 20256 core.ReadWriteOnce, 20257 }, 20258 }, core.PersistentVolumeClaimStatus{ 20259 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20260 core.ResourceStorage: core.PersistentVolumeClaimNodeResizePending, 20261 }, 20262 }) 20263 20264 validNodeResizeInProgress := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20265 AccessModes: []core.PersistentVolumeAccessMode{ 20266 core.ReadWriteOnce, 20267 }, 20268 }, core.PersistentVolumeClaimStatus{ 20269 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20270 core.ResourceStorage: core.PersistentVolumeClaimNodeResizeInProgress, 20271 }, 20272 }) 20273 20274 validNodeResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20275 AccessModes: []core.PersistentVolumeAccessMode{ 20276 core.ReadWriteOnce, 20277 }, 20278 }, core.PersistentVolumeClaimStatus{ 20279 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20280 core.ResourceStorage: core.PersistentVolumeClaimNodeResizeFailed, 20281 }, 20282 }) 20283 20284 invalidResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20285 AccessModes: []core.PersistentVolumeAccessMode{ 20286 core.ReadWriteOnce, 20287 }, 20288 }, core.PersistentVolumeClaimStatus{ 20289 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20290 core.ResourceStorage: invalidResizeStatus, 20291 }, 20292 }) 20293 20294 invalidNativeResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20295 AccessModes: []core.PersistentVolumeAccessMode{ 20296 core.ReadWriteOnce, 20297 }, 20298 }, core.PersistentVolumeClaimStatus{ 20299 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20300 invalidNativeResizeKey: core.PersistentVolumeClaimNodeResizePending, 20301 }, 20302 }) 20303 20304 validExternalResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20305 AccessModes: []core.PersistentVolumeAccessMode{ 20306 core.ReadWriteOnce, 20307 }, 20308 }, core.PersistentVolumeClaimStatus{ 20309 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20310 validResizeKeyCustom: core.PersistentVolumeClaimNodeResizePending, 20311 }, 20312 }) 20313 20314 multipleResourceStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20315 AccessModes: []core.PersistentVolumeAccessMode{ 20316 core.ReadWriteOnce, 20317 }, 20318 }, core.PersistentVolumeClaimStatus{ 20319 AllocatedResources: core.ResourceList{ 20320 core.ResourceStorage: resource.MustParse("5Gi"), 20321 validResizeKeyCustom: resource.MustParse("10Gi"), 20322 }, 20323 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 20324 core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed, 20325 validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress, 20326 }, 20327 }) 20328 20329 invalidNativeResourceAllocatedKey := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20330 AccessModes: []core.PersistentVolumeAccessMode{ 20331 core.ReadWriteOnce, 20332 core.ReadOnlyMany, 20333 }, 20334 Resources: core.VolumeResourceRequirements{ 20335 Requests: core.ResourceList{ 20336 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20337 }, 20338 }, 20339 }, core.PersistentVolumeClaimStatus{ 20340 Phase: core.ClaimPending, 20341 Conditions: []core.PersistentVolumeClaimCondition{ 20342 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue}, 20343 }, 20344 AllocatedResources: core.ResourceList{ 20345 invalidNativeResizeKey: resource.MustParse("14G"), 20346 }, 20347 }) 20348 20349 validExternalAllocatedResource := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{ 20350 AccessModes: []core.PersistentVolumeAccessMode{ 20351 core.ReadWriteOnce, 20352 core.ReadOnlyMany, 20353 }, 20354 Resources: core.VolumeResourceRequirements{ 20355 Requests: core.ResourceList{ 20356 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 20357 }, 20358 }, 20359 }, core.PersistentVolumeClaimStatus{ 20360 Phase: core.ClaimPending, 20361 Conditions: []core.PersistentVolumeClaimCondition{ 20362 {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue}, 20363 }, 20364 AllocatedResources: core.ResourceList{ 20365 validResizeKeyCustom: resource.MustParse("14G"), 20366 }, 20367 }) 20368 20369 scenarios := map[string]struct { 20370 isExpectedFailure bool 20371 oldClaim *core.PersistentVolumeClaim 20372 newClaim *core.PersistentVolumeClaim 20373 enableRecoverFromExpansion bool 20374 }{ 20375 "condition-update-with-enabled-feature-gate": { 20376 isExpectedFailure: false, 20377 oldClaim: validClaim, 20378 newClaim: validConditionUpdate, 20379 }, 20380 "status-update-with-valid-allocatedResources-feature-enabled": { 20381 isExpectedFailure: false, 20382 oldClaim: validClaim, 20383 newClaim: validAllocatedResources, 20384 enableRecoverFromExpansion: true, 20385 }, 20386 "status-update-with-invalid-allocatedResources-native-key-feature-enabled": { 20387 isExpectedFailure: true, 20388 oldClaim: validClaim, 20389 newClaim: invalidNativeResourceAllocatedKey, 20390 enableRecoverFromExpansion: true, 20391 }, 20392 "status-update-with-valid-allocatedResources-external-key-feature-enabled": { 20393 isExpectedFailure: false, 20394 oldClaim: validClaim, 20395 newClaim: validExternalAllocatedResource, 20396 enableRecoverFromExpansion: true, 20397 }, 20398 20399 "status-update-with-invalid-allocatedResources-feature-enabled": { 20400 isExpectedFailure: true, 20401 oldClaim: validClaim, 20402 newClaim: invalidAllocatedResources, 20403 enableRecoverFromExpansion: true, 20404 }, 20405 "status-update-with-no-storage-update": { 20406 isExpectedFailure: true, 20407 oldClaim: validClaim, 20408 newClaim: noStoraegeClaimStatus, 20409 enableRecoverFromExpansion: true, 20410 }, 20411 "staus-update-with-controller-resize-failed": { 20412 isExpectedFailure: false, 20413 oldClaim: validClaim, 20414 newClaim: validResizeStatusControllerResizeFailed, 20415 enableRecoverFromExpansion: true, 20416 }, 20417 "staus-update-with-node-resize-pending": { 20418 isExpectedFailure: false, 20419 oldClaim: validClaim, 20420 newClaim: validNodeResizePending, 20421 enableRecoverFromExpansion: true, 20422 }, 20423 "staus-update-with-node-resize-inprogress": { 20424 isExpectedFailure: false, 20425 oldClaim: validClaim, 20426 newClaim: validNodeResizeInProgress, 20427 enableRecoverFromExpansion: true, 20428 }, 20429 "staus-update-with-node-resize-failed": { 20430 isExpectedFailure: false, 20431 oldClaim: validClaim, 20432 newClaim: validNodeResizeFailed, 20433 enableRecoverFromExpansion: true, 20434 }, 20435 "staus-update-with-invalid-native-resource-status-key": { 20436 isExpectedFailure: true, 20437 oldClaim: validClaim, 20438 newClaim: invalidNativeResizeStatusPVC, 20439 enableRecoverFromExpansion: true, 20440 }, 20441 "staus-update-with-valid-external-resource-status-key": { 20442 isExpectedFailure: false, 20443 oldClaim: validClaim, 20444 newClaim: validExternalResizeStatusPVC, 20445 enableRecoverFromExpansion: true, 20446 }, 20447 "status-update-with-multiple-resources-key": { 20448 isExpectedFailure: false, 20449 oldClaim: validClaim, 20450 newClaim: multipleResourceStatusPVC, 20451 enableRecoverFromExpansion: true, 20452 }, 20453 "status-update-with-valid-pvc-resize-status": { 20454 isExpectedFailure: false, 20455 oldClaim: validClaim, 20456 newClaim: validResizeStatusPVC, 20457 enableRecoverFromExpansion: true, 20458 }, 20459 "status-update-with-invalid-pvc-resize-status": { 20460 isExpectedFailure: true, 20461 oldClaim: validClaim, 20462 newClaim: invalidResizeStatusPVC, 20463 enableRecoverFromExpansion: true, 20464 }, 20465 "status-update-with-old-pvc-valid-resourcestatus-newpvc-invalid-recovery-disabled": { 20466 isExpectedFailure: true, 20467 oldClaim: validResizeStatusPVC, 20468 newClaim: invalidResizeStatusPVC, 20469 enableRecoverFromExpansion: false, 20470 }, 20471 "status-update-with-old-pvc-valid-allocatedResource-newpvc-invalid-recovery-disabled": { 20472 isExpectedFailure: true, 20473 oldClaim: validExternalAllocatedResource, 20474 newClaim: invalidNativeResourceAllocatedKey, 20475 enableRecoverFromExpansion: false, 20476 }, 20477 } 20478 for name, scenario := range scenarios { 20479 t.Run(name, func(t *testing.T) { 20480 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)() 20481 20482 validateOpts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim) 20483 20484 // ensure we have a resource version specified for updates 20485 scenario.oldClaim.ResourceVersion = "1" 20486 scenario.newClaim.ResourceVersion = "1" 20487 errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim, validateOpts) 20488 if len(errs) == 0 && scenario.isExpectedFailure { 20489 t.Errorf("Unexpected success for scenario: %s", name) 20490 } 20491 if len(errs) > 0 && !scenario.isExpectedFailure { 20492 t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) 20493 } 20494 }) 20495 } 20496 } 20497 20498 func TestValidateResourceQuota(t *testing.T) { 20499 spec := core.ResourceQuotaSpec{ 20500 Hard: core.ResourceList{ 20501 core.ResourceCPU: resource.MustParse("100"), 20502 core.ResourceMemory: resource.MustParse("10000"), 20503 core.ResourceRequestsCPU: resource.MustParse("100"), 20504 core.ResourceRequestsMemory: resource.MustParse("10000"), 20505 core.ResourceLimitsCPU: resource.MustParse("100"), 20506 core.ResourceLimitsMemory: resource.MustParse("10000"), 20507 core.ResourcePods: resource.MustParse("10"), 20508 core.ResourceServices: resource.MustParse("0"), 20509 core.ResourceReplicationControllers: resource.MustParse("10"), 20510 core.ResourceQuotas: resource.MustParse("10"), 20511 core.ResourceConfigMaps: resource.MustParse("10"), 20512 core.ResourceSecrets: resource.MustParse("10"), 20513 }, 20514 } 20515 20516 terminatingSpec := core.ResourceQuotaSpec{ 20517 Hard: core.ResourceList{ 20518 core.ResourceCPU: resource.MustParse("100"), 20519 core.ResourceLimitsCPU: resource.MustParse("200"), 20520 }, 20521 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating}, 20522 } 20523 20524 nonTerminatingSpec := core.ResourceQuotaSpec{ 20525 Hard: core.ResourceList{ 20526 core.ResourceCPU: resource.MustParse("100"), 20527 }, 20528 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating}, 20529 } 20530 20531 bestEffortSpec := core.ResourceQuotaSpec{ 20532 Hard: core.ResourceList{ 20533 core.ResourcePods: resource.MustParse("100"), 20534 }, 20535 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort}, 20536 } 20537 20538 nonBestEffortSpec := core.ResourceQuotaSpec{ 20539 Hard: core.ResourceList{ 20540 core.ResourceCPU: resource.MustParse("100"), 20541 }, 20542 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort}, 20543 } 20544 20545 crossNamespaceAffinitySpec := core.ResourceQuotaSpec{ 20546 Hard: core.ResourceList{ 20547 core.ResourceCPU: resource.MustParse("100"), 20548 core.ResourceLimitsCPU: resource.MustParse("200"), 20549 }, 20550 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeCrossNamespacePodAffinity}, 20551 } 20552 20553 scopeSelectorSpec := core.ResourceQuotaSpec{ 20554 ScopeSelector: &core.ScopeSelector{ 20555 MatchExpressions: []core.ScopedResourceSelectorRequirement{{ 20556 ScopeName: core.ResourceQuotaScopePriorityClass, 20557 Operator: core.ScopeSelectorOpIn, 20558 Values: []string{"cluster-services"}, 20559 }}, 20560 }, 20561 } 20562 20563 // storage is not yet supported as a quota tracked resource 20564 invalidQuotaResourceSpec := core.ResourceQuotaSpec{ 20565 Hard: core.ResourceList{ 20566 core.ResourceStorage: resource.MustParse("10"), 20567 }, 20568 } 20569 20570 negativeSpec := core.ResourceQuotaSpec{ 20571 Hard: core.ResourceList{ 20572 core.ResourceCPU: resource.MustParse("-100"), 20573 core.ResourceMemory: resource.MustParse("-10000"), 20574 core.ResourcePods: resource.MustParse("-10"), 20575 core.ResourceServices: resource.MustParse("-10"), 20576 core.ResourceReplicationControllers: resource.MustParse("-10"), 20577 core.ResourceQuotas: resource.MustParse("-10"), 20578 core.ResourceConfigMaps: resource.MustParse("-10"), 20579 core.ResourceSecrets: resource.MustParse("-10"), 20580 }, 20581 } 20582 20583 fractionalComputeSpec := core.ResourceQuotaSpec{ 20584 Hard: core.ResourceList{ 20585 core.ResourceCPU: resource.MustParse("100m"), 20586 }, 20587 } 20588 20589 fractionalPodSpec := core.ResourceQuotaSpec{ 20590 Hard: core.ResourceList{ 20591 core.ResourcePods: resource.MustParse(".1"), 20592 core.ResourceServices: resource.MustParse(".5"), 20593 core.ResourceReplicationControllers: resource.MustParse("1.25"), 20594 core.ResourceQuotas: resource.MustParse("2.5"), 20595 }, 20596 } 20597 20598 invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{ 20599 Hard: core.ResourceList{ 20600 core.ResourceCPU: resource.MustParse("100"), 20601 }, 20602 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating}, 20603 } 20604 20605 invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{ 20606 Hard: core.ResourceList{ 20607 core.ResourcePods: resource.MustParse("100"), 20608 }, 20609 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort}, 20610 } 20611 20612 invalidCrossNamespaceAffinitySpec := core.ResourceQuotaSpec{ 20613 ScopeSelector: &core.ScopeSelector{ 20614 MatchExpressions: []core.ScopedResourceSelectorRequirement{{ 20615 ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity, 20616 Operator: core.ScopeSelectorOpIn, 20617 Values: []string{"cluster-services"}, 20618 }}, 20619 }, 20620 } 20621 20622 invalidScopeNameSpec := core.ResourceQuotaSpec{ 20623 Hard: core.ResourceList{ 20624 core.ResourceCPU: resource.MustParse("100"), 20625 }, 20626 Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")}, 20627 } 20628 20629 testCases := map[string]struct { 20630 rq core.ResourceQuota 20631 errDetail string 20632 errField string 20633 }{ 20634 "no-scope": { 20635 rq: core.ResourceQuota{ 20636 ObjectMeta: metav1.ObjectMeta{ 20637 Name: "abc", 20638 Namespace: "foo", 20639 }, 20640 Spec: spec, 20641 }, 20642 }, 20643 "fractional-compute-spec": { 20644 rq: core.ResourceQuota{ 20645 ObjectMeta: metav1.ObjectMeta{ 20646 Name: "abc", 20647 Namespace: "foo", 20648 }, 20649 Spec: fractionalComputeSpec, 20650 }, 20651 }, 20652 "terminating-spec": { 20653 rq: core.ResourceQuota{ 20654 ObjectMeta: metav1.ObjectMeta{ 20655 Name: "abc", 20656 Namespace: "foo", 20657 }, 20658 Spec: terminatingSpec, 20659 }, 20660 }, 20661 "non-terminating-spec": { 20662 rq: core.ResourceQuota{ 20663 ObjectMeta: metav1.ObjectMeta{ 20664 Name: "abc", 20665 Namespace: "foo", 20666 }, 20667 Spec: nonTerminatingSpec, 20668 }, 20669 }, 20670 "best-effort-spec": { 20671 rq: core.ResourceQuota{ 20672 ObjectMeta: metav1.ObjectMeta{ 20673 Name: "abc", 20674 Namespace: "foo", 20675 }, 20676 Spec: bestEffortSpec, 20677 }, 20678 }, 20679 "cross-namespace-affinity-spec": { 20680 rq: core.ResourceQuota{ 20681 ObjectMeta: metav1.ObjectMeta{ 20682 Name: "abc", 20683 Namespace: "foo", 20684 }, 20685 Spec: crossNamespaceAffinitySpec, 20686 }, 20687 }, 20688 "scope-selector-spec": { 20689 rq: core.ResourceQuota{ 20690 ObjectMeta: metav1.ObjectMeta{ 20691 Name: "abc", 20692 Namespace: "foo", 20693 }, 20694 Spec: scopeSelectorSpec, 20695 }, 20696 }, 20697 "non-best-effort-spec": { 20698 rq: core.ResourceQuota{ 20699 ObjectMeta: metav1.ObjectMeta{ 20700 Name: "abc", 20701 Namespace: "foo", 20702 }, 20703 Spec: nonBestEffortSpec, 20704 }, 20705 }, 20706 "zero-length Name": { 20707 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec}, 20708 errDetail: "name or generateName is required", 20709 }, 20710 "zero-length Namespace": { 20711 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec}, 20712 errField: "metadata.namespace", 20713 }, 20714 "invalid Name": { 20715 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec}, 20716 errDetail: dnsSubdomainLabelErrMsg, 20717 }, 20718 "invalid Namespace": { 20719 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec}, 20720 errDetail: dnsLabelErrMsg, 20721 }, 20722 "negative-limits": { 20723 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec}, 20724 errDetail: isNegativeErrorMsg, 20725 }, 20726 "fractional-api-resource": { 20727 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec}, 20728 errDetail: isNotIntegerErrorMsg, 20729 }, 20730 "invalid-quota-resource": { 20731 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec}, 20732 errDetail: isInvalidQuotaResource, 20733 }, 20734 "invalid-quota-terminating-pair": { 20735 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec}, 20736 errDetail: "conflicting scopes", 20737 }, 20738 "invalid-quota-besteffort-pair": { 20739 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec}, 20740 errDetail: "conflicting scopes", 20741 }, 20742 "invalid-quota-scope-name": { 20743 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec}, 20744 errDetail: "unsupported scope", 20745 }, 20746 "invalid-cross-namespace-affinity": { 20747 rq: core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec}, 20748 errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity", 20749 }, 20750 } 20751 for name, tc := range testCases { 20752 t.Run(name, func(t *testing.T) { 20753 errs := ValidateResourceQuota(&tc.rq) 20754 if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 { 20755 t.Errorf("expected success: %v", errs) 20756 } else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 { 20757 t.Errorf("expected failure") 20758 } else { 20759 for i := range errs { 20760 if !strings.Contains(errs[i].Detail, tc.errDetail) { 20761 t.Errorf("expected error detail either empty or %s, got %s", tc.errDetail, errs[i].Detail) 20762 } 20763 } 20764 } 20765 }) 20766 } 20767 } 20768 20769 func TestValidateNamespace(t *testing.T) { 20770 validLabels := map[string]string{"a": "b"} 20771 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 20772 successCases := []core.Namespace{{ 20773 ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels}, 20774 }, { 20775 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, 20776 Spec: core.NamespaceSpec{ 20777 Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"}, 20778 }, 20779 }, 20780 } 20781 for _, successCase := range successCases { 20782 if errs := ValidateNamespace(&successCase); len(errs) != 0 { 20783 t.Errorf("expected success: %v", errs) 20784 } 20785 } 20786 errorCases := map[string]struct { 20787 R core.Namespace 20788 D string 20789 }{ 20790 "zero-length name": { 20791 core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}}, 20792 "", 20793 }, 20794 "defined-namespace": { 20795 core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}}, 20796 "", 20797 }, 20798 "invalid-labels": { 20799 core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}}, 20800 "", 20801 }, 20802 } 20803 for k, v := range errorCases { 20804 errs := ValidateNamespace(&v.R) 20805 if len(errs) == 0 { 20806 t.Errorf("expected failure for %s", k) 20807 } 20808 } 20809 } 20810 20811 func TestValidateNamespaceFinalizeUpdate(t *testing.T) { 20812 tests := []struct { 20813 oldNamespace core.Namespace 20814 namespace core.Namespace 20815 valid bool 20816 }{ 20817 {core.Namespace{}, core.Namespace{}, true}, 20818 {core.Namespace{ 20819 ObjectMeta: metav1.ObjectMeta{ 20820 Name: "foo"}}, 20821 core.Namespace{ 20822 ObjectMeta: metav1.ObjectMeta{ 20823 Name: "foo"}, 20824 Spec: core.NamespaceSpec{ 20825 Finalizers: []core.FinalizerName{"Foo"}, 20826 }, 20827 }, false}, 20828 {core.Namespace{ 20829 ObjectMeta: metav1.ObjectMeta{ 20830 Name: "foo"}, 20831 Spec: core.NamespaceSpec{ 20832 Finalizers: []core.FinalizerName{"foo.com/bar"}, 20833 }, 20834 }, 20835 core.Namespace{ 20836 ObjectMeta: metav1.ObjectMeta{ 20837 Name: "foo"}, 20838 Spec: core.NamespaceSpec{ 20839 Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"}, 20840 }, 20841 }, true}, 20842 {core.Namespace{ 20843 ObjectMeta: metav1.ObjectMeta{ 20844 Name: "fooemptyfinalizer"}, 20845 Spec: core.NamespaceSpec{ 20846 Finalizers: []core.FinalizerName{"foo.com/bar"}, 20847 }, 20848 }, 20849 core.Namespace{ 20850 ObjectMeta: metav1.ObjectMeta{ 20851 Name: "fooemptyfinalizer"}, 20852 Spec: core.NamespaceSpec{ 20853 Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"}, 20854 }, 20855 }, false}, 20856 } 20857 for i, test := range tests { 20858 test.namespace.ObjectMeta.ResourceVersion = "1" 20859 test.oldNamespace.ObjectMeta.ResourceVersion = "1" 20860 errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace) 20861 if test.valid && len(errs) > 0 { 20862 t.Errorf("%d: Unexpected error: %v", i, errs) 20863 t.Logf("%#v vs %#v", test.oldNamespace, test.namespace) 20864 } 20865 if !test.valid && len(errs) == 0 { 20866 t.Errorf("%d: Unexpected non-error", i) 20867 } 20868 } 20869 } 20870 20871 func TestValidateNamespaceStatusUpdate(t *testing.T) { 20872 now := metav1.Now() 20873 20874 tests := []struct { 20875 oldNamespace core.Namespace 20876 namespace core.Namespace 20877 valid bool 20878 }{ 20879 {core.Namespace{}, core.Namespace{ 20880 Status: core.NamespaceStatus{ 20881 Phase: core.NamespaceActive, 20882 }, 20883 }, true}, 20884 // Cannot set deletionTimestamp via status update 20885 {core.Namespace{ 20886 ObjectMeta: metav1.ObjectMeta{ 20887 Name: "foo"}}, 20888 core.Namespace{ 20889 ObjectMeta: metav1.ObjectMeta{ 20890 Name: "foo", 20891 DeletionTimestamp: &now}, 20892 Status: core.NamespaceStatus{ 20893 Phase: core.NamespaceTerminating, 20894 }, 20895 }, false}, 20896 // Can update phase via status update 20897 {core.Namespace{ 20898 ObjectMeta: metav1.ObjectMeta{ 20899 Name: "foo", 20900 DeletionTimestamp: &now}}, 20901 core.Namespace{ 20902 ObjectMeta: metav1.ObjectMeta{ 20903 Name: "foo", 20904 DeletionTimestamp: &now}, 20905 Status: core.NamespaceStatus{ 20906 Phase: core.NamespaceTerminating, 20907 }, 20908 }, true}, 20909 {core.Namespace{ 20910 ObjectMeta: metav1.ObjectMeta{ 20911 Name: "foo"}}, 20912 core.Namespace{ 20913 ObjectMeta: metav1.ObjectMeta{ 20914 Name: "foo"}, 20915 Status: core.NamespaceStatus{ 20916 Phase: core.NamespaceTerminating, 20917 }, 20918 }, false}, 20919 {core.Namespace{ 20920 ObjectMeta: metav1.ObjectMeta{ 20921 Name: "foo"}}, 20922 core.Namespace{ 20923 ObjectMeta: metav1.ObjectMeta{ 20924 Name: "bar"}, 20925 Status: core.NamespaceStatus{ 20926 Phase: core.NamespaceTerminating, 20927 }, 20928 }, false}, 20929 } 20930 for i, test := range tests { 20931 test.namespace.ObjectMeta.ResourceVersion = "1" 20932 test.oldNamespace.ObjectMeta.ResourceVersion = "1" 20933 errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace) 20934 if test.valid && len(errs) > 0 { 20935 t.Errorf("%d: Unexpected error: %v", i, errs) 20936 t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta) 20937 } 20938 if !test.valid && len(errs) == 0 { 20939 t.Errorf("%d: Unexpected non-error", i) 20940 } 20941 } 20942 } 20943 20944 func TestValidateNamespaceUpdate(t *testing.T) { 20945 tests := []struct { 20946 oldNamespace core.Namespace 20947 namespace core.Namespace 20948 valid bool 20949 }{ 20950 {core.Namespace{}, core.Namespace{}, true}, 20951 {core.Namespace{ 20952 ObjectMeta: metav1.ObjectMeta{ 20953 Name: "foo1"}}, 20954 core.Namespace{ 20955 ObjectMeta: metav1.ObjectMeta{ 20956 Name: "bar1"}, 20957 }, false}, 20958 {core.Namespace{ 20959 ObjectMeta: metav1.ObjectMeta{ 20960 Name: "foo2", 20961 Labels: map[string]string{"foo": "bar"}, 20962 }, 20963 }, core.Namespace{ 20964 ObjectMeta: metav1.ObjectMeta{ 20965 Name: "foo2", 20966 Labels: map[string]string{"foo": "baz"}, 20967 }, 20968 }, true}, 20969 {core.Namespace{ 20970 ObjectMeta: metav1.ObjectMeta{ 20971 Name: "foo3", 20972 }, 20973 }, core.Namespace{ 20974 ObjectMeta: metav1.ObjectMeta{ 20975 Name: "foo3", 20976 Labels: map[string]string{"foo": "baz"}, 20977 }, 20978 }, true}, 20979 {core.Namespace{ 20980 ObjectMeta: metav1.ObjectMeta{ 20981 Name: "foo4", 20982 Labels: map[string]string{"bar": "foo"}, 20983 }, 20984 }, core.Namespace{ 20985 ObjectMeta: metav1.ObjectMeta{ 20986 Name: "foo4", 20987 Labels: map[string]string{"foo": "baz"}, 20988 }, 20989 }, true}, 20990 {core.Namespace{ 20991 ObjectMeta: metav1.ObjectMeta{ 20992 Name: "foo5", 20993 Labels: map[string]string{"foo": "baz"}, 20994 }, 20995 }, core.Namespace{ 20996 ObjectMeta: metav1.ObjectMeta{ 20997 Name: "foo5", 20998 Labels: map[string]string{"Foo": "baz"}, 20999 }, 21000 }, true}, 21001 {core.Namespace{ 21002 ObjectMeta: metav1.ObjectMeta{ 21003 Name: "foo6", 21004 Labels: map[string]string{"foo": "baz"}, 21005 }, 21006 }, core.Namespace{ 21007 ObjectMeta: metav1.ObjectMeta{ 21008 Name: "foo6", 21009 Labels: map[string]string{"Foo": "baz"}, 21010 }, 21011 Spec: core.NamespaceSpec{ 21012 Finalizers: []core.FinalizerName{"kubernetes"}, 21013 }, 21014 Status: core.NamespaceStatus{ 21015 Phase: core.NamespaceTerminating, 21016 }, 21017 }, true}, 21018 } 21019 for i, test := range tests { 21020 test.namespace.ObjectMeta.ResourceVersion = "1" 21021 test.oldNamespace.ObjectMeta.ResourceVersion = "1" 21022 errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace) 21023 if test.valid && len(errs) > 0 { 21024 t.Errorf("%d: Unexpected error: %v", i, errs) 21025 t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta) 21026 } 21027 if !test.valid && len(errs) == 0 { 21028 t.Errorf("%d: Unexpected non-error", i) 21029 } 21030 } 21031 } 21032 21033 func TestValidateSecret(t *testing.T) { 21034 // Opaque secret validation 21035 validSecret := func() core.Secret { 21036 return core.Secret{ 21037 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 21038 Data: map[string][]byte{ 21039 "data-1": []byte("bar"), 21040 }, 21041 } 21042 } 21043 21044 var ( 21045 emptyName = validSecret() 21046 invalidName = validSecret() 21047 emptyNs = validSecret() 21048 invalidNs = validSecret() 21049 overMaxSize = validSecret() 21050 invalidKey = validSecret() 21051 leadingDotKey = validSecret() 21052 dotKey = validSecret() 21053 doubleDotKey = validSecret() 21054 ) 21055 21056 emptyName.Name = "" 21057 invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals" 21058 emptyNs.Namespace = "" 21059 invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals" 21060 overMaxSize.Data = map[string][]byte{ 21061 "over": make([]byte, core.MaxSecretSize+1), 21062 } 21063 invalidKey.Data["a*b"] = []byte("whoops") 21064 leadingDotKey.Data[".key"] = []byte("bar") 21065 dotKey.Data["."] = []byte("bar") 21066 doubleDotKey.Data[".."] = []byte("bar") 21067 21068 // kubernetes.io/service-account-token secret validation 21069 validServiceAccountTokenSecret := func() core.Secret { 21070 return core.Secret{ 21071 ObjectMeta: metav1.ObjectMeta{ 21072 Name: "foo", 21073 Namespace: "bar", 21074 Annotations: map[string]string{ 21075 core.ServiceAccountNameKey: "foo", 21076 }, 21077 }, 21078 Type: core.SecretTypeServiceAccountToken, 21079 Data: map[string][]byte{ 21080 "data-1": []byte("bar"), 21081 }, 21082 } 21083 } 21084 21085 var ( 21086 emptyTokenAnnotation = validServiceAccountTokenSecret() 21087 missingTokenAnnotation = validServiceAccountTokenSecret() 21088 missingTokenAnnotations = validServiceAccountTokenSecret() 21089 ) 21090 emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = "" 21091 delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey) 21092 missingTokenAnnotations.Annotations = nil 21093 21094 tests := map[string]struct { 21095 secret core.Secret 21096 valid bool 21097 }{ 21098 "valid": {validSecret(), true}, 21099 "empty name": {emptyName, false}, 21100 "invalid name": {invalidName, false}, 21101 "empty namespace": {emptyNs, false}, 21102 "invalid namespace": {invalidNs, false}, 21103 "over max size": {overMaxSize, false}, 21104 "invalid key": {invalidKey, false}, 21105 "valid service-account-token secret": {validServiceAccountTokenSecret(), true}, 21106 "empty service-account-token annotation": {emptyTokenAnnotation, false}, 21107 "missing service-account-token annotation": {missingTokenAnnotation, false}, 21108 "missing service-account-token annotations": {missingTokenAnnotations, false}, 21109 "leading dot key": {leadingDotKey, true}, 21110 "dot key": {dotKey, false}, 21111 "double dot key": {doubleDotKey, false}, 21112 } 21113 21114 for name, tc := range tests { 21115 errs := ValidateSecret(&tc.secret) 21116 if tc.valid && len(errs) > 0 { 21117 t.Errorf("%v: Unexpected error: %v", name, errs) 21118 } 21119 if !tc.valid && len(errs) == 0 { 21120 t.Errorf("%v: Unexpected non-error", name) 21121 } 21122 } 21123 } 21124 21125 func TestValidateSecretUpdate(t *testing.T) { 21126 validSecret := func() core.Secret { 21127 return core.Secret{ 21128 ObjectMeta: metav1.ObjectMeta{ 21129 Name: "foo", 21130 Namespace: "bar", 21131 ResourceVersion: "20", 21132 }, 21133 Data: map[string][]byte{ 21134 "data-1": []byte("bar"), 21135 }, 21136 } 21137 } 21138 21139 falseVal := false 21140 trueVal := true 21141 21142 secret := validSecret() 21143 immutableSecret := validSecret() 21144 immutableSecret.Immutable = &trueVal 21145 mutableSecret := validSecret() 21146 mutableSecret.Immutable = &falseVal 21147 21148 secretWithData := validSecret() 21149 secretWithData.Data["data-2"] = []byte("baz") 21150 immutableSecretWithData := validSecret() 21151 immutableSecretWithData.Immutable = &trueVal 21152 immutableSecretWithData.Data["data-2"] = []byte("baz") 21153 21154 secretWithChangedData := validSecret() 21155 secretWithChangedData.Data["data-1"] = []byte("foo") 21156 immutableSecretWithChangedData := validSecret() 21157 immutableSecretWithChangedData.Immutable = &trueVal 21158 immutableSecretWithChangedData.Data["data-1"] = []byte("foo") 21159 21160 tests := []struct { 21161 name string 21162 oldSecret core.Secret 21163 newSecret core.Secret 21164 valid bool 21165 }{{ 21166 name: "mark secret immutable", 21167 oldSecret: secret, 21168 newSecret: immutableSecret, 21169 valid: true, 21170 }, { 21171 name: "revert immutable secret", 21172 oldSecret: immutableSecret, 21173 newSecret: secret, 21174 valid: false, 21175 }, { 21176 name: "makr immutable secret mutable", 21177 oldSecret: immutableSecret, 21178 newSecret: mutableSecret, 21179 valid: false, 21180 }, { 21181 name: "add data in secret", 21182 oldSecret: secret, 21183 newSecret: secretWithData, 21184 valid: true, 21185 }, { 21186 name: "add data in immutable secret", 21187 oldSecret: immutableSecret, 21188 newSecret: immutableSecretWithData, 21189 valid: false, 21190 }, { 21191 name: "change data in secret", 21192 oldSecret: secret, 21193 newSecret: secretWithChangedData, 21194 valid: true, 21195 }, { 21196 name: "change data in immutable secret", 21197 oldSecret: immutableSecret, 21198 newSecret: immutableSecretWithChangedData, 21199 valid: false, 21200 }, 21201 } 21202 21203 for _, tc := range tests { 21204 t.Run(tc.name, func(t *testing.T) { 21205 errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret) 21206 if tc.valid && len(errs) > 0 { 21207 t.Errorf("Unexpected error: %v", errs) 21208 } 21209 if !tc.valid && len(errs) == 0 { 21210 t.Errorf("Unexpected lack of error") 21211 } 21212 }) 21213 } 21214 } 21215 21216 func TestValidateDockerConfigSecret(t *testing.T) { 21217 validDockerSecret := func() core.Secret { 21218 return core.Secret{ 21219 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 21220 Type: core.SecretTypeDockercfg, 21221 Data: map[string][]byte{ 21222 core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`), 21223 }, 21224 } 21225 } 21226 validDockerSecret2 := func() core.Secret { 21227 return core.Secret{ 21228 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 21229 Type: core.SecretTypeDockerConfigJSON, 21230 Data: map[string][]byte{ 21231 core.DockerConfigJSONKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`), 21232 }, 21233 } 21234 } 21235 21236 var ( 21237 missingDockerConfigKey = validDockerSecret() 21238 emptyDockerConfigKey = validDockerSecret() 21239 invalidDockerConfigKey = validDockerSecret() 21240 missingDockerConfigKey2 = validDockerSecret2() 21241 emptyDockerConfigKey2 = validDockerSecret2() 21242 invalidDockerConfigKey2 = validDockerSecret2() 21243 ) 21244 21245 delete(missingDockerConfigKey.Data, core.DockerConfigKey) 21246 emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("") 21247 invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad") 21248 delete(missingDockerConfigKey2.Data, core.DockerConfigJSONKey) 21249 emptyDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("") 21250 invalidDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("bad") 21251 21252 tests := map[string]struct { 21253 secret core.Secret 21254 valid bool 21255 }{ 21256 "valid dockercfg": {validDockerSecret(), true}, 21257 "missing dockercfg": {missingDockerConfigKey, false}, 21258 "empty dockercfg": {emptyDockerConfigKey, false}, 21259 "invalid dockercfg": {invalidDockerConfigKey, false}, 21260 "valid config.json": {validDockerSecret2(), true}, 21261 "missing config.json": {missingDockerConfigKey2, false}, 21262 "empty config.json": {emptyDockerConfigKey2, false}, 21263 "invalid config.json": {invalidDockerConfigKey2, false}, 21264 } 21265 21266 for name, tc := range tests { 21267 errs := ValidateSecret(&tc.secret) 21268 if tc.valid && len(errs) > 0 { 21269 t.Errorf("%v: Unexpected error: %v", name, errs) 21270 } 21271 if !tc.valid && len(errs) == 0 { 21272 t.Errorf("%v: Unexpected non-error", name) 21273 } 21274 } 21275 } 21276 21277 func TestValidateBasicAuthSecret(t *testing.T) { 21278 validBasicAuthSecret := func() core.Secret { 21279 return core.Secret{ 21280 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 21281 Type: core.SecretTypeBasicAuth, 21282 Data: map[string][]byte{ 21283 core.BasicAuthUsernameKey: []byte("username"), 21284 core.BasicAuthPasswordKey: []byte("password"), 21285 }, 21286 } 21287 } 21288 21289 var ( 21290 missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret() 21291 ) 21292 21293 delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey) 21294 delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey) 21295 21296 tests := map[string]struct { 21297 secret core.Secret 21298 valid bool 21299 }{ 21300 "valid": {validBasicAuthSecret(), true}, 21301 "missing username and password": {missingBasicAuthUsernamePasswordKeys, false}, 21302 } 21303 21304 for name, tc := range tests { 21305 errs := ValidateSecret(&tc.secret) 21306 if tc.valid && len(errs) > 0 { 21307 t.Errorf("%v: Unexpected error: %v", name, errs) 21308 } 21309 if !tc.valid && len(errs) == 0 { 21310 t.Errorf("%v: Unexpected non-error", name) 21311 } 21312 } 21313 } 21314 21315 func TestValidateSSHAuthSecret(t *testing.T) { 21316 validSSHAuthSecret := func() core.Secret { 21317 return core.Secret{ 21318 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 21319 Type: core.SecretTypeSSHAuth, 21320 Data: map[string][]byte{ 21321 core.SSHAuthPrivateKey: []byte("foo-bar-baz"), 21322 }, 21323 } 21324 } 21325 21326 missingSSHAuthPrivateKey := validSSHAuthSecret() 21327 21328 delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey) 21329 21330 tests := map[string]struct { 21331 secret core.Secret 21332 valid bool 21333 }{ 21334 "valid": {validSSHAuthSecret(), true}, 21335 "missing private key": {missingSSHAuthPrivateKey, false}, 21336 } 21337 21338 for name, tc := range tests { 21339 errs := ValidateSecret(&tc.secret) 21340 if tc.valid && len(errs) > 0 { 21341 t.Errorf("%v: Unexpected error: %v", name, errs) 21342 } 21343 if !tc.valid && len(errs) == 0 { 21344 t.Errorf("%v: Unexpected non-error", name) 21345 } 21346 } 21347 } 21348 21349 func TestValidateEndpointsCreate(t *testing.T) { 21350 successCases := map[string]struct { 21351 endpoints core.Endpoints 21352 }{ 21353 "simple endpoint": { 21354 endpoints: core.Endpoints{ 21355 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21356 Subsets: []core.EndpointSubset{{ 21357 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}}, 21358 Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, 21359 }, { 21360 Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, 21361 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}}, 21362 }}, 21363 }, 21364 }, 21365 "empty subsets": { 21366 endpoints: core.Endpoints{ 21367 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21368 }, 21369 }, 21370 "no name required for singleton port": { 21371 endpoints: core.Endpoints{ 21372 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21373 Subsets: []core.EndpointSubset{{ 21374 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21375 Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}}, 21376 }}, 21377 }, 21378 }, 21379 "valid appProtocol": { 21380 endpoints: core.Endpoints{ 21381 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21382 Subsets: []core.EndpointSubset{{ 21383 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21384 Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}}, 21385 }}, 21386 }, 21387 }, 21388 "empty ports": { 21389 endpoints: core.Endpoints{ 21390 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21391 Subsets: []core.EndpointSubset{{ 21392 Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}}, 21393 }}, 21394 }, 21395 }, 21396 } 21397 21398 for name, tc := range successCases { 21399 t.Run(name, func(t *testing.T) { 21400 errs := ValidateEndpointsCreate(&tc.endpoints) 21401 if len(errs) != 0 { 21402 t.Errorf("Expected no validation errors, got %v", errs) 21403 } 21404 21405 }) 21406 } 21407 21408 errorCases := map[string]struct { 21409 endpoints core.Endpoints 21410 errorType field.ErrorType 21411 errorDetail string 21412 }{ 21413 "missing namespace": { 21414 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}}, 21415 errorType: "FieldValueRequired", 21416 }, 21417 "missing name": { 21418 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}}, 21419 errorType: "FieldValueRequired", 21420 }, 21421 "invalid namespace": { 21422 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}}, 21423 errorType: "FieldValueInvalid", 21424 errorDetail: dnsLabelErrMsg, 21425 }, 21426 "invalid name": { 21427 endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}}, 21428 errorType: "FieldValueInvalid", 21429 errorDetail: dnsSubdomainLabelErrMsg, 21430 }, 21431 "empty addresses": { 21432 endpoints: core.Endpoints{ 21433 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21434 Subsets: []core.EndpointSubset{{ 21435 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, 21436 }}, 21437 }, 21438 errorType: "FieldValueRequired", 21439 }, 21440 "invalid IP": { 21441 endpoints: core.Endpoints{ 21442 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21443 Subsets: []core.EndpointSubset{{ 21444 Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}}, 21445 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, 21446 }}, 21447 }, 21448 errorType: "FieldValueInvalid", 21449 errorDetail: "must be a valid IP address", 21450 }, 21451 "Multiple ports, one without name": { 21452 endpoints: core.Endpoints{ 21453 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21454 Subsets: []core.EndpointSubset{{ 21455 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21456 Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}}, 21457 }}, 21458 }, 21459 errorType: "FieldValueRequired", 21460 }, 21461 "Invalid port number": { 21462 endpoints: core.Endpoints{ 21463 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21464 Subsets: []core.EndpointSubset{{ 21465 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21466 Ports: []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}}, 21467 }}, 21468 }, 21469 errorType: "FieldValueInvalid", 21470 errorDetail: "between", 21471 }, 21472 "Invalid protocol": { 21473 endpoints: core.Endpoints{ 21474 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21475 Subsets: []core.EndpointSubset{{ 21476 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21477 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}}, 21478 }}, 21479 }, 21480 errorType: "FieldValueNotSupported", 21481 }, 21482 "Address missing IP": { 21483 endpoints: core.Endpoints{ 21484 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21485 Subsets: []core.EndpointSubset{{ 21486 Addresses: []core.EndpointAddress{{}}, 21487 Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}}, 21488 }}, 21489 }, 21490 errorType: "FieldValueInvalid", 21491 errorDetail: "must be a valid IP address", 21492 }, 21493 "Port missing number": { 21494 endpoints: core.Endpoints{ 21495 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21496 Subsets: []core.EndpointSubset{{ 21497 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21498 Ports: []core.EndpointPort{{Name: "a", Protocol: "TCP"}}, 21499 }}, 21500 }, 21501 errorType: "FieldValueInvalid", 21502 errorDetail: "between", 21503 }, 21504 "Port missing protocol": { 21505 endpoints: core.Endpoints{ 21506 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21507 Subsets: []core.EndpointSubset{{ 21508 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21509 Ports: []core.EndpointPort{{Name: "a", Port: 93}}, 21510 }}, 21511 }, 21512 errorType: "FieldValueRequired", 21513 }, 21514 "Address is loopback": { 21515 endpoints: core.Endpoints{ 21516 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21517 Subsets: []core.EndpointSubset{{ 21518 Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}}, 21519 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, 21520 }}, 21521 }, 21522 errorType: "FieldValueInvalid", 21523 errorDetail: "loopback", 21524 }, 21525 "Address is link-local": { 21526 endpoints: core.Endpoints{ 21527 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21528 Subsets: []core.EndpointSubset{{ 21529 Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}}, 21530 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, 21531 }}, 21532 }, 21533 errorType: "FieldValueInvalid", 21534 errorDetail: "link-local", 21535 }, 21536 "Address is link-local multicast": { 21537 endpoints: core.Endpoints{ 21538 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21539 Subsets: []core.EndpointSubset{{ 21540 Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}}, 21541 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}}, 21542 }}, 21543 }, 21544 errorType: "FieldValueInvalid", 21545 errorDetail: "link-local multicast", 21546 }, 21547 "Invalid AppProtocol": { 21548 endpoints: core.Endpoints{ 21549 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"}, 21550 Subsets: []core.EndpointSubset{{ 21551 Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}}, 21552 Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}}, 21553 }}, 21554 }, 21555 errorType: "FieldValueInvalid", 21556 errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character", 21557 }, 21558 } 21559 21560 for k, v := range errorCases { 21561 t.Run(k, func(t *testing.T) { 21562 if errs := ValidateEndpointsCreate(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { 21563 t.Errorf("Expected error type %s with detail %q, got %v", v.errorType, v.errorDetail, errs) 21564 } 21565 }) 21566 } 21567 } 21568 21569 func TestValidateEndpointsUpdate(t *testing.T) { 21570 baseEndpoints := core.Endpoints{ 21571 ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace", ResourceVersion: "1234"}, 21572 Subsets: []core.EndpointSubset{{ 21573 Addresses: []core.EndpointAddress{{IP: "10.1.2.3"}}, 21574 }}, 21575 } 21576 21577 testCases := map[string]struct { 21578 tweakOldEndpoints func(ep *core.Endpoints) 21579 tweakNewEndpoints func(ep *core.Endpoints) 21580 numExpectedErrors int 21581 }{ 21582 "update to valid app protocol": { 21583 tweakOldEndpoints: func(ep *core.Endpoints) { 21584 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}} 21585 }, 21586 tweakNewEndpoints: func(ep *core.Endpoints) { 21587 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("https")}} 21588 }, 21589 numExpectedErrors: 0, 21590 }, 21591 "update to invalid app protocol": { 21592 tweakOldEndpoints: func(ep *core.Endpoints) { 21593 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}} 21594 }, 21595 tweakNewEndpoints: func(ep *core.Endpoints) { 21596 ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("~https")}} 21597 }, 21598 numExpectedErrors: 1, 21599 }, 21600 } 21601 21602 for name, tc := range testCases { 21603 t.Run(name, func(t *testing.T) { 21604 oldEndpoints := baseEndpoints.DeepCopy() 21605 tc.tweakOldEndpoints(oldEndpoints) 21606 newEndpoints := baseEndpoints.DeepCopy() 21607 tc.tweakNewEndpoints(newEndpoints) 21608 21609 errs := ValidateEndpointsUpdate(newEndpoints, oldEndpoints) 21610 if len(errs) != tc.numExpectedErrors { 21611 t.Errorf("Expected %d validation errors, got %d: %v", tc.numExpectedErrors, len(errs), errs) 21612 } 21613 21614 }) 21615 } 21616 } 21617 21618 func TestValidateWindowsSecurityContext(t *testing.T) { 21619 tests := []struct { 21620 name string 21621 sc *core.PodSpec 21622 expectError bool 21623 errorMsg string 21624 errorType field.ErrorType 21625 }{{ 21626 name: "pod with SELinux Options", 21627 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}}, 21628 expectError: true, 21629 errorMsg: "cannot be set for a windows pod", 21630 errorType: "FieldValueForbidden", 21631 }, { 21632 name: "pod with SeccompProfile", 21633 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}}, 21634 expectError: true, 21635 errorMsg: "cannot be set for a windows pod", 21636 errorType: "FieldValueForbidden", 21637 }, { 21638 name: "pod with WindowsOptions, no error", 21639 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}}, 21640 expectError: false, 21641 }, 21642 } 21643 for _, test := range tests { 21644 t.Run(test.name, func(t *testing.T) { 21645 errs := validateWindows(test.sc, field.NewPath("field")) 21646 if test.expectError && len(errs) > 0 { 21647 if errs[0].Type != test.errorType { 21648 t.Errorf("expected error type %q got %q", test.errorType, errs[0].Type) 21649 } 21650 if errs[0].Detail != test.errorMsg { 21651 t.Errorf("expected error detail %q, got %q", test.errorMsg, errs[0].Detail) 21652 } 21653 } else if test.expectError && len(errs) == 0 { 21654 t.Error("Unexpected success") 21655 } 21656 if !test.expectError && len(errs) != 0 { 21657 t.Errorf("Unexpected error(s): %v", errs) 21658 } 21659 }) 21660 } 21661 } 21662 21663 func TestValidateOSFields(t *testing.T) { 21664 // Contains the list of OS specific fields within pod spec. 21665 // All the fields in pod spec should be either osSpecific or osNeutral field 21666 // To make a field OS specific: 21667 // - Add documentation to the os specific field indicating which os it can/cannot be set for 21668 // - Add documentation to the os field in the api 21669 // - Add validation logic validateLinux, validateWindows functions to make sure the field is only set for eligible OSes 21670 osSpecificFields := sets.NewString( 21671 "Containers[*].SecurityContext.AllowPrivilegeEscalation", 21672 "Containers[*].SecurityContext.Capabilities", 21673 "Containers[*].SecurityContext.Privileged", 21674 "Containers[*].SecurityContext.ProcMount", 21675 "Containers[*].SecurityContext.ReadOnlyRootFilesystem", 21676 "Containers[*].SecurityContext.RunAsGroup", 21677 "Containers[*].SecurityContext.RunAsUser", 21678 "Containers[*].SecurityContext.SELinuxOptions", 21679 "Containers[*].SecurityContext.SeccompProfile", 21680 "Containers[*].SecurityContext.WindowsOptions", 21681 "InitContainers[*].SecurityContext.AllowPrivilegeEscalation", 21682 "InitContainers[*].SecurityContext.Capabilities", 21683 "InitContainers[*].SecurityContext.Privileged", 21684 "InitContainers[*].SecurityContext.ProcMount", 21685 "InitContainers[*].SecurityContext.ReadOnlyRootFilesystem", 21686 "InitContainers[*].SecurityContext.RunAsGroup", 21687 "InitContainers[*].SecurityContext.RunAsUser", 21688 "InitContainers[*].SecurityContext.SELinuxOptions", 21689 "InitContainers[*].SecurityContext.SeccompProfile", 21690 "InitContainers[*].SecurityContext.WindowsOptions", 21691 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AllowPrivilegeEscalation", 21692 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Capabilities", 21693 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Privileged", 21694 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ProcMount", 21695 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ReadOnlyRootFilesystem", 21696 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsGroup", 21697 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsUser", 21698 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SELinuxOptions", 21699 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile", 21700 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions", 21701 "OS", 21702 "SecurityContext.FSGroup", 21703 "SecurityContext.FSGroupChangePolicy", 21704 "SecurityContext.HostIPC", 21705 "SecurityContext.HostNetwork", 21706 "SecurityContext.HostPID", 21707 "SecurityContext.HostUsers", 21708 "SecurityContext.RunAsGroup", 21709 "SecurityContext.RunAsUser", 21710 "SecurityContext.SELinuxOptions", 21711 "SecurityContext.SeccompProfile", 21712 "SecurityContext.ShareProcessNamespace", 21713 "SecurityContext.SupplementalGroups", 21714 "SecurityContext.Sysctls", 21715 "SecurityContext.WindowsOptions", 21716 ) 21717 osNeutralFields := sets.NewString( 21718 "ActiveDeadlineSeconds", 21719 "Affinity", 21720 "AutomountServiceAccountToken", 21721 "Containers[*].Args", 21722 "Containers[*].Command", 21723 "Containers[*].Env", 21724 "Containers[*].EnvFrom", 21725 "Containers[*].Image", 21726 "Containers[*].ImagePullPolicy", 21727 "Containers[*].Lifecycle", 21728 "Containers[*].LivenessProbe", 21729 "Containers[*].Name", 21730 "Containers[*].Ports", 21731 "Containers[*].ReadinessProbe", 21732 "Containers[*].Resources", 21733 "Containers[*].ResizePolicy[*].RestartPolicy", 21734 "Containers[*].ResizePolicy[*].ResourceName", 21735 "Containers[*].RestartPolicy", 21736 "Containers[*].SecurityContext.RunAsNonRoot", 21737 "Containers[*].Stdin", 21738 "Containers[*].StdinOnce", 21739 "Containers[*].StartupProbe", 21740 "Containers[*].VolumeDevices[*]", 21741 "Containers[*].VolumeMounts[*]", 21742 "Containers[*].TTY", 21743 "Containers[*].TerminationMessagePath", 21744 "Containers[*].TerminationMessagePolicy", 21745 "Containers[*].WorkingDir", 21746 "DNSPolicy", 21747 "EnableServiceLinks", 21748 "EphemeralContainers[*].EphemeralContainerCommon.Args", 21749 "EphemeralContainers[*].EphemeralContainerCommon.Command", 21750 "EphemeralContainers[*].EphemeralContainerCommon.Env", 21751 "EphemeralContainers[*].EphemeralContainerCommon.EnvFrom", 21752 "EphemeralContainers[*].EphemeralContainerCommon.Image", 21753 "EphemeralContainers[*].EphemeralContainerCommon.ImagePullPolicy", 21754 "EphemeralContainers[*].EphemeralContainerCommon.Lifecycle", 21755 "EphemeralContainers[*].EphemeralContainerCommon.LivenessProbe", 21756 "EphemeralContainers[*].EphemeralContainerCommon.Name", 21757 "EphemeralContainers[*].EphemeralContainerCommon.Ports", 21758 "EphemeralContainers[*].EphemeralContainerCommon.ReadinessProbe", 21759 "EphemeralContainers[*].EphemeralContainerCommon.Resources", 21760 "EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].RestartPolicy", 21761 "EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].ResourceName", 21762 "EphemeralContainers[*].EphemeralContainerCommon.RestartPolicy", 21763 "EphemeralContainers[*].EphemeralContainerCommon.Stdin", 21764 "EphemeralContainers[*].EphemeralContainerCommon.StdinOnce", 21765 "EphemeralContainers[*].EphemeralContainerCommon.TTY", 21766 "EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePath", 21767 "EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePolicy", 21768 "EphemeralContainers[*].EphemeralContainerCommon.WorkingDir", 21769 "EphemeralContainers[*].TargetContainerName", 21770 "EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsNonRoot", 21771 "EphemeralContainers[*].EphemeralContainerCommon.StartupProbe", 21772 "EphemeralContainers[*].EphemeralContainerCommon.VolumeDevices[*]", 21773 "EphemeralContainers[*].EphemeralContainerCommon.VolumeMounts[*]", 21774 "HostAliases", 21775 "Hostname", 21776 "ImagePullSecrets", 21777 "InitContainers[*].Args", 21778 "InitContainers[*].Command", 21779 "InitContainers[*].Env", 21780 "InitContainers[*].EnvFrom", 21781 "InitContainers[*].Image", 21782 "InitContainers[*].ImagePullPolicy", 21783 "InitContainers[*].Lifecycle", 21784 "InitContainers[*].LivenessProbe", 21785 "InitContainers[*].Name", 21786 "InitContainers[*].Ports", 21787 "InitContainers[*].ReadinessProbe", 21788 "InitContainers[*].Resources", 21789 "InitContainers[*].ResizePolicy[*].RestartPolicy", 21790 "InitContainers[*].ResizePolicy[*].ResourceName", 21791 "InitContainers[*].RestartPolicy", 21792 "InitContainers[*].Stdin", 21793 "InitContainers[*].StdinOnce", 21794 "InitContainers[*].TTY", 21795 "InitContainers[*].TerminationMessagePath", 21796 "InitContainers[*].TerminationMessagePolicy", 21797 "InitContainers[*].WorkingDir", 21798 "InitContainers[*].SecurityContext.RunAsNonRoot", 21799 "InitContainers[*].StartupProbe", 21800 "InitContainers[*].VolumeDevices[*]", 21801 "InitContainers[*].VolumeMounts[*]", 21802 "NodeName", 21803 "NodeSelector", 21804 "PreemptionPolicy", 21805 "Priority", 21806 "PriorityClassName", 21807 "ReadinessGates", 21808 "ResourceClaims[*].Name", 21809 "ResourceClaims[*].Source.ResourceClaimName", 21810 "ResourceClaims[*].Source.ResourceClaimTemplateName", 21811 "RestartPolicy", 21812 "RuntimeClassName", 21813 "SchedulerName", 21814 "SchedulingGates[*].Name", 21815 "SecurityContext.RunAsNonRoot", 21816 "ServiceAccountName", 21817 "SetHostnameAsFQDN", 21818 "Subdomain", 21819 "TerminationGracePeriodSeconds", 21820 "Volumes", 21821 "DNSConfig", 21822 "Overhead", 21823 "Tolerations", 21824 "TopologySpreadConstraints", 21825 ) 21826 21827 expect := sets.NewString().Union(osSpecificFields).Union(osNeutralFields) 21828 21829 result := collectResourcePaths(t, expect, reflect.TypeOf(&core.PodSpec{}), nil) 21830 21831 if !expect.Equal(result) { 21832 // expected fields missing from result 21833 missing := expect.Difference(result) 21834 // unexpected fields in result but not specified in expect 21835 unexpected := result.Difference(expect) 21836 if len(missing) > 0 { 21837 t.Errorf("the following fields were expected, but missing from the result. "+ 21838 "If the field has been removed, please remove it from the osNeutralFields set "+ 21839 "or remove it from the osSpecificFields set, as appropriate:\n%s", 21840 strings.Join(missing.List(), "\n")) 21841 } 21842 if len(unexpected) > 0 { 21843 t.Errorf("the following fields were in the result, but unexpected. "+ 21844 "If the field is new, please add it to the osNeutralFields set "+ 21845 "or add it to the osSpecificFields set, as appropriate:\n%s", 21846 strings.Join(unexpected.List(), "\n")) 21847 } 21848 } 21849 } 21850 21851 func TestValidateSchedulingGates(t *testing.T) { 21852 fieldPath := field.NewPath("field") 21853 21854 tests := []struct { 21855 name string 21856 schedulingGates []core.PodSchedulingGate 21857 wantFieldErrors field.ErrorList 21858 }{{ 21859 name: "nil gates", 21860 schedulingGates: nil, 21861 wantFieldErrors: field.ErrorList{}, 21862 }, { 21863 name: "empty string in gates", 21864 schedulingGates: []core.PodSchedulingGate{ 21865 {Name: "foo"}, 21866 {Name: ""}, 21867 }, 21868 wantFieldErrors: field.ErrorList{ 21869 field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"), 21870 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]')"), 21871 }, 21872 }, { 21873 name: "legal gates", 21874 schedulingGates: []core.PodSchedulingGate{ 21875 {Name: "foo"}, 21876 {Name: "bar"}, 21877 }, 21878 wantFieldErrors: field.ErrorList{}, 21879 }, { 21880 name: "illegal gates", 21881 schedulingGates: []core.PodSchedulingGate{ 21882 {Name: "foo"}, 21883 {Name: "\nbar"}, 21884 }, 21885 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]')")}, 21886 }, { 21887 name: "duplicated gates (single duplication)", 21888 schedulingGates: []core.PodSchedulingGate{ 21889 {Name: "foo"}, 21890 {Name: "bar"}, 21891 {Name: "bar"}, 21892 }, 21893 wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")}, 21894 }, { 21895 name: "duplicated gates (multiple duplications)", 21896 schedulingGates: []core.PodSchedulingGate{ 21897 {Name: "foo"}, 21898 {Name: "bar"}, 21899 {Name: "foo"}, 21900 {Name: "baz"}, 21901 {Name: "foo"}, 21902 {Name: "bar"}, 21903 }, 21904 wantFieldErrors: field.ErrorList{ 21905 field.Duplicate(fieldPath.Index(2), "foo"), 21906 field.Duplicate(fieldPath.Index(4), "foo"), 21907 field.Duplicate(fieldPath.Index(5), "bar"), 21908 }, 21909 }, 21910 } 21911 for _, tt := range tests { 21912 t.Run(tt.name, func(t *testing.T) { 21913 errs := validateSchedulingGates(tt.schedulingGates, fieldPath) 21914 if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" { 21915 t.Errorf("unexpected field errors (-want, +got):\n%s", diff) 21916 } 21917 }) 21918 } 21919 } 21920 21921 // collectResourcePaths traverses the object, computing all the struct paths. 21922 func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String { 21923 if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) { 21924 return sets.NewString(pathStr) 21925 } 21926 21927 paths := sets.NewString() 21928 switch tp.Kind() { 21929 case reflect.Pointer: 21930 paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path).List()...) 21931 case reflect.Struct: 21932 for i := 0; i < tp.NumField(); i++ { 21933 field := tp.Field(i) 21934 paths.Insert(collectResourcePaths(t, skipRecurseList, field.Type, path.Child(field.Name)).List()...) 21935 } 21936 case reflect.Map, reflect.Slice: 21937 paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path.Key("*")).List()...) 21938 case reflect.Interface: 21939 t.Fatalf("unexpected interface{} field %s", path.String()) 21940 default: 21941 // if we hit a primitive type, we're at a leaf 21942 paths.Insert(path.String()) 21943 } 21944 return paths 21945 } 21946 21947 func TestValidateTLSSecret(t *testing.T) { 21948 successCases := map[string]core.Secret{ 21949 "empty certificate chain": { 21950 ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"}, 21951 Data: map[string][]byte{ 21952 core.TLSCertKey: []byte("public key"), 21953 core.TLSPrivateKeyKey: []byte("private key"), 21954 }, 21955 }, 21956 } 21957 for k, v := range successCases { 21958 if errs := ValidateSecret(&v); len(errs) != 0 { 21959 t.Errorf("Expected success for %s, got %v", k, errs) 21960 } 21961 } 21962 errorCases := map[string]struct { 21963 secrets core.Secret 21964 errorType field.ErrorType 21965 errorDetail string 21966 }{ 21967 "missing public key": { 21968 secrets: core.Secret{ 21969 ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"}, 21970 Data: map[string][]byte{ 21971 core.TLSCertKey: []byte("public key"), 21972 }, 21973 }, 21974 errorType: "FieldValueRequired", 21975 }, 21976 "missing private key": { 21977 secrets: core.Secret{ 21978 ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"}, 21979 Data: map[string][]byte{ 21980 core.TLSCertKey: []byte("public key"), 21981 }, 21982 }, 21983 errorType: "FieldValueRequired", 21984 }, 21985 } 21986 for k, v := range errorCases { 21987 if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { 21988 t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs) 21989 } 21990 } 21991 } 21992 21993 func TestValidateLinuxSecurityContext(t *testing.T) { 21994 runAsUser := int64(1) 21995 validLinuxSC := &core.SecurityContext{ 21996 Privileged: utilpointer.Bool(false), 21997 Capabilities: &core.Capabilities{ 21998 Add: []core.Capability{"foo"}, 21999 Drop: []core.Capability{"bar"}, 22000 }, 22001 SELinuxOptions: &core.SELinuxOptions{ 22002 User: "user", 22003 Role: "role", 22004 Type: "type", 22005 Level: "level", 22006 }, 22007 RunAsUser: &runAsUser, 22008 } 22009 invalidLinuxSC := &core.SecurityContext{ 22010 WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")}, 22011 } 22012 cases := map[string]struct { 22013 sc *core.PodSpec 22014 expectErr bool 22015 errorType field.ErrorType 22016 errorDetail string 22017 }{ 22018 "valid SC, linux, no error": { 22019 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: validLinuxSC}}}, 22020 expectErr: false, 22021 }, 22022 "invalid SC, linux, error": { 22023 sc: &core.PodSpec{Containers: []core.Container{{SecurityContext: invalidLinuxSC}}}, 22024 errorType: "FieldValueForbidden", 22025 errorDetail: "windows options cannot be set for a linux pod", 22026 expectErr: true, 22027 }, 22028 } 22029 for k, v := range cases { 22030 t.Run(k, func(t *testing.T) { 22031 errs := validateLinux(v.sc, field.NewPath("field")) 22032 if v.expectErr && len(errs) > 0 { 22033 if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { 22034 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) 22035 } 22036 } else if v.expectErr && len(errs) == 0 { 22037 t.Errorf("Unexpected success") 22038 } 22039 if !v.expectErr && len(errs) != 0 { 22040 t.Errorf("Unexpected error(s): %v", errs) 22041 } 22042 }) 22043 } 22044 } 22045 22046 func TestValidateSecurityContext(t *testing.T) { 22047 runAsUser := int64(1) 22048 fullValidSC := func() *core.SecurityContext { 22049 return &core.SecurityContext{ 22050 Privileged: utilpointer.Bool(false), 22051 Capabilities: &core.Capabilities{ 22052 Add: []core.Capability{"foo"}, 22053 Drop: []core.Capability{"bar"}, 22054 }, 22055 SELinuxOptions: &core.SELinuxOptions{ 22056 User: "user", 22057 Role: "role", 22058 Type: "type", 22059 Level: "level", 22060 }, 22061 RunAsUser: &runAsUser, 22062 } 22063 } 22064 22065 // setup data 22066 allSettings := fullValidSC() 22067 noCaps := fullValidSC() 22068 noCaps.Capabilities = nil 22069 22070 noSELinux := fullValidSC() 22071 noSELinux.SELinuxOptions = nil 22072 22073 noPrivRequest := fullValidSC() 22074 noPrivRequest.Privileged = nil 22075 22076 noRunAsUser := fullValidSC() 22077 noRunAsUser.RunAsUser = nil 22078 22079 successCases := map[string]struct { 22080 sc *core.SecurityContext 22081 }{ 22082 "all settings": {allSettings}, 22083 "no capabilities": {noCaps}, 22084 "no selinux": {noSELinux}, 22085 "no priv request": {noPrivRequest}, 22086 "no run as user": {noRunAsUser}, 22087 } 22088 for k, v := range successCases { 22089 if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) != 0 { 22090 t.Errorf("[%s] Expected success, got %v", k, errs) 22091 } 22092 } 22093 22094 privRequestWithGlobalDeny := fullValidSC() 22095 privRequestWithGlobalDeny.Privileged = utilpointer.Bool(true) 22096 22097 negativeRunAsUser := fullValidSC() 22098 negativeUser := int64(-1) 22099 negativeRunAsUser.RunAsUser = &negativeUser 22100 22101 privWithoutEscalation := fullValidSC() 22102 privWithoutEscalation.Privileged = utilpointer.Bool(true) 22103 privWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false) 22104 22105 capSysAdminWithoutEscalation := fullValidSC() 22106 capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"} 22107 capSysAdminWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false) 22108 22109 errorCases := map[string]struct { 22110 sc *core.SecurityContext 22111 errorType field.ErrorType 22112 errorDetail string 22113 capAllowPriv bool 22114 }{ 22115 "request privileged when capabilities forbids": { 22116 sc: privRequestWithGlobalDeny, 22117 errorType: "FieldValueForbidden", 22118 errorDetail: "disallowed by cluster policy", 22119 }, 22120 "negative RunAsUser": { 22121 sc: negativeRunAsUser, 22122 errorType: "FieldValueInvalid", 22123 errorDetail: "must be between", 22124 }, 22125 "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": { 22126 sc: capSysAdminWithoutEscalation, 22127 errorType: "FieldValueInvalid", 22128 errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN", 22129 }, 22130 "with privileged and allowPrivilegeEscalation false": { 22131 sc: privWithoutEscalation, 22132 errorType: "FieldValueInvalid", 22133 errorDetail: "cannot set `allowPrivilegeEscalation` to false and `privileged` to true", 22134 capAllowPriv: true, 22135 }, 22136 } 22137 for k, v := range errorCases { 22138 capabilities.SetForTests(capabilities.Capabilities{ 22139 AllowPrivileged: v.capAllowPriv, 22140 }) 22141 if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { 22142 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) 22143 } 22144 } 22145 } 22146 22147 func fakeValidSecurityContext(priv bool) *core.SecurityContext { 22148 return &core.SecurityContext{ 22149 Privileged: &priv, 22150 } 22151 } 22152 22153 func TestValidPodLogOptions(t *testing.T) { 22154 now := metav1.Now() 22155 negative := int64(-1) 22156 zero := int64(0) 22157 positive := int64(1) 22158 tests := []struct { 22159 opt core.PodLogOptions 22160 errs int 22161 }{ 22162 {core.PodLogOptions{}, 0}, 22163 {core.PodLogOptions{Previous: true}, 0}, 22164 {core.PodLogOptions{Follow: true}, 0}, 22165 {core.PodLogOptions{TailLines: &zero}, 0}, 22166 {core.PodLogOptions{TailLines: &negative}, 1}, 22167 {core.PodLogOptions{TailLines: &positive}, 0}, 22168 {core.PodLogOptions{LimitBytes: &zero}, 1}, 22169 {core.PodLogOptions{LimitBytes: &negative}, 1}, 22170 {core.PodLogOptions{LimitBytes: &positive}, 0}, 22171 {core.PodLogOptions{SinceSeconds: &negative}, 1}, 22172 {core.PodLogOptions{SinceSeconds: &positive}, 0}, 22173 {core.PodLogOptions{SinceSeconds: &zero}, 1}, 22174 {core.PodLogOptions{SinceTime: &now}, 0}, 22175 } 22176 for i, test := range tests { 22177 errs := ValidatePodLogOptions(&test.opt) 22178 if test.errs != len(errs) { 22179 t.Errorf("%d: Unexpected errors: %v", i, errs) 22180 } 22181 } 22182 } 22183 22184 func TestValidateConfigMap(t *testing.T) { 22185 newConfigMap := func(name, namespace string, data map[string]string, binaryData map[string][]byte) core.ConfigMap { 22186 return core.ConfigMap{ 22187 ObjectMeta: metav1.ObjectMeta{ 22188 Name: name, 22189 Namespace: namespace, 22190 }, 22191 Data: data, 22192 BinaryData: binaryData, 22193 } 22194 } 22195 22196 var ( 22197 validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}, map[string][]byte{"bin": []byte("value")}) 22198 maxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}, nil) 22199 22200 emptyName = newConfigMap("", "validns", nil, nil) 22201 invalidName = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil, nil) 22202 emptyNs = newConfigMap("validname", "", nil, nil) 22203 invalidNs = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, nil) 22204 invalidKey = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}, nil) 22205 leadingDotKey = newConfigMap("validname", "validns", map[string]string{".ab": "value"}, nil) 22206 dotKey = newConfigMap("validname", "validns", map[string]string{".": "value"}, nil) 22207 doubleDotKey = newConfigMap("validname", "validns", map[string]string{"..": "value"}, nil) 22208 overMaxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}, nil) 22209 overMaxSize = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", v1.MaxSecretSize+1)}, nil) 22210 duplicatedKey = newConfigMap("validname", "validns", map[string]string{"key": "value1"}, map[string][]byte{"key": []byte("value2")}) 22211 binDataInvalidKey = newConfigMap("validname", "validns", nil, map[string][]byte{"a*b": []byte("value")}) 22212 binDataLeadingDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{".ab": []byte("value")}) 22213 binDataDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{".": []byte("value")}) 22214 binDataDoubleDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{"..": []byte("value")}) 22215 binDataOverMaxKeyLength = newConfigMap("validname", "validns", nil, map[string][]byte{strings.Repeat("a", 254): []byte("value")}) 22216 binDataOverMaxSize = newConfigMap("validname", "validns", nil, map[string][]byte{"bin": bytes.Repeat([]byte("a"), v1.MaxSecretSize+1)}) 22217 binNonUtf8Value = newConfigMap("validname", "validns", nil, map[string][]byte{"key": {0, 0xFE, 0, 0xFF}}) 22218 ) 22219 22220 tests := map[string]struct { 22221 cfg core.ConfigMap 22222 isValid bool 22223 }{ 22224 "valid": {validConfigMap, true}, 22225 "max key length": {maxKeyLength, true}, 22226 "leading dot key": {leadingDotKey, true}, 22227 "empty name": {emptyName, false}, 22228 "invalid name": {invalidName, false}, 22229 "invalid key": {invalidKey, false}, 22230 "empty namespace": {emptyNs, false}, 22231 "invalid namespace": {invalidNs, false}, 22232 "dot key": {dotKey, false}, 22233 "double dot key": {doubleDotKey, false}, 22234 "over max key length": {overMaxKeyLength, false}, 22235 "over max size": {overMaxSize, false}, 22236 "duplicated key": {duplicatedKey, false}, 22237 "binary data invalid key": {binDataInvalidKey, false}, 22238 "binary data leading dot key": {binDataLeadingDotKey, true}, 22239 "binary data dot key": {binDataDotKey, false}, 22240 "binary data double dot key": {binDataDoubleDotKey, false}, 22241 "binary data over max key length": {binDataOverMaxKeyLength, false}, 22242 "binary data max size": {binDataOverMaxSize, false}, 22243 "binary data non utf-8 bytes": {binNonUtf8Value, true}, 22244 } 22245 22246 for name, tc := range tests { 22247 errs := ValidateConfigMap(&tc.cfg) 22248 if tc.isValid && len(errs) > 0 { 22249 t.Errorf("%v: unexpected error: %v", name, errs) 22250 } 22251 if !tc.isValid && len(errs) == 0 { 22252 t.Errorf("%v: unexpected non-error", name) 22253 } 22254 } 22255 } 22256 22257 func TestValidateConfigMapUpdate(t *testing.T) { 22258 newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap { 22259 return core.ConfigMap{ 22260 ObjectMeta: metav1.ObjectMeta{ 22261 Name: name, 22262 Namespace: namespace, 22263 ResourceVersion: version, 22264 }, 22265 Data: data, 22266 } 22267 } 22268 validConfigMap := func() core.ConfigMap { 22269 return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"}) 22270 } 22271 22272 falseVal := false 22273 trueVal := true 22274 22275 configMap := validConfigMap() 22276 immutableConfigMap := validConfigMap() 22277 immutableConfigMap.Immutable = &trueVal 22278 mutableConfigMap := validConfigMap() 22279 mutableConfigMap.Immutable = &falseVal 22280 22281 configMapWithData := validConfigMap() 22282 configMapWithData.Data["key-2"] = "value-2" 22283 immutableConfigMapWithData := validConfigMap() 22284 immutableConfigMapWithData.Immutable = &trueVal 22285 immutableConfigMapWithData.Data["key-2"] = "value-2" 22286 22287 configMapWithChangedData := validConfigMap() 22288 configMapWithChangedData.Data["key"] = "foo" 22289 immutableConfigMapWithChangedData := validConfigMap() 22290 immutableConfigMapWithChangedData.Immutable = &trueVal 22291 immutableConfigMapWithChangedData.Data["key"] = "foo" 22292 22293 noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"}) 22294 22295 cases := []struct { 22296 name string 22297 newCfg core.ConfigMap 22298 oldCfg core.ConfigMap 22299 valid bool 22300 }{{ 22301 name: "valid", 22302 newCfg: configMap, 22303 oldCfg: configMap, 22304 valid: true, 22305 }, { 22306 name: "invalid", 22307 newCfg: noVersion, 22308 oldCfg: configMap, 22309 valid: false, 22310 }, { 22311 name: "mark configmap immutable", 22312 oldCfg: configMap, 22313 newCfg: immutableConfigMap, 22314 valid: true, 22315 }, { 22316 name: "revert immutable configmap", 22317 oldCfg: immutableConfigMap, 22318 newCfg: configMap, 22319 valid: false, 22320 }, { 22321 name: "mark immutable configmap mutable", 22322 oldCfg: immutableConfigMap, 22323 newCfg: mutableConfigMap, 22324 valid: false, 22325 }, { 22326 name: "add data in configmap", 22327 oldCfg: configMap, 22328 newCfg: configMapWithData, 22329 valid: true, 22330 }, { 22331 name: "add data in immutable configmap", 22332 oldCfg: immutableConfigMap, 22333 newCfg: immutableConfigMapWithData, 22334 valid: false, 22335 }, { 22336 name: "change data in configmap", 22337 oldCfg: configMap, 22338 newCfg: configMapWithChangedData, 22339 valid: true, 22340 }, { 22341 name: "change data in immutable configmap", 22342 oldCfg: immutableConfigMap, 22343 newCfg: immutableConfigMapWithChangedData, 22344 valid: false, 22345 }, 22346 } 22347 22348 for _, tc := range cases { 22349 t.Run(tc.name, func(t *testing.T) { 22350 errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg) 22351 if tc.valid && len(errs) > 0 { 22352 t.Errorf("Unexpected error: %v", errs) 22353 } 22354 if !tc.valid && len(errs) == 0 { 22355 t.Errorf("Unexpected lack of error") 22356 } 22357 }) 22358 } 22359 } 22360 22361 func TestValidateHasLabel(t *testing.T) { 22362 successCase := metav1.ObjectMeta{ 22363 Name: "123", 22364 Namespace: "ns", 22365 Labels: map[string]string{ 22366 "other": "blah", 22367 "foo": "bar", 22368 }, 22369 } 22370 if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 { 22371 t.Errorf("expected success: %v", errs) 22372 } 22373 22374 missingCase := metav1.ObjectMeta{ 22375 Name: "123", 22376 Namespace: "ns", 22377 Labels: map[string]string{ 22378 "other": "blah", 22379 }, 22380 } 22381 if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { 22382 t.Errorf("expected failure") 22383 } 22384 22385 wrongValueCase := metav1.ObjectMeta{ 22386 Name: "123", 22387 Namespace: "ns", 22388 Labels: map[string]string{ 22389 "other": "blah", 22390 "foo": "notbar", 22391 }, 22392 } 22393 if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { 22394 t.Errorf("expected failure") 22395 } 22396 } 22397 22398 func TestIsValidSysctlName(t *testing.T) { 22399 valid := []string{ 22400 "a.b.c.d", 22401 "a", 22402 "a_b", 22403 "a-b", 22404 "abc", 22405 "abc.def", 22406 "a/b/c/d", 22407 "a/b.c", 22408 } 22409 invalid := []string{ 22410 "", 22411 "*", 22412 "ä", 22413 "a_", 22414 "_", 22415 "__", 22416 "_a", 22417 "_a._b", 22418 "-", 22419 ".", 22420 "a.", 22421 ".a", 22422 "a.b.", 22423 "a*.b", 22424 "a*b", 22425 "*a", 22426 "a.*", 22427 "*", 22428 "abc*", 22429 "a.abc*", 22430 "a.b.*", 22431 "Abc", 22432 "/", 22433 "/a", 22434 "a/abc*", 22435 "a/b/*", 22436 func(n int) string { 22437 x := make([]byte, n) 22438 for i := range x { 22439 x[i] = byte('a') 22440 } 22441 return string(x) 22442 }(256), 22443 } 22444 22445 for _, s := range valid { 22446 if !IsValidSysctlName(s) { 22447 t.Errorf("%q expected to be a valid sysctl name", s) 22448 } 22449 } 22450 for _, s := range invalid { 22451 if IsValidSysctlName(s) { 22452 t.Errorf("%q expected to be an invalid sysctl name", s) 22453 } 22454 } 22455 } 22456 22457 func TestValidateSysctls(t *testing.T) { 22458 valid := []string{ 22459 "net.foo.bar", 22460 "kernel.shmmax", 22461 "net.ipv4.conf.enp3s0/200.forwarding", 22462 "net/ipv4/conf/enp3s0.200/forwarding", 22463 } 22464 invalid := []string{ 22465 "i..nvalid", 22466 "_invalid", 22467 } 22468 22469 invalidWithHostNet := []string{ 22470 "net.ipv4.conf.enp3s0/200.forwarding", 22471 "net/ipv4/conf/enp3s0.200/forwarding", 22472 } 22473 22474 invalidWithHostIPC := []string{ 22475 "kernel.shmmax", 22476 "kernel.msgmax", 22477 } 22478 22479 duplicates := []string{ 22480 "kernel.shmmax", 22481 "kernel.shmmax", 22482 } 22483 opts := PodValidationOptions{ 22484 AllowNamespacedSysctlsForHostNetAndHostIPC: false, 22485 } 22486 22487 sysctls := make([]core.Sysctl, len(valid)) 22488 validSecurityContext := &core.PodSecurityContext{ 22489 Sysctls: sysctls, 22490 } 22491 for i, sysctl := range valid { 22492 sysctls[i].Name = sysctl 22493 } 22494 errs := validateSysctls(validSecurityContext, field.NewPath("foo"), opts) 22495 if len(errs) != 0 { 22496 t.Errorf("unexpected validation errors: %v", errs) 22497 } 22498 22499 sysctls = make([]core.Sysctl, len(invalid)) 22500 for i, sysctl := range invalid { 22501 sysctls[i].Name = sysctl 22502 } 22503 inValidSecurityContext := &core.PodSecurityContext{ 22504 Sysctls: sysctls, 22505 } 22506 errs = validateSysctls(inValidSecurityContext, field.NewPath("foo"), opts) 22507 if len(errs) != 2 { 22508 t.Errorf("expected 2 validation errors. Got: %v", errs) 22509 } else { 22510 if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) { 22511 t.Errorf("unexpected errors: expected=%q, got=%q", expected, got) 22512 } 22513 if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) { 22514 t.Errorf("unexpected errors: expected=%q, got=%q", expected, got) 22515 } 22516 } 22517 22518 sysctls = make([]core.Sysctl, len(duplicates)) 22519 for i, sysctl := range duplicates { 22520 sysctls[i].Name = sysctl 22521 } 22522 securityContextWithDup := &core.PodSecurityContext{ 22523 Sysctls: sysctls, 22524 } 22525 errs = validateSysctls(securityContextWithDup, field.NewPath("foo"), opts) 22526 if len(errs) != 1 { 22527 t.Errorf("unexpected validation errors: %v", errs) 22528 } else if errs[0].Type != field.ErrorTypeDuplicate { 22529 t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type) 22530 } 22531 22532 sysctls = make([]core.Sysctl, len(invalidWithHostNet)) 22533 for i, sysctl := range invalidWithHostNet { 22534 sysctls[i].Name = sysctl 22535 } 22536 invalidSecurityContextWithHostNet := &core.PodSecurityContext{ 22537 Sysctls: sysctls, 22538 HostIPC: false, 22539 HostNetwork: true, 22540 } 22541 errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts) 22542 if len(errs) != 2 { 22543 t.Errorf("unexpected validation errors: %v", errs) 22544 } 22545 opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true 22546 errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts) 22547 if len(errs) != 0 { 22548 t.Errorf("unexpected validation errors: %v", errs) 22549 } 22550 22551 sysctls = make([]core.Sysctl, len(invalidWithHostIPC)) 22552 for i, sysctl := range invalidWithHostIPC { 22553 sysctls[i].Name = sysctl 22554 } 22555 invalidSecurityContextWithHostIPC := &core.PodSecurityContext{ 22556 Sysctls: sysctls, 22557 HostIPC: true, 22558 HostNetwork: false, 22559 } 22560 opts.AllowNamespacedSysctlsForHostNetAndHostIPC = false 22561 errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts) 22562 if len(errs) != 2 { 22563 t.Errorf("unexpected validation errors: %v", errs) 22564 } 22565 opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true 22566 errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts) 22567 if len(errs) != 0 { 22568 t.Errorf("unexpected validation errors: %v", errs) 22569 } 22570 } 22571 22572 func newNodeNameEndpoint(nodeName string) *core.Endpoints { 22573 ep := &core.Endpoints{ 22574 ObjectMeta: metav1.ObjectMeta{ 22575 Name: "foo", 22576 Namespace: metav1.NamespaceDefault, 22577 ResourceVersion: "1", 22578 }, 22579 Subsets: []core.EndpointSubset{{ 22580 NotReadyAddresses: []core.EndpointAddress{}, 22581 Ports: []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}}, 22582 Addresses: []core.EndpointAddress{{ 22583 IP: "8.8.8.8", 22584 Hostname: "zookeeper1", 22585 NodeName: &nodeName}}}}} 22586 return ep 22587 } 22588 22589 func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) { 22590 oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend") 22591 updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename") 22592 // Check that NodeName can be changed during update, this is to accommodate the case where nodeIP or PodCIDR is reused. 22593 // The same ip will now have a different nodeName. 22594 errList := ValidateEndpoints(updatedEndpoint) 22595 errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...) 22596 if len(errList) != 0 { 22597 t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update") 22598 } 22599 } 22600 22601 func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) { 22602 // Check NodeName DNS validation 22603 endpoint := newNodeNameEndpoint("illegal*.nodename") 22604 errList := ValidateEndpoints(endpoint) 22605 if len(errList) == 0 { 22606 t.Error("Endpoint should reject invalid NodeName") 22607 } 22608 } 22609 22610 func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) { 22611 endpoint := newNodeNameEndpoint("10.10.1.1") 22612 errList := ValidateEndpoints(endpoint) 22613 if len(errList) != 0 { 22614 t.Error("Endpoint should accept a NodeName that is an IP address") 22615 } 22616 } 22617 22618 func TestValidateFlexVolumeSource(t *testing.T) { 22619 testcases := map[string]struct { 22620 source *core.FlexVolumeSource 22621 expectedErrs map[string]string 22622 }{ 22623 "valid": { 22624 source: &core.FlexVolumeSource{Driver: "foo"}, 22625 expectedErrs: map[string]string{}, 22626 }, 22627 "valid with options": { 22628 source: &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}}, 22629 expectedErrs: map[string]string{}, 22630 }, 22631 "no driver": { 22632 source: &core.FlexVolumeSource{Driver: ""}, 22633 expectedErrs: map[string]string{"driver": "Required value"}, 22634 }, 22635 "reserved option keys": { 22636 source: &core.FlexVolumeSource{ 22637 Driver: "foo", 22638 Options: map[string]string{ 22639 // valid options 22640 "myns.io": "A", 22641 "myns.io/bar": "A", 22642 "myns.io/kubernetes.io": "A", 22643 22644 // invalid options 22645 "KUBERNETES.IO": "A", 22646 "kubernetes.io": "A", 22647 "kubernetes.io/": "A", 22648 "kubernetes.io/foo": "A", 22649 22650 "alpha.kubernetes.io": "A", 22651 "alpha.kubernetes.io/": "A", 22652 "alpha.kubernetes.io/foo": "A", 22653 22654 "k8s.io": "A", 22655 "k8s.io/": "A", 22656 "k8s.io/foo": "A", 22657 22658 "alpha.k8s.io": "A", 22659 "alpha.k8s.io/": "A", 22660 "alpha.k8s.io/foo": "A", 22661 }, 22662 }, 22663 expectedErrs: map[string]string{ 22664 "options[KUBERNETES.IO]": "reserved", 22665 "options[kubernetes.io]": "reserved", 22666 "options[kubernetes.io/]": "reserved", 22667 "options[kubernetes.io/foo]": "reserved", 22668 "options[alpha.kubernetes.io]": "reserved", 22669 "options[alpha.kubernetes.io/]": "reserved", 22670 "options[alpha.kubernetes.io/foo]": "reserved", 22671 "options[k8s.io]": "reserved", 22672 "options[k8s.io/]": "reserved", 22673 "options[k8s.io/foo]": "reserved", 22674 "options[alpha.k8s.io]": "reserved", 22675 "options[alpha.k8s.io/]": "reserved", 22676 "options[alpha.k8s.io/foo]": "reserved", 22677 }, 22678 }, 22679 } 22680 22681 for k, tc := range testcases { 22682 errs := validateFlexVolumeSource(tc.source, nil) 22683 for _, err := range errs { 22684 expectedErr, ok := tc.expectedErrs[err.Field] 22685 if !ok { 22686 t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err) 22687 continue 22688 } 22689 if !strings.Contains(err.Error(), expectedErr) { 22690 t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error()) 22691 continue 22692 } 22693 } 22694 if len(errs) != len(tc.expectedErrs) { 22695 t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs) 22696 continue 22697 } 22698 } 22699 } 22700 22701 func TestValidateOrSetClientIPAffinityConfig(t *testing.T) { 22702 successCases := map[string]*core.SessionAffinityConfig{ 22703 "non-empty config, valid timeout: 1": { 22704 ClientIP: &core.ClientIPConfig{ 22705 TimeoutSeconds: utilpointer.Int32(1), 22706 }, 22707 }, 22708 "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": { 22709 ClientIP: &core.ClientIPConfig{ 22710 TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds - 1), 22711 }, 22712 }, 22713 "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": { 22714 ClientIP: &core.ClientIPConfig{ 22715 TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds), 22716 }, 22717 }, 22718 } 22719 22720 for name, test := range successCases { 22721 if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 { 22722 t.Errorf("case: %s, expected success: %v", name, errs) 22723 } 22724 } 22725 22726 errorCases := map[string]*core.SessionAffinityConfig{ 22727 "empty session affinity config": nil, 22728 "empty client IP config": { 22729 ClientIP: nil, 22730 }, 22731 "empty timeoutSeconds": { 22732 ClientIP: &core.ClientIPConfig{ 22733 TimeoutSeconds: nil, 22734 }, 22735 }, 22736 "non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": { 22737 ClientIP: &core.ClientIPConfig{ 22738 TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds + 1), 22739 }, 22740 }, 22741 "non-empty config, invalid timeout: -1": { 22742 ClientIP: &core.ClientIPConfig{ 22743 TimeoutSeconds: utilpointer.Int32(-1), 22744 }, 22745 }, 22746 "non-empty config, invalid timeout: 0": { 22747 ClientIP: &core.ClientIPConfig{ 22748 TimeoutSeconds: utilpointer.Int32(0), 22749 }, 22750 }, 22751 } 22752 22753 for name, test := range errorCases { 22754 if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 { 22755 t.Errorf("case: %v, expected failures: %v", name, errs) 22756 } 22757 } 22758 } 22759 22760 func TestValidateWindowsSecurityContextOptions(t *testing.T) { 22761 toPtr := func(s string) *string { 22762 return &s 22763 } 22764 22765 testCases := []struct { 22766 testName string 22767 22768 windowsOptions *core.WindowsSecurityContextOptions 22769 expectedErrorSubstring string 22770 }{{ 22771 testName: "a nil pointer", 22772 }, { 22773 testName: "an empty struct", 22774 windowsOptions: &core.WindowsSecurityContextOptions{}, 22775 }, { 22776 testName: "a valid input", 22777 windowsOptions: &core.WindowsSecurityContextOptions{ 22778 GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"), 22779 GMSACredentialSpec: toPtr("dummy-gmsa-crep-spec-contents"), 22780 }, 22781 }, { 22782 testName: "a GMSA cred spec name that is not a valid resource name", 22783 windowsOptions: &core.WindowsSecurityContextOptions{ 22784 // invalid because of the underscore 22785 GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"), 22786 }, 22787 expectedErrorSubstring: dnsSubdomainLabelErrMsg, 22788 }, { 22789 testName: "empty GMSA cred spec contents", 22790 windowsOptions: &core.WindowsSecurityContextOptions{ 22791 GMSACredentialSpec: toPtr(""), 22792 }, 22793 expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string", 22794 }, { 22795 testName: "GMSA cred spec contents that are too long", 22796 windowsOptions: &core.WindowsSecurityContextOptions{ 22797 GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)), 22798 }, 22799 expectedErrorSubstring: "gmsaCredentialSpec size must be under", 22800 }, { 22801 testName: "RunAsUserName is nil", 22802 windowsOptions: &core.WindowsSecurityContextOptions{ 22803 RunAsUserName: nil, 22804 }, 22805 }, { 22806 testName: "a valid RunAsUserName", 22807 windowsOptions: &core.WindowsSecurityContextOptions{ 22808 RunAsUserName: toPtr("Container. User"), 22809 }, 22810 }, { 22811 testName: "a valid RunAsUserName with NetBios Domain", 22812 windowsOptions: &core.WindowsSecurityContextOptions{ 22813 RunAsUserName: toPtr("Network Service\\Container. User"), 22814 }, 22815 }, { 22816 testName: "a valid RunAsUserName with DNS Domain", 22817 windowsOptions: &core.WindowsSecurityContextOptions{ 22818 RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"), 22819 }, 22820 }, { 22821 testName: "a valid RunAsUserName with DNS Domain with a single character segment", 22822 windowsOptions: &core.WindowsSecurityContextOptions{ 22823 RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"), 22824 }, 22825 }, { 22826 testName: "a valid RunAsUserName with a long single segment DNS Domain", 22827 windowsOptions: &core.WindowsSecurityContextOptions{ 22828 RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"), 22829 }, 22830 }, { 22831 testName: "an empty RunAsUserName", 22832 windowsOptions: &core.WindowsSecurityContextOptions{ 22833 RunAsUserName: toPtr(""), 22834 }, 22835 expectedErrorSubstring: "runAsUserName cannot be an empty string", 22836 }, { 22837 testName: "RunAsUserName containing a control character", 22838 windowsOptions: &core.WindowsSecurityContextOptions{ 22839 RunAsUserName: toPtr("Container\tUser"), 22840 }, 22841 expectedErrorSubstring: "runAsUserName cannot contain control characters", 22842 }, { 22843 testName: "RunAsUserName containing too many backslashes", 22844 windowsOptions: &core.WindowsSecurityContextOptions{ 22845 RunAsUserName: toPtr("Container\\Foo\\Lish"), 22846 }, 22847 expectedErrorSubstring: "runAsUserName cannot contain more than one backslash", 22848 }, { 22849 testName: "RunAsUserName containing backslash but empty Domain", 22850 windowsOptions: &core.WindowsSecurityContextOptions{ 22851 RunAsUserName: toPtr("\\User"), 22852 }, 22853 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format", 22854 }, { 22855 testName: "RunAsUserName containing backslash but empty User", 22856 windowsOptions: &core.WindowsSecurityContextOptions{ 22857 RunAsUserName: toPtr("Container\\"), 22858 }, 22859 expectedErrorSubstring: "runAsUserName's User cannot be empty", 22860 }, { 22861 testName: "RunAsUserName's NetBios Domain is too long", 22862 windowsOptions: &core.WindowsSecurityContextOptions{ 22863 RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"), 22864 }, 22865 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", 22866 }, { 22867 testName: "RunAsUserName's DNS Domain is too long", 22868 windowsOptions: &core.WindowsSecurityContextOptions{ 22869 // even if this tests the max Domain length, the Domain should still be "valid". 22870 RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"), 22871 }, 22872 expectedErrorSubstring: "runAsUserName's Domain length must be under", 22873 }, { 22874 testName: "RunAsUserName's User is too long", 22875 windowsOptions: &core.WindowsSecurityContextOptions{ 22876 RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)), 22877 }, 22878 expectedErrorSubstring: "runAsUserName's User length must not be longer than", 22879 }, { 22880 testName: "RunAsUserName's User cannot contain only spaces or periods", 22881 windowsOptions: &core.WindowsSecurityContextOptions{ 22882 RunAsUserName: toPtr("... ..."), 22883 }, 22884 expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces", 22885 }, { 22886 testName: "RunAsUserName's NetBios Domain cannot start with a dot", 22887 windowsOptions: &core.WindowsSecurityContextOptions{ 22888 RunAsUserName: toPtr(".FooLish\\User"), 22889 }, 22890 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", 22891 }, { 22892 testName: "RunAsUserName's NetBios Domain cannot contain invalid characters", 22893 windowsOptions: &core.WindowsSecurityContextOptions{ 22894 RunAsUserName: toPtr("Foo? Lish?\\User"), 22895 }, 22896 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios", 22897 }, { 22898 testName: "RunAsUserName's DNS Domain cannot contain invalid characters", 22899 windowsOptions: &core.WindowsSecurityContextOptions{ 22900 RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"), 22901 }, 22902 expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format", 22903 }, { 22904 testName: "RunAsUserName's User cannot contain invalid characters", 22905 windowsOptions: &core.WindowsSecurityContextOptions{ 22906 RunAsUserName: toPtr("Container/User"), 22907 }, 22908 expectedErrorSubstring: "runAsUserName's User cannot contain the following characters", 22909 }, 22910 } 22911 22912 for _, testCase := range testCases { 22913 t.Run("validateWindowsSecurityContextOptions with"+testCase.testName, func(t *testing.T) { 22914 errs := validateWindowsSecurityContextOptions(testCase.windowsOptions, field.NewPath("field")) 22915 22916 switch len(errs) { 22917 case 0: 22918 if testCase.expectedErrorSubstring != "" { 22919 t.Errorf("expected a failure containing the substring: %q", testCase.expectedErrorSubstring) 22920 } 22921 case 1: 22922 if testCase.expectedErrorSubstring == "" { 22923 t.Errorf("didn't expect a failure, got: %q", errs[0].Error()) 22924 } else if !strings.Contains(errs[0].Error(), testCase.expectedErrorSubstring) { 22925 t.Errorf("expected a failure with the substring %q, got %q instead", testCase.expectedErrorSubstring, errs[0].Error()) 22926 } 22927 default: 22928 t.Errorf("got %d failures", len(errs)) 22929 for i, err := range errs { 22930 t.Errorf("error %d: %q", i, err.Error()) 22931 } 22932 } 22933 }) 22934 } 22935 } 22936 22937 func testDataSourceInSpec(name, kind, apiGroup string) *core.PersistentVolumeClaimSpec { 22938 scName := "csi-plugin" 22939 dataSourceInSpec := core.PersistentVolumeClaimSpec{ 22940 AccessModes: []core.PersistentVolumeAccessMode{ 22941 core.ReadOnlyMany, 22942 }, 22943 Resources: core.VolumeResourceRequirements{ 22944 Requests: core.ResourceList{ 22945 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 22946 }, 22947 }, 22948 StorageClassName: &scName, 22949 DataSource: &core.TypedLocalObjectReference{ 22950 APIGroup: &apiGroup, 22951 Kind: kind, 22952 Name: name, 22953 }, 22954 } 22955 22956 return &dataSourceInSpec 22957 } 22958 22959 func TestAlphaVolumePVCDataSource(t *testing.T) { 22960 testCases := []struct { 22961 testName string 22962 claimSpec core.PersistentVolumeClaimSpec 22963 expectedFail bool 22964 }{{ 22965 testName: "test create from valid snapshot source", 22966 claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), 22967 }, { 22968 testName: "test create from valid pvc source", 22969 claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""), 22970 }, { 22971 testName: "test missing name in snapshot datasource should fail", 22972 claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), 22973 expectedFail: true, 22974 }, { 22975 testName: "test missing kind in snapshot datasource should fail", 22976 claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), 22977 expectedFail: true, 22978 }, { 22979 testName: "test create from valid generic custom resource source", 22980 claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"), 22981 }, { 22982 testName: "test invalid datasource should fail", 22983 claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""), 22984 expectedFail: true, 22985 }, 22986 } 22987 22988 for _, tc := range testCases { 22989 opts := PersistentVolumeClaimSpecValidationOptions{} 22990 if tc.expectedFail { 22991 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 { 22992 t.Errorf("expected failure: %v", errs) 22993 } 22994 22995 } else { 22996 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 { 22997 t.Errorf("expected success: %v", errs) 22998 } 22999 } 23000 } 23001 } 23002 23003 func testAnyDataSource(t *testing.T, ds, dsRef bool) { 23004 testCases := []struct { 23005 testName string 23006 claimSpec core.PersistentVolumeClaimSpec 23007 expectedFail bool 23008 }{{ 23009 testName: "test create from valid snapshot source", 23010 claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"), 23011 }, { 23012 testName: "test create from valid pvc source", 23013 claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""), 23014 }, { 23015 testName: "test missing name in snapshot datasource should fail", 23016 claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"), 23017 expectedFail: true, 23018 }, { 23019 testName: "test missing kind in snapshot datasource should fail", 23020 claimSpec: *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"), 23021 expectedFail: true, 23022 }, { 23023 testName: "test create from valid generic custom resource source", 23024 claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"), 23025 }, { 23026 testName: "test invalid datasource should fail", 23027 claimSpec: *testDataSourceInSpec("test_pod", "Pod", ""), 23028 expectedFail: true, 23029 }, 23030 } 23031 23032 for _, tc := range testCases { 23033 if dsRef { 23034 tc.claimSpec.DataSourceRef = &core.TypedObjectReference{ 23035 APIGroup: tc.claimSpec.DataSource.APIGroup, 23036 Kind: tc.claimSpec.DataSource.Kind, 23037 Name: tc.claimSpec.DataSource.Name, 23038 } 23039 } 23040 if !ds { 23041 tc.claimSpec.DataSource = nil 23042 } 23043 opts := PersistentVolumeClaimSpecValidationOptions{} 23044 if tc.expectedFail { 23045 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 { 23046 t.Errorf("expected failure: %v", errs) 23047 } 23048 } else { 23049 if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 { 23050 t.Errorf("expected success: %v", errs) 23051 } 23052 } 23053 } 23054 } 23055 23056 func TestAnyDataSource(t *testing.T) { 23057 testAnyDataSource(t, true, false) 23058 testAnyDataSource(t, false, true) 23059 testAnyDataSource(t, true, false) 23060 } 23061 23062 func pvcSpecWithCrossNamespaceSource(apiGroup *string, kind string, namespace *string, name string, isDataSourceSet bool) *core.PersistentVolumeClaimSpec { 23063 scName := "csi-plugin" 23064 spec := core.PersistentVolumeClaimSpec{ 23065 AccessModes: []core.PersistentVolumeAccessMode{ 23066 core.ReadOnlyMany, 23067 }, 23068 Resources: core.VolumeResourceRequirements{ 23069 Requests: core.ResourceList{ 23070 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 23071 }, 23072 }, 23073 StorageClassName: &scName, 23074 DataSourceRef: &core.TypedObjectReference{ 23075 APIGroup: apiGroup, 23076 Kind: kind, 23077 Namespace: namespace, 23078 Name: name, 23079 }, 23080 } 23081 23082 if isDataSourceSet { 23083 spec.DataSource = &core.TypedLocalObjectReference{ 23084 APIGroup: apiGroup, 23085 Kind: kind, 23086 Name: name, 23087 } 23088 } 23089 return &spec 23090 } 23091 23092 func TestCrossNamespaceSource(t *testing.T) { 23093 snapAPIGroup := "snapshot.storage.k8s.io" 23094 coreAPIGroup := "" 23095 unsupportedAPIGroup := "unsupported.example.com" 23096 snapKind := "VolumeSnapshot" 23097 pvcKind := "PersistentVolumeClaim" 23098 goodNS := "ns1" 23099 badNS := "a*b" 23100 emptyNS := "" 23101 goodName := "snapshot1" 23102 23103 testCases := []struct { 23104 testName string 23105 expectedFail bool 23106 claimSpec *core.PersistentVolumeClaimSpec 23107 }{{ 23108 testName: "Feature gate enabled and valid xns DataSourceRef specified", 23109 expectedFail: false, 23110 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false), 23111 }, { 23112 testName: "Feature gate enabled and xns DataSourceRef with PVC source specified", 23113 expectedFail: false, 23114 claimSpec: pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false), 23115 }, { 23116 testName: "Feature gate enabled and xns DataSourceRef with unsupported source specified", 23117 expectedFail: false, 23118 claimSpec: pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false), 23119 }, { 23120 testName: "Feature gate enabled and xns DataSourceRef with nil apiGroup", 23121 expectedFail: true, 23122 claimSpec: pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false), 23123 }, { 23124 testName: "Feature gate enabled and xns DataSourceRef with invalid namspace specified", 23125 expectedFail: true, 23126 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false), 23127 }, { 23128 testName: "Feature gate enabled and xns DataSourceRef with nil namspace specified", 23129 expectedFail: false, 23130 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false), 23131 }, { 23132 testName: "Feature gate enabled and xns DataSourceRef with empty namspace specified", 23133 expectedFail: false, 23134 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false), 23135 }, { 23136 testName: "Feature gate enabled and both xns DataSourceRef and DataSource specified", 23137 expectedFail: true, 23138 claimSpec: pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true), 23139 }, 23140 } 23141 23142 for _, tc := range testCases { 23143 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)() 23144 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)() 23145 opts := PersistentVolumeClaimSpecValidationOptions{} 23146 if tc.expectedFail { 23147 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 { 23148 t.Errorf("%s: expected failure: %v", tc.testName, errs) 23149 } 23150 } else { 23151 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 { 23152 t.Errorf("%s: expected success: %v", tc.testName, errs) 23153 } 23154 } 23155 } 23156 } 23157 23158 func pvcSpecWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimSpec { 23159 scName := "csi-plugin" 23160 spec := core.PersistentVolumeClaimSpec{ 23161 AccessModes: []core.PersistentVolumeAccessMode{ 23162 core.ReadOnlyMany, 23163 }, 23164 Resources: core.VolumeResourceRequirements{ 23165 Requests: core.ResourceList{ 23166 core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), 23167 }, 23168 }, 23169 StorageClassName: &scName, 23170 VolumeAttributesClassName: vacName, 23171 } 23172 return &spec 23173 } 23174 23175 func TestVolumeAttributesClass(t *testing.T) { 23176 testCases := []struct { 23177 testName string 23178 expectedFail bool 23179 enableVolumeAttributesClass bool 23180 claimSpec *core.PersistentVolumeClaimSpec 23181 }{ 23182 { 23183 testName: "Feature gate enabled and valid no volumeAttributesClassName specified", 23184 expectedFail: false, 23185 enableVolumeAttributesClass: true, 23186 claimSpec: pvcSpecWithVolumeAttributesClassName(nil), 23187 }, 23188 { 23189 testName: "Feature gate enabled and an empty volumeAttributesClassName specified", 23190 expectedFail: false, 23191 enableVolumeAttributesClass: true, 23192 claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("")), 23193 }, 23194 { 23195 testName: "Feature gate enabled and valid volumeAttributesClassName specified", 23196 expectedFail: false, 23197 enableVolumeAttributesClass: true, 23198 claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("foo")), 23199 }, 23200 { 23201 testName: "Feature gate enabled and invalid volumeAttributesClassName specified", 23202 expectedFail: true, 23203 enableVolumeAttributesClass: true, 23204 claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("-invalid-")), 23205 }, 23206 } 23207 for _, tc := range testCases { 23208 opts := PersistentVolumeClaimSpecValidationOptions{ 23209 EnableVolumeAttributesClass: tc.enableVolumeAttributesClass, 23210 } 23211 if tc.expectedFail { 23212 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 { 23213 t.Errorf("%s: expected failure: %v", tc.testName, errs) 23214 } 23215 } else { 23216 if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 { 23217 t.Errorf("%s: expected success: %v", tc.testName, errs) 23218 } 23219 } 23220 } 23221 } 23222 23223 func TestValidateTopologySpreadConstraints(t *testing.T) { 23224 fieldPath := field.NewPath("field") 23225 subFldPath0 := fieldPath.Index(0) 23226 fieldPathMinDomains := subFldPath0.Child("minDomains") 23227 fieldPathMaxSkew := subFldPath0.Child("maxSkew") 23228 fieldPathTopologyKey := subFldPath0.Child("topologyKey") 23229 fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable") 23230 fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}") 23231 fieldPathMatchLabelKeys := subFldPath0.Child("matchLabelKeys") 23232 nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy") 23233 nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy") 23234 labelSelectorField := subFldPath0.Child("labelSelector") 23235 unknown := core.NodeInclusionPolicy("Unknown") 23236 ignore := core.NodeInclusionPolicyIgnore 23237 honor := core.NodeInclusionPolicyHonor 23238 23239 testCases := []struct { 23240 name string 23241 constraints []core.TopologySpreadConstraint 23242 wantFieldErrors field.ErrorList 23243 opts PodValidationOptions 23244 }{{ 23245 name: "all required fields ok", 23246 constraints: []core.TopologySpreadConstraint{{ 23247 MaxSkew: 1, 23248 TopologyKey: "k8s.io/zone", 23249 WhenUnsatisfiable: core.DoNotSchedule, 23250 MinDomains: utilpointer.Int32(3), 23251 }}, 23252 wantFieldErrors: field.ErrorList{}, 23253 }, { 23254 name: "missing MaxSkew", 23255 constraints: []core.TopologySpreadConstraint{ 23256 {TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, 23257 }, 23258 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)}, 23259 }, { 23260 name: "negative MaxSkew", 23261 constraints: []core.TopologySpreadConstraint{ 23262 {MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, 23263 }, 23264 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)}, 23265 }, { 23266 name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil", 23267 constraints: []core.TopologySpreadConstraint{{ 23268 MaxSkew: 1, 23269 TopologyKey: "k8s.io/zone", 23270 WhenUnsatisfiable: core.ScheduleAnyway, 23271 MinDomains: nil, 23272 }}, 23273 wantFieldErrors: field.ErrorList{}, 23274 }, { 23275 name: "negative minDomains is invalid", 23276 constraints: []core.TopologySpreadConstraint{{ 23277 MaxSkew: 1, 23278 TopologyKey: "k8s.io/zone", 23279 WhenUnsatisfiable: core.DoNotSchedule, 23280 MinDomains: utilpointer.Int32(-1), 23281 }}, 23282 wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)}, 23283 }, { 23284 name: "cannot use non-nil MinDomains with ScheduleAnyway", 23285 constraints: []core.TopologySpreadConstraint{{ 23286 MaxSkew: 1, 23287 TopologyKey: "k8s.io/zone", 23288 WhenUnsatisfiable: core.ScheduleAnyway, 23289 MinDomains: utilpointer.Int32(10), 23290 }}, 23291 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)))}, 23292 }, { 23293 name: "use negative MinDomains with ScheduleAnyway(invalid)", 23294 constraints: []core.TopologySpreadConstraint{{ 23295 MaxSkew: 1, 23296 TopologyKey: "k8s.io/zone", 23297 WhenUnsatisfiable: core.ScheduleAnyway, 23298 MinDomains: utilpointer.Int32(-1), 23299 }}, 23300 wantFieldErrors: []*field.Error{ 23301 field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg), 23302 field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))), 23303 }, 23304 }, { 23305 name: "missing TopologyKey", 23306 constraints: []core.TopologySpreadConstraint{ 23307 {MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule}, 23308 }, 23309 wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")}, 23310 }, { 23311 name: "missing scheduling mode", 23312 constraints: []core.TopologySpreadConstraint{ 23313 {MaxSkew: 1, TopologyKey: "k8s.io/zone"}, 23314 }, 23315 wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), sets.List(supportedScheduleActions))}, 23316 }, { 23317 name: "unsupported scheduling mode", 23318 constraints: []core.TopologySpreadConstraint{ 23319 {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")}, 23320 }, 23321 wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), sets.List(supportedScheduleActions))}, 23322 }, { 23323 name: "multiple constraints ok with all required fields", 23324 constraints: []core.TopologySpreadConstraint{ 23325 {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, 23326 {MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway}, 23327 }, 23328 wantFieldErrors: field.ErrorList{}, 23329 }, { 23330 name: "multiple constraints missing TopologyKey on partial ones", 23331 constraints: []core.TopologySpreadConstraint{ 23332 {MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway}, 23333 {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, 23334 }, 23335 wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")}, 23336 }, { 23337 name: "duplicate constraints", 23338 constraints: []core.TopologySpreadConstraint{ 23339 {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, 23340 {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule}, 23341 }, 23342 wantFieldErrors: []*field.Error{ 23343 field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)), 23344 }, 23345 }, { 23346 name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy", 23347 constraints: []core.TopologySpreadConstraint{{ 23348 MaxSkew: 1, 23349 TopologyKey: "k8s.io/zone", 23350 WhenUnsatisfiable: core.DoNotSchedule, 23351 NodeAffinityPolicy: &honor, 23352 NodeTaintsPolicy: &ignore, 23353 }}, 23354 wantFieldErrors: []*field.Error{}, 23355 }, { 23356 name: "unsupported policy name set on NodeAffinityPolicy", 23357 constraints: []core.TopologySpreadConstraint{{ 23358 MaxSkew: 1, 23359 TopologyKey: "k8s.io/zone", 23360 WhenUnsatisfiable: core.DoNotSchedule, 23361 NodeAffinityPolicy: &unknown, 23362 NodeTaintsPolicy: &ignore, 23363 }}, 23364 wantFieldErrors: []*field.Error{ 23365 field.NotSupported(nodeAffinityField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)), 23366 }, 23367 }, { 23368 name: "unsupported policy name set on NodeTaintsPolicy", 23369 constraints: []core.TopologySpreadConstraint{{ 23370 MaxSkew: 1, 23371 TopologyKey: "k8s.io/zone", 23372 WhenUnsatisfiable: core.DoNotSchedule, 23373 NodeAffinityPolicy: &honor, 23374 NodeTaintsPolicy: &unknown, 23375 }}, 23376 wantFieldErrors: []*field.Error{ 23377 field.NotSupported(nodeTaintsField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)), 23378 }, 23379 }, { 23380 name: "key in MatchLabelKeys isn't correctly defined", 23381 constraints: []core.TopologySpreadConstraint{{ 23382 MaxSkew: 1, 23383 TopologyKey: "k8s.io/zone", 23384 LabelSelector: &metav1.LabelSelector{}, 23385 WhenUnsatisfiable: core.DoNotSchedule, 23386 MatchLabelKeys: []string{"/simple"}, 23387 }}, 23388 wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")}, 23389 }, { 23390 name: "key exists in both matchLabelKeys and labelSelector", 23391 constraints: []core.TopologySpreadConstraint{{ 23392 MaxSkew: 1, 23393 TopologyKey: "k8s.io/zone", 23394 WhenUnsatisfiable: core.DoNotSchedule, 23395 MatchLabelKeys: []string{"foo"}, 23396 LabelSelector: &metav1.LabelSelector{ 23397 MatchExpressions: []metav1.LabelSelectorRequirement{ 23398 { 23399 Key: "foo", 23400 Operator: metav1.LabelSelectorOpNotIn, 23401 Values: []string{"value1", "value2"}, 23402 }, 23403 }, 23404 }, 23405 }}, 23406 wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")}, 23407 }, { 23408 name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set", 23409 constraints: []core.TopologySpreadConstraint{{ 23410 MaxSkew: 1, 23411 TopologyKey: "k8s.io/zone", 23412 WhenUnsatisfiable: core.DoNotSchedule, 23413 MatchLabelKeys: []string{"foo"}, 23414 }}, 23415 wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")}, 23416 }, { 23417 name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false", 23418 constraints: []core.TopologySpreadConstraint{{ 23419 MaxSkew: 1, 23420 TopologyKey: "k8s.io/zone", 23421 WhenUnsatisfiable: core.DoNotSchedule, 23422 MinDomains: nil, 23423 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}}, 23424 }}, 23425 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]')")}, 23426 opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false}, 23427 }, { 23428 name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true", 23429 constraints: []core.TopologySpreadConstraint{{ 23430 MaxSkew: 1, 23431 TopologyKey: "k8s.io/zone", 23432 WhenUnsatisfiable: core.DoNotSchedule, 23433 MinDomains: nil, 23434 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}}, 23435 }}, 23436 wantFieldErrors: []*field.Error{}, 23437 opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true}, 23438 }, { 23439 name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false", 23440 constraints: []core.TopologySpreadConstraint{{ 23441 MaxSkew: 1, 23442 TopologyKey: "k8s.io/zone", 23443 WhenUnsatisfiable: core.DoNotSchedule, 23444 MinDomains: nil, 23445 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}}, 23446 }}, 23447 wantFieldErrors: []*field.Error{}, 23448 opts: PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false}, 23449 }, 23450 } 23451 23452 for _, tc := range testCases { 23453 t.Run(tc.name, func(t *testing.T) { 23454 errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts) 23455 if diff := cmp.Diff(tc.wantFieldErrors, errs); diff != "" { 23456 t.Errorf("unexpected field errors (-want, +got):\n%s", diff) 23457 } 23458 }) 23459 } 23460 } 23461 23462 func TestValidateOverhead(t *testing.T) { 23463 successCase := []struct { 23464 Name string 23465 overhead core.ResourceList 23466 }{{ 23467 Name: "Valid Overhead for CPU + Memory", 23468 overhead: core.ResourceList{ 23469 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 23470 core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), 23471 }, 23472 }, 23473 } 23474 for _, tc := range successCase { 23475 if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 { 23476 t.Errorf("%q unexpected error: %v", tc.Name, errs) 23477 } 23478 } 23479 23480 errorCase := []struct { 23481 Name string 23482 overhead core.ResourceList 23483 }{{ 23484 Name: "Invalid Overhead Resources", 23485 overhead: core.ResourceList{ 23486 core.ResourceName("my.org"): resource.MustParse("10m"), 23487 }, 23488 }, 23489 } 23490 for _, tc := range errorCase { 23491 if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 { 23492 t.Errorf("%q expected error", tc.Name) 23493 } 23494 } 23495 } 23496 23497 // helper creates a pod with name, namespace and IPs 23498 func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod { 23499 return core.Pod{ 23500 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace}, 23501 Spec: core.PodSpec{ 23502 Containers: []core.Container{{ 23503 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 23504 }}, 23505 RestartPolicy: core.RestartPolicyAlways, 23506 DNSPolicy: core.DNSClusterFirst, 23507 }, 23508 Status: core.PodStatus{ 23509 PodIPs: podIPs, 23510 }, 23511 } 23512 } 23513 func TestPodIPsValidation(t *testing.T) { 23514 testCases := []struct { 23515 pod core.Pod 23516 expectError bool 23517 }{{ 23518 expectError: false, 23519 pod: makePod("nil-ips", "ns", nil), 23520 }, { 23521 expectError: false, 23522 pod: makePod("empty-podips-list", "ns", []core.PodIP{}), 23523 }, { 23524 expectError: false, 23525 pod: makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}), 23526 }, { 23527 expectError: false, 23528 pod: makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}), 23529 }, { 23530 expectError: false, 23531 pod: makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}), 23532 }, { 23533 expectError: false, 23534 pod: makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}), 23535 }, 23536 /* failure cases start here */ 23537 { 23538 expectError: true, 23539 pod: makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}), 23540 }, { 23541 expectError: true, 23542 pod: makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}), 23543 }, { 23544 expectError: true, 23545 pod: makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}), 23546 }, { 23547 expectError: true, 23548 pod: makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}), 23549 }, { 23550 expectError: true, 23551 pod: makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}), 23552 }, 23553 23554 { 23555 expectError: true, 23556 pod: makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}), 23557 }, { 23558 expectError: true, 23559 pod: makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}), 23560 }, 23561 } 23562 23563 for _, testCase := range testCases { 23564 t.Run(testCase.pod.Name, func(t *testing.T) { 23565 for _, oldTestCase := range testCases { 23566 newPod := testCase.pod.DeepCopy() 23567 newPod.ResourceVersion = "1" 23568 23569 oldPod := oldTestCase.pod.DeepCopy() 23570 oldPod.ResourceVersion = "1" 23571 oldPod.Name = newPod.Name 23572 23573 errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{}) 23574 23575 if len(errs) == 0 && testCase.expectError { 23576 t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name) 23577 } 23578 if len(errs) != 0 && !testCase.expectError { 23579 t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs) 23580 } 23581 } 23582 }) 23583 } 23584 } 23585 23586 func makePodWithHostIPs(podName string, podNamespace string, hostIPs []core.HostIP) core.Pod { 23587 hostIP := "" 23588 if len(hostIPs) > 0 { 23589 hostIP = hostIPs[0].IP 23590 } 23591 return core.Pod{ 23592 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace}, 23593 Spec: core.PodSpec{ 23594 Containers: []core.Container{ 23595 { 23596 Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", 23597 }, 23598 }, 23599 RestartPolicy: core.RestartPolicyAlways, 23600 DNSPolicy: core.DNSClusterFirst, 23601 }, 23602 Status: core.PodStatus{ 23603 HostIP: hostIP, 23604 HostIPs: hostIPs, 23605 }, 23606 } 23607 } 23608 23609 func TestHostIPsValidation(t *testing.T) { 23610 testCases := []struct { 23611 pod core.Pod 23612 expectError bool 23613 }{ 23614 { 23615 expectError: false, 23616 pod: makePodWithHostIPs("nil-ips", "ns", nil), 23617 }, 23618 { 23619 expectError: false, 23620 pod: makePodWithHostIPs("empty-HostIPs-list", "ns", []core.HostIP{}), 23621 }, 23622 { 23623 expectError: false, 23624 pod: makePodWithHostIPs("single-ip-family-6", "ns", []core.HostIP{{IP: "::1"}}), 23625 }, 23626 { 23627 expectError: false, 23628 pod: makePodWithHostIPs("single-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}}), 23629 }, 23630 { 23631 expectError: false, 23632 pod: makePodWithHostIPs("dual-stack-4-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}}), 23633 }, 23634 { 23635 expectError: false, 23636 pod: makePodWithHostIPs("dual-stack-6-4", "ns", []core.HostIP{{IP: "::1"}, {IP: "1.1.1.1"}}), 23637 }, 23638 /* failure cases start here */ 23639 { 23640 expectError: true, 23641 pod: makePodWithHostIPs("invalid-pod-ip", "ns", []core.HostIP{{IP: "this-is-not-an-ip"}}), 23642 }, 23643 { 23644 expectError: true, 23645 pod: makePodWithHostIPs("dualstack-same-ip-family-6", "ns", []core.HostIP{{IP: "::1"}, {IP: "::2"}}), 23646 }, 23647 { 23648 expectError: true, 23649 pod: makePodWithHostIPs("dualstack-same-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}), 23650 }, 23651 { 23652 expectError: true, 23653 pod: makePodWithHostIPs("dualstack-repeated-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}), 23654 }, 23655 { 23656 expectError: true, 23657 pod: makePodWithHostIPs("dualstack-repeated-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}), 23658 }, 23659 23660 { 23661 expectError: true, 23662 pod: makePodWithHostIPs("dualstack-duplicate-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}), 23663 }, 23664 { 23665 expectError: true, 23666 pod: makePodWithHostIPs("dualstack-duplicate-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}), 23667 }, 23668 } 23669 23670 for _, testCase := range testCases { 23671 t.Run(testCase.pod.Name, func(t *testing.T) { 23672 for _, oldTestCase := range testCases { 23673 newPod := testCase.pod.DeepCopy() 23674 newPod.ResourceVersion = "1" 23675 23676 oldPod := oldTestCase.pod.DeepCopy() 23677 oldPod.ResourceVersion = "1" 23678 oldPod.Name = newPod.Name 23679 23680 errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{}) 23681 23682 if len(errs) == 0 && testCase.expectError { 23683 t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name) 23684 } 23685 if len(errs) != 0 && !testCase.expectError { 23686 t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs) 23687 } 23688 } 23689 }) 23690 } 23691 } 23692 23693 // makes a node with pod cidr and a name 23694 func makeNode(nodeName string, podCIDRs []string) core.Node { 23695 return core.Node{ 23696 ObjectMeta: metav1.ObjectMeta{ 23697 Name: nodeName, 23698 }, 23699 Status: core.NodeStatus{ 23700 Addresses: []core.NodeAddress{ 23701 {Type: core.NodeExternalIP, Address: "something"}, 23702 }, 23703 Capacity: core.ResourceList{ 23704 core.ResourceName(core.ResourceCPU): resource.MustParse("10"), 23705 core.ResourceName(core.ResourceMemory): resource.MustParse("0"), 23706 }, 23707 }, 23708 Spec: core.NodeSpec{ 23709 PodCIDRs: podCIDRs, 23710 }, 23711 } 23712 } 23713 func TestValidateNodeCIDRs(t *testing.T) { 23714 testCases := []struct { 23715 expectError bool 23716 node core.Node 23717 }{{ 23718 expectError: false, 23719 node: makeNode("nil-pod-cidr", nil), 23720 }, { 23721 expectError: false, 23722 node: makeNode("empty-pod-cidr", []string{}), 23723 }, { 23724 expectError: false, 23725 node: makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}), 23726 }, { 23727 expectError: false, 23728 node: makeNode("single-pod-cidr-6", []string{"2000::/10"}), 23729 }, 23730 23731 { 23732 expectError: false, 23733 node: makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}), 23734 }, { 23735 expectError: false, 23736 node: makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}), 23737 }, 23738 // error cases starts here 23739 { 23740 expectError: true, 23741 node: makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}), 23742 }, { 23743 expectError: true, 23744 node: makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}), 23745 }, { 23746 expectError: true, 23747 node: makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}), 23748 }, { 23749 expectError: true, 23750 node: makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}), 23751 }, { 23752 expectError: true, 23753 node: makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}), 23754 }, { 23755 expectError: true, 23756 node: makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}), 23757 }, { 23758 expectError: true, 23759 node: makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}), 23760 }, 23761 } 23762 for _, testCase := range testCases { 23763 errs := ValidateNode(&testCase.node) 23764 if len(errs) == 0 && testCase.expectError { 23765 t.Errorf("expected failure for %s, but there were none", testCase.node.Name) 23766 return 23767 } 23768 if len(errs) != 0 && !testCase.expectError { 23769 t.Errorf("expected success for %s, but there were errors: %v", testCase.node.Name, errs) 23770 return 23771 } 23772 } 23773 } 23774 23775 func TestValidateSeccompAnnotationAndField(t *testing.T) { 23776 const containerName = "container" 23777 testProfile := "test" 23778 23779 for _, test := range []struct { 23780 description string 23781 pod *core.Pod 23782 validation func(*testing.T, string, field.ErrorList, *v1.Pod) 23783 }{{ 23784 description: "Field type unconfined and annotation does not match", 23785 pod: &core.Pod{ 23786 ObjectMeta: metav1.ObjectMeta{ 23787 Annotations: map[string]string{ 23788 v1.SeccompPodAnnotationKey: "not-matching", 23789 }, 23790 }, 23791 Spec: core.PodSpec{ 23792 SecurityContext: &core.PodSecurityContext{ 23793 SeccompProfile: &core.SeccompProfile{ 23794 Type: core.SeccompProfileTypeUnconfined, 23795 }, 23796 }, 23797 }, 23798 }, 23799 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23800 require.NotNil(t, allErrs, desc) 23801 }, 23802 }, { 23803 description: "Field type default and annotation does not match", 23804 pod: &core.Pod{ 23805 ObjectMeta: metav1.ObjectMeta{ 23806 Annotations: map[string]string{ 23807 v1.SeccompPodAnnotationKey: "not-matching", 23808 }, 23809 }, 23810 Spec: core.PodSpec{ 23811 SecurityContext: &core.PodSecurityContext{ 23812 SeccompProfile: &core.SeccompProfile{ 23813 Type: core.SeccompProfileTypeRuntimeDefault, 23814 }, 23815 }, 23816 }, 23817 }, 23818 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23819 require.NotNil(t, allErrs, desc) 23820 }, 23821 }, { 23822 description: "Field type localhost and annotation does not match", 23823 pod: &core.Pod{ 23824 ObjectMeta: metav1.ObjectMeta{ 23825 Annotations: map[string]string{ 23826 v1.SeccompPodAnnotationKey: "not-matching", 23827 }, 23828 }, 23829 Spec: core.PodSpec{ 23830 SecurityContext: &core.PodSecurityContext{ 23831 SeccompProfile: &core.SeccompProfile{ 23832 Type: core.SeccompProfileTypeLocalhost, 23833 LocalhostProfile: &testProfile, 23834 }, 23835 }, 23836 }, 23837 }, 23838 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23839 require.NotNil(t, allErrs, desc) 23840 }, 23841 }, { 23842 description: "Field type localhost and localhost/ prefixed annotation does not match", 23843 pod: &core.Pod{ 23844 ObjectMeta: metav1.ObjectMeta{ 23845 Annotations: map[string]string{ 23846 v1.SeccompPodAnnotationKey: "localhost/not-matching", 23847 }, 23848 }, 23849 Spec: core.PodSpec{ 23850 SecurityContext: &core.PodSecurityContext{ 23851 SeccompProfile: &core.SeccompProfile{ 23852 Type: core.SeccompProfileTypeLocalhost, 23853 LocalhostProfile: &testProfile, 23854 }, 23855 }, 23856 }, 23857 }, 23858 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23859 require.NotNil(t, allErrs, desc) 23860 }, 23861 }, { 23862 description: "Field type unconfined and annotation does not match (container)", 23863 pod: &core.Pod{ 23864 ObjectMeta: metav1.ObjectMeta{ 23865 Annotations: map[string]string{ 23866 v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", 23867 }, 23868 }, 23869 Spec: core.PodSpec{ 23870 Containers: []core.Container{{ 23871 Name: containerName, 23872 SecurityContext: &core.SecurityContext{ 23873 SeccompProfile: &core.SeccompProfile{ 23874 Type: core.SeccompProfileTypeUnconfined, 23875 }, 23876 }, 23877 }}, 23878 }, 23879 }, 23880 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23881 require.NotNil(t, allErrs, desc) 23882 }, 23883 }, { 23884 description: "Field type default and annotation does not match (container)", 23885 pod: &core.Pod{ 23886 ObjectMeta: metav1.ObjectMeta{ 23887 Annotations: map[string]string{ 23888 v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", 23889 }, 23890 }, 23891 Spec: core.PodSpec{ 23892 Containers: []core.Container{{ 23893 Name: containerName, 23894 SecurityContext: &core.SecurityContext{ 23895 SeccompProfile: &core.SeccompProfile{ 23896 Type: core.SeccompProfileTypeRuntimeDefault, 23897 }, 23898 }, 23899 }}, 23900 }, 23901 }, 23902 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23903 require.NotNil(t, allErrs, desc) 23904 }, 23905 }, { 23906 description: "Field type localhost and annotation does not match (container)", 23907 pod: &core.Pod{ 23908 ObjectMeta: metav1.ObjectMeta{ 23909 Annotations: map[string]string{ 23910 v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching", 23911 }, 23912 }, 23913 Spec: core.PodSpec{ 23914 Containers: []core.Container{{ 23915 Name: containerName, 23916 SecurityContext: &core.SecurityContext{ 23917 SeccompProfile: &core.SeccompProfile{ 23918 Type: core.SeccompProfileTypeLocalhost, 23919 LocalhostProfile: &testProfile, 23920 }, 23921 }, 23922 }}, 23923 }, 23924 }, 23925 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23926 require.NotNil(t, allErrs, desc) 23927 }, 23928 }, { 23929 description: "Field type localhost and localhost/ prefixed annotation does not match (container)", 23930 pod: &core.Pod{ 23931 ObjectMeta: metav1.ObjectMeta{ 23932 Annotations: map[string]string{ 23933 v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching", 23934 }, 23935 }, 23936 Spec: core.PodSpec{ 23937 Containers: []core.Container{{ 23938 Name: containerName, 23939 SecurityContext: &core.SecurityContext{ 23940 SeccompProfile: &core.SeccompProfile{ 23941 Type: core.SeccompProfileTypeLocalhost, 23942 LocalhostProfile: &testProfile, 23943 }, 23944 }, 23945 }}, 23946 }, 23947 }, 23948 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23949 require.NotNil(t, allErrs, desc) 23950 }, 23951 }, { 23952 description: "Nil errors must not be appended (pod)", 23953 pod: &core.Pod{ 23954 ObjectMeta: metav1.ObjectMeta{ 23955 Annotations: map[string]string{ 23956 v1.SeccompPodAnnotationKey: "localhost/anyprofile", 23957 }, 23958 }, 23959 Spec: core.PodSpec{ 23960 SecurityContext: &core.PodSecurityContext{ 23961 SeccompProfile: &core.SeccompProfile{ 23962 Type: "Abc", 23963 }, 23964 }, 23965 Containers: []core.Container{{ 23966 Name: containerName, 23967 }}, 23968 }, 23969 }, 23970 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23971 require.Empty(t, allErrs, desc) 23972 }, 23973 }, { 23974 description: "Nil errors must not be appended (container)", 23975 pod: &core.Pod{ 23976 ObjectMeta: metav1.ObjectMeta{ 23977 Annotations: map[string]string{ 23978 v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching", 23979 }, 23980 }, 23981 Spec: core.PodSpec{ 23982 Containers: []core.Container{{ 23983 SecurityContext: &core.SecurityContext{ 23984 SeccompProfile: &core.SeccompProfile{ 23985 Type: "Abc", 23986 }, 23987 }, 23988 Name: containerName, 23989 }}, 23990 }, 23991 }, 23992 validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) { 23993 require.Empty(t, allErrs, desc) 23994 }, 23995 }, 23996 } { 23997 output := &v1.Pod{ 23998 ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}, 23999 } 24000 for i, ctr := range test.pod.Spec.Containers { 24001 output.Spec.Containers = append(output.Spec.Containers, v1.Container{}) 24002 if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil { 24003 output.Spec.Containers[i].SecurityContext = &v1.SecurityContext{ 24004 SeccompProfile: &v1.SeccompProfile{ 24005 Type: v1.SeccompProfileType(ctr.SecurityContext.SeccompProfile.Type), 24006 LocalhostProfile: ctr.SecurityContext.SeccompProfile.LocalhostProfile, 24007 }, 24008 } 24009 } 24010 } 24011 errList := validateSeccompAnnotationsAndFields(test.pod.ObjectMeta, &test.pod.Spec, field.NewPath("")) 24012 test.validation(t, test.description, errList, output) 24013 } 24014 } 24015 24016 func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) { 24017 rootFld := field.NewPath("") 24018 tests := []struct { 24019 description string 24020 annotationValue string 24021 seccompField *core.SeccompProfile 24022 fldPath *field.Path 24023 expectedErr *field.Error 24024 }{{ 24025 description: "seccompField nil should return empty", 24026 expectedErr: nil, 24027 }, { 24028 description: "unconfined annotation and SeccompProfileTypeUnconfined should return empty", 24029 annotationValue: "unconfined", 24030 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined}, 24031 expectedErr: nil, 24032 }, { 24033 description: "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty", 24034 annotationValue: "runtime/default", 24035 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, 24036 expectedErr: nil, 24037 }, { 24038 description: "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty", 24039 annotationValue: "docker/default", 24040 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, 24041 expectedErr: nil, 24042 }, { 24043 description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty", 24044 annotationValue: "localhost/test.json", 24045 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")}, 24046 expectedErr: nil, 24047 }, { 24048 description: "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error", 24049 annotationValue: "localhost/test.json", 24050 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost}, 24051 fldPath: rootFld, 24052 expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"), 24053 }, { 24054 description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error", 24055 annotationValue: "localhost/test.json", 24056 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")}, 24057 fldPath: rootFld, 24058 expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"), 24059 }, { 24060 description: "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error", 24061 annotationValue: "localhost/test.json", 24062 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined}, 24063 fldPath: rootFld, 24064 expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"), 24065 }, { 24066 description: "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error", 24067 annotationValue: "localhost/test.json", 24068 seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault}, 24069 fldPath: rootFld, 24070 expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"), 24071 }, 24072 } 24073 24074 for i, test := range tests { 24075 err := validateSeccompAnnotationsAndFieldsMatch(test.annotationValue, test.seccompField, test.fldPath) 24076 assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description) 24077 } 24078 } 24079 24080 func TestValidatePodTemplateSpecSeccomp(t *testing.T) { 24081 rootFld := field.NewPath("template") 24082 tests := []struct { 24083 description string 24084 spec *core.PodTemplateSpec 24085 fldPath *field.Path 24086 expectedErr field.ErrorList 24087 }{{ 24088 description: "seccomp field and container annotation must match", 24089 fldPath: rootFld, 24090 expectedErr: field.ErrorList{ 24091 field.Forbidden( 24092 rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"), 24093 "seccomp type in annotation and field must match"), 24094 }, 24095 spec: &core.PodTemplateSpec{ 24096 ObjectMeta: metav1.ObjectMeta{ 24097 Annotations: map[string]string{ 24098 "container.seccomp.security.alpha.kubernetes.io/test2": "unconfined", 24099 }, 24100 }, 24101 Spec: core.PodSpec{ 24102 Containers: []core.Container{{ 24103 Name: "test1", 24104 Image: "alpine", 24105 ImagePullPolicy: core.PullAlways, 24106 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, 24107 }, { 24108 SecurityContext: &core.SecurityContext{ 24109 SeccompProfile: &core.SeccompProfile{ 24110 Type: core.SeccompProfileTypeRuntimeDefault, 24111 }, 24112 }, 24113 Name: "test2", 24114 Image: "alpine", 24115 ImagePullPolicy: core.PullAlways, 24116 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, 24117 }}, 24118 RestartPolicy: core.RestartPolicyAlways, 24119 DNSPolicy: core.DNSDefault, 24120 }, 24121 }, 24122 }, { 24123 description: "seccomp field and pod annotation must match", 24124 fldPath: rootFld, 24125 expectedErr: field.ErrorList{ 24126 field.Forbidden( 24127 rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"), 24128 "seccomp type in annotation and field must match"), 24129 }, 24130 spec: &core.PodTemplateSpec{ 24131 ObjectMeta: metav1.ObjectMeta{ 24132 Annotations: map[string]string{ 24133 "seccomp.security.alpha.kubernetes.io/pod": "runtime/default", 24134 }, 24135 }, 24136 Spec: core.PodSpec{ 24137 SecurityContext: &core.PodSecurityContext{ 24138 SeccompProfile: &core.SeccompProfile{ 24139 Type: core.SeccompProfileTypeUnconfined, 24140 }, 24141 }, 24142 Containers: []core.Container{{ 24143 Name: "test", 24144 Image: "alpine", 24145 ImagePullPolicy: core.PullAlways, 24146 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, 24147 }}, 24148 RestartPolicy: core.RestartPolicyAlways, 24149 DNSPolicy: core.DNSDefault, 24150 }, 24151 }, 24152 }, { 24153 description: "init seccomp field and container annotation must match", 24154 fldPath: rootFld, 24155 expectedErr: field.ErrorList{ 24156 field.Forbidden( 24157 rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"), 24158 "seccomp type in annotation and field must match"), 24159 }, 24160 spec: &core.PodTemplateSpec{ 24161 ObjectMeta: metav1.ObjectMeta{ 24162 Annotations: map[string]string{ 24163 "container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined", 24164 }, 24165 }, 24166 Spec: core.PodSpec{ 24167 Containers: []core.Container{{ 24168 Name: "test", 24169 Image: "alpine", 24170 ImagePullPolicy: core.PullAlways, 24171 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, 24172 }}, 24173 InitContainers: []core.Container{{ 24174 Name: "init-test", 24175 SecurityContext: &core.SecurityContext{ 24176 SeccompProfile: &core.SeccompProfile{ 24177 Type: core.SeccompProfileTypeRuntimeDefault, 24178 }, 24179 }, 24180 Image: "alpine", 24181 ImagePullPolicy: core.PullAlways, 24182 TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError, 24183 }}, 24184 RestartPolicy: core.RestartPolicyAlways, 24185 DNSPolicy: core.DNSDefault, 24186 }, 24187 }, 24188 }, 24189 } 24190 24191 for i, test := range tests { 24192 err := ValidatePodTemplateSpec(test.spec, rootFld, PodValidationOptions{}) 24193 assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description) 24194 } 24195 } 24196 24197 func TestValidateResourceRequirements(t *testing.T) { 24198 path := field.NewPath("resources") 24199 tests := []struct { 24200 name string 24201 requirements core.ResourceRequirements 24202 opts PodValidationOptions 24203 }{{ 24204 name: "limits and requests of hugepage resource are equal", 24205 requirements: core.ResourceRequirements{ 24206 Limits: core.ResourceList{ 24207 core.ResourceCPU: resource.MustParse("10"), 24208 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), 24209 }, 24210 Requests: core.ResourceList{ 24211 core.ResourceCPU: resource.MustParse("10"), 24212 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), 24213 }, 24214 }, 24215 opts: PodValidationOptions{}, 24216 }, { 24217 name: "limits and requests of memory resource are equal", 24218 requirements: core.ResourceRequirements{ 24219 Limits: core.ResourceList{ 24220 core.ResourceMemory: resource.MustParse("2Mi"), 24221 }, 24222 Requests: core.ResourceList{ 24223 core.ResourceMemory: resource.MustParse("2Mi"), 24224 }, 24225 }, 24226 opts: PodValidationOptions{}, 24227 }, { 24228 name: "limits and requests of cpu resource are equal", 24229 requirements: core.ResourceRequirements{ 24230 Limits: core.ResourceList{ 24231 core.ResourceCPU: resource.MustParse("10"), 24232 }, 24233 Requests: core.ResourceList{ 24234 core.ResourceCPU: resource.MustParse("10"), 24235 }, 24236 }, 24237 opts: PodValidationOptions{}, 24238 }, 24239 } 24240 24241 for _, tc := range tests { 24242 t.Run(tc.name, func(t *testing.T) { 24243 if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) != 0 { 24244 t.Errorf("unexpected errors: %v", errs) 24245 } 24246 }) 24247 } 24248 24249 errTests := []struct { 24250 name string 24251 requirements core.ResourceRequirements 24252 opts PodValidationOptions 24253 }{{ 24254 name: "hugepage resource without cpu or memory", 24255 requirements: core.ResourceRequirements{ 24256 Limits: core.ResourceList{ 24257 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), 24258 }, 24259 Requests: core.ResourceList{ 24260 core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), 24261 }, 24262 }, 24263 opts: PodValidationOptions{}, 24264 }, 24265 } 24266 24267 for _, tc := range errTests { 24268 t.Run(tc.name, func(t *testing.T) { 24269 if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) == 0 { 24270 t.Error("expected errors") 24271 } 24272 }) 24273 } 24274 } 24275 24276 func TestValidateNonSpecialIP(t *testing.T) { 24277 fp := field.NewPath("ip") 24278 24279 // Valid values. 24280 for _, tc := range []struct { 24281 desc string 24282 ip string 24283 }{ 24284 {"ipv4", "10.1.2.3"}, 24285 {"ipv4 class E", "244.1.2.3"}, 24286 {"ipv6", "2000::1"}, 24287 } { 24288 t.Run(tc.desc, func(t *testing.T) { 24289 errs := ValidateNonSpecialIP(tc.ip, fp) 24290 if len(errs) != 0 { 24291 t.Errorf("ValidateNonSpecialIP(%q, ...) = %v; want nil", tc.ip, errs) 24292 } 24293 }) 24294 } 24295 // Invalid cases 24296 for _, tc := range []struct { 24297 desc string 24298 ip string 24299 }{ 24300 {"ipv4 unspecified", "0.0.0.0"}, 24301 {"ipv6 unspecified", "::0"}, 24302 {"ipv4 localhost", "127.0.0.0"}, 24303 {"ipv4 localhost", "127.255.255.255"}, 24304 {"ipv6 localhost", "::1"}, 24305 {"ipv6 link local", "fe80::"}, 24306 {"ipv6 local multicast", "ff02::"}, 24307 } { 24308 t.Run(tc.desc, func(t *testing.T) { 24309 errs := ValidateNonSpecialIP(tc.ip, fp) 24310 if len(errs) == 0 { 24311 t.Errorf("ValidateNonSpecialIP(%q, ...) = nil; want non-nil (errors)", tc.ip) 24312 } 24313 }) 24314 } 24315 } 24316 24317 func TestValidateHostUsers(t *testing.T) { 24318 falseVar := false 24319 trueVar := true 24320 24321 cases := []struct { 24322 name string 24323 success bool 24324 spec *core.PodSpec 24325 }{{ 24326 name: "empty", 24327 success: true, 24328 spec: &core.PodSpec{}, 24329 }, { 24330 name: "hostUsers unset", 24331 success: true, 24332 spec: &core.PodSpec{ 24333 SecurityContext: &core.PodSecurityContext{}, 24334 }, 24335 }, { 24336 name: "hostUsers=false", 24337 success: true, 24338 spec: &core.PodSpec{ 24339 SecurityContext: &core.PodSecurityContext{ 24340 HostUsers: &falseVar, 24341 }, 24342 }, 24343 }, { 24344 name: "hostUsers=true", 24345 success: true, 24346 spec: &core.PodSpec{ 24347 SecurityContext: &core.PodSecurityContext{ 24348 HostUsers: &trueVar, 24349 }, 24350 }, 24351 }, { 24352 name: "hostUsers=false & volumes", 24353 success: true, 24354 spec: &core.PodSpec{ 24355 SecurityContext: &core.PodSecurityContext{ 24356 HostUsers: &falseVar, 24357 }, 24358 Volumes: []core.Volume{{ 24359 Name: "configmap", 24360 VolumeSource: core.VolumeSource{ 24361 ConfigMap: &core.ConfigMapVolumeSource{ 24362 LocalObjectReference: core.LocalObjectReference{Name: "configmap"}, 24363 }, 24364 }, 24365 }, { 24366 Name: "secret", 24367 VolumeSource: core.VolumeSource{ 24368 Secret: &core.SecretVolumeSource{ 24369 SecretName: "secret", 24370 }, 24371 }, 24372 }, { 24373 Name: "downward-api", 24374 VolumeSource: core.VolumeSource{ 24375 DownwardAPI: &core.DownwardAPIVolumeSource{}, 24376 }, 24377 }, { 24378 Name: "proj", 24379 VolumeSource: core.VolumeSource{ 24380 Projected: &core.ProjectedVolumeSource{}, 24381 }, 24382 }, { 24383 Name: "empty-dir", 24384 VolumeSource: core.VolumeSource{ 24385 EmptyDir: &core.EmptyDirVolumeSource{}, 24386 }, 24387 }}, 24388 }, 24389 }, { 24390 name: "hostUsers=false - stateful volume", 24391 success: true, 24392 spec: &core.PodSpec{ 24393 SecurityContext: &core.PodSecurityContext{ 24394 HostUsers: &falseVar, 24395 }, 24396 Volumes: []core.Volume{{ 24397 Name: "host-path", 24398 VolumeSource: core.VolumeSource{ 24399 HostPath: &core.HostPathVolumeSource{}, 24400 }, 24401 }}, 24402 }, 24403 }, { 24404 name: "hostUsers=true - unsupported volume", 24405 success: true, 24406 spec: &core.PodSpec{ 24407 SecurityContext: &core.PodSecurityContext{ 24408 HostUsers: &trueVar, 24409 }, 24410 Volumes: []core.Volume{{ 24411 Name: "host-path", 24412 VolumeSource: core.VolumeSource{ 24413 HostPath: &core.HostPathVolumeSource{}, 24414 }, 24415 }}, 24416 }, 24417 }, { 24418 name: "hostUsers=false & HostNetwork", 24419 success: false, 24420 spec: &core.PodSpec{ 24421 SecurityContext: &core.PodSecurityContext{ 24422 HostUsers: &falseVar, 24423 HostNetwork: true, 24424 }, 24425 }, 24426 }, { 24427 name: "hostUsers=false & HostPID", 24428 success: false, 24429 spec: &core.PodSpec{ 24430 SecurityContext: &core.PodSecurityContext{ 24431 HostUsers: &falseVar, 24432 HostPID: true, 24433 }, 24434 }, 24435 }, { 24436 name: "hostUsers=false & HostIPC", 24437 success: false, 24438 spec: &core.PodSpec{ 24439 SecurityContext: &core.PodSecurityContext{ 24440 HostUsers: &falseVar, 24441 HostIPC: true, 24442 }, 24443 }, 24444 }, 24445 } 24446 24447 for _, tc := range cases { 24448 t.Run(tc.name, func(t *testing.T) { 24449 fPath := field.NewPath("spec") 24450 24451 allErrs := validateHostUsers(tc.spec, fPath) 24452 if !tc.success && len(allErrs) == 0 { 24453 t.Errorf("Unexpected success") 24454 } 24455 if tc.success && len(allErrs) != 0 { 24456 t.Errorf("Unexpected error(s): %v", allErrs) 24457 } 24458 }) 24459 } 24460 } 24461 24462 func TestValidateWindowsHostProcessPod(t *testing.T) { 24463 const containerName = "container" 24464 falseVar := false 24465 trueVar := true 24466 24467 testCases := []struct { 24468 name string 24469 expectError bool 24470 allowPrivileged bool 24471 podSpec *core.PodSpec 24472 }{{ 24473 name: "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate", 24474 expectError: true, 24475 allowPrivileged: true, 24476 podSpec: &core.PodSpec{ 24477 SecurityContext: &core.PodSecurityContext{ 24478 WindowsOptions: &core.WindowsSecurityContextOptions{ 24479 HostProcess: &trueVar, 24480 }, 24481 }, 24482 Containers: []core.Container{{ 24483 Name: containerName, 24484 }}, 24485 }, 24486 }, { 24487 name: "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate", 24488 expectError: false, 24489 allowPrivileged: true, 24490 podSpec: &core.PodSpec{ 24491 SecurityContext: &core.PodSecurityContext{ 24492 HostNetwork: true, 24493 WindowsOptions: &core.WindowsSecurityContextOptions{ 24494 HostProcess: &trueVar, 24495 }, 24496 }, 24497 Containers: []core.Container{{ 24498 Name: containerName, 24499 }}, 24500 }, 24501 }, { 24502 name: "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate", 24503 expectError: false, 24504 allowPrivileged: true, 24505 podSpec: &core.PodSpec{ 24506 SecurityContext: &core.PodSecurityContext{ 24507 HostNetwork: true, 24508 WindowsOptions: &core.WindowsSecurityContextOptions{ 24509 HostProcess: &trueVar, 24510 }, 24511 }, 24512 Containers: []core.Container{{ 24513 Name: containerName, 24514 SecurityContext: &core.SecurityContext{ 24515 WindowsOptions: &core.WindowsSecurityContextOptions{ 24516 HostProcess: &trueVar, 24517 }, 24518 }, 24519 }}, 24520 InitContainers: []core.Container{{ 24521 Name: containerName, 24522 SecurityContext: &core.SecurityContext{ 24523 WindowsOptions: &core.WindowsSecurityContextOptions{ 24524 HostProcess: &trueVar, 24525 }, 24526 }, 24527 }}, 24528 }, 24529 }, { 24530 name: "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate", 24531 expectError: false, 24532 allowPrivileged: true, 24533 podSpec: &core.PodSpec{ 24534 SecurityContext: &core.PodSecurityContext{ 24535 HostNetwork: true, 24536 }, 24537 Containers: []core.Container{{ 24538 Name: containerName, 24539 SecurityContext: &core.SecurityContext{ 24540 WindowsOptions: &core.WindowsSecurityContextOptions{ 24541 HostProcess: &trueVar, 24542 }, 24543 }, 24544 }}, 24545 InitContainers: []core.Container{{ 24546 Name: containerName, 24547 SecurityContext: &core.SecurityContext{ 24548 WindowsOptions: &core.WindowsSecurityContextOptions{ 24549 HostProcess: &trueVar, 24550 }, 24551 }, 24552 }}, 24553 }, 24554 }, { 24555 name: "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate", 24556 expectError: true, 24557 allowPrivileged: true, 24558 podSpec: &core.PodSpec{ 24559 SecurityContext: &core.PodSecurityContext{ 24560 HostNetwork: true, 24561 }, 24562 Containers: []core.Container{{ 24563 Name: containerName, 24564 SecurityContext: &core.SecurityContext{ 24565 WindowsOptions: &core.WindowsSecurityContextOptions{ 24566 HostProcess: &trueVar, 24567 }, 24568 }, 24569 }}, 24570 InitContainers: []core.Container{{ 24571 Name: containerName, 24572 SecurityContext: &core.SecurityContext{ 24573 WindowsOptions: &core.WindowsSecurityContextOptions{ 24574 HostProcess: &falseVar, 24575 }, 24576 }, 24577 }}, 24578 }, 24579 }, { 24580 name: "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate", 24581 expectError: true, 24582 allowPrivileged: true, 24583 podSpec: &core.PodSpec{ 24584 SecurityContext: &core.PodSecurityContext{ 24585 HostNetwork: true, 24586 }, 24587 Containers: []core.Container{{ 24588 Name: containerName, 24589 SecurityContext: &core.SecurityContext{ 24590 WindowsOptions: &core.WindowsSecurityContextOptions{ 24591 HostProcess: &trueVar, 24592 }, 24593 }, 24594 }}, 24595 InitContainers: []core.Container{{ 24596 Name: containerName, 24597 }}, 24598 }, 24599 }, { 24600 name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate", 24601 expectError: true, 24602 allowPrivileged: true, 24603 podSpec: &core.PodSpec{ 24604 SecurityContext: &core.PodSecurityContext{ 24605 HostNetwork: true, 24606 WindowsOptions: &core.WindowsSecurityContextOptions{ 24607 HostProcess: &trueVar, 24608 }, 24609 }, 24610 Containers: []core.Container{{ 24611 Name: containerName, 24612 SecurityContext: &core.SecurityContext{ 24613 WindowsOptions: &core.WindowsSecurityContextOptions{ 24614 HostProcess: &trueVar, 24615 }, 24616 }, 24617 }}, 24618 InitContainers: []core.Container{{ 24619 Name: containerName, 24620 SecurityContext: &core.SecurityContext{ 24621 WindowsOptions: &core.WindowsSecurityContextOptions{ 24622 HostProcess: &falseVar, 24623 }, 24624 }, 24625 }}, 24626 }, 24627 }, { 24628 name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate", 24629 expectError: true, 24630 allowPrivileged: true, 24631 podSpec: &core.PodSpec{ 24632 SecurityContext: &core.PodSecurityContext{ 24633 HostNetwork: true, 24634 WindowsOptions: &core.WindowsSecurityContextOptions{ 24635 HostProcess: &trueVar, 24636 }, 24637 }, 24638 Containers: []core.Container{{ 24639 Name: containerName, 24640 SecurityContext: &core.SecurityContext{ 24641 WindowsOptions: &core.WindowsSecurityContextOptions{ 24642 HostProcess: &trueVar, 24643 }, 24644 }, 24645 }, { 24646 Name: containerName, 24647 SecurityContext: &core.SecurityContext{ 24648 WindowsOptions: &core.WindowsSecurityContextOptions{ 24649 HostProcess: &falseVar, 24650 }, 24651 }, 24652 }}, 24653 }, 24654 }, { 24655 name: "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate", 24656 expectError: false, 24657 allowPrivileged: true, 24658 podSpec: &core.PodSpec{ 24659 SecurityContext: &core.PodSecurityContext{ 24660 HostNetwork: true, 24661 WindowsOptions: &core.WindowsSecurityContextOptions{ 24662 HostProcess: &trueVar, 24663 }, 24664 }, 24665 Containers: []core.Container{{ 24666 Name: containerName, 24667 SecurityContext: &core.SecurityContext{ 24668 WindowsOptions: &core.WindowsSecurityContextOptions{ 24669 HostProcess: &trueVar, 24670 }, 24671 }, 24672 }}, 24673 InitContainers: []core.Container{{ 24674 Name: containerName, 24675 }}, 24676 }, 24677 }, { 24678 name: "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate", 24679 expectError: true, 24680 allowPrivileged: true, 24681 podSpec: &core.PodSpec{ 24682 SecurityContext: &core.PodSecurityContext{ 24683 HostNetwork: true, 24684 WindowsOptions: &core.WindowsSecurityContextOptions{ 24685 HostProcess: &falseVar, 24686 }, 24687 }, 24688 Containers: []core.Container{{ 24689 Name: containerName, 24690 SecurityContext: &core.SecurityContext{ 24691 WindowsOptions: &core.WindowsSecurityContextOptions{ 24692 HostProcess: &trueVar, 24693 }, 24694 }, 24695 }}, 24696 InitContainers: []core.Container{{ 24697 Name: containerName, 24698 }}, 24699 }, 24700 }, { 24701 name: "Pod's HostProcess set to true but all containers override to false should not validate", 24702 expectError: true, 24703 allowPrivileged: true, 24704 podSpec: &core.PodSpec{ 24705 SecurityContext: &core.PodSecurityContext{ 24706 HostNetwork: true, 24707 WindowsOptions: &core.WindowsSecurityContextOptions{ 24708 HostProcess: &trueVar, 24709 }, 24710 }, 24711 Containers: []core.Container{{ 24712 Name: containerName, 24713 SecurityContext: &core.SecurityContext{ 24714 WindowsOptions: &core.WindowsSecurityContextOptions{ 24715 HostProcess: &falseVar, 24716 }, 24717 }, 24718 }}, 24719 }, 24720 }, { 24721 name: "Valid HostProcess pod should spec should not validate if allowPrivileged is not set", 24722 expectError: true, 24723 allowPrivileged: false, 24724 podSpec: &core.PodSpec{ 24725 SecurityContext: &core.PodSecurityContext{ 24726 HostNetwork: true, 24727 }, 24728 Containers: []core.Container{{ 24729 Name: containerName, 24730 SecurityContext: &core.SecurityContext{ 24731 WindowsOptions: &core.WindowsSecurityContextOptions{ 24732 HostProcess: &trueVar, 24733 }, 24734 }, 24735 }}, 24736 }, 24737 }, { 24738 name: "Non-HostProcess ephemeral container in HostProcess pod should not validate", 24739 expectError: true, 24740 allowPrivileged: true, 24741 podSpec: &core.PodSpec{ 24742 SecurityContext: &core.PodSecurityContext{ 24743 HostNetwork: true, 24744 WindowsOptions: &core.WindowsSecurityContextOptions{ 24745 HostProcess: &trueVar, 24746 }, 24747 }, 24748 Containers: []core.Container{{ 24749 Name: containerName, 24750 }}, 24751 EphemeralContainers: []core.EphemeralContainer{{ 24752 EphemeralContainerCommon: core.EphemeralContainerCommon{ 24753 SecurityContext: &core.SecurityContext{ 24754 WindowsOptions: &core.WindowsSecurityContextOptions{ 24755 HostProcess: &falseVar, 24756 }, 24757 }, 24758 }, 24759 }}, 24760 }, 24761 }, { 24762 name: "HostProcess ephemeral container in HostProcess pod should validate", 24763 expectError: false, 24764 allowPrivileged: true, 24765 podSpec: &core.PodSpec{ 24766 SecurityContext: &core.PodSecurityContext{ 24767 HostNetwork: true, 24768 WindowsOptions: &core.WindowsSecurityContextOptions{ 24769 HostProcess: &trueVar, 24770 }, 24771 }, 24772 Containers: []core.Container{{ 24773 Name: containerName, 24774 }}, 24775 EphemeralContainers: []core.EphemeralContainer{{ 24776 EphemeralContainerCommon: core.EphemeralContainerCommon{}, 24777 }}, 24778 }, 24779 }, { 24780 name: "Non-HostProcess ephemeral container in Non-HostProcess pod should validate", 24781 expectError: false, 24782 allowPrivileged: true, 24783 podSpec: &core.PodSpec{ 24784 Containers: []core.Container{{ 24785 Name: containerName, 24786 }}, 24787 EphemeralContainers: []core.EphemeralContainer{{ 24788 EphemeralContainerCommon: core.EphemeralContainerCommon{ 24789 SecurityContext: &core.SecurityContext{ 24790 WindowsOptions: &core.WindowsSecurityContextOptions{ 24791 HostProcess: &falseVar, 24792 }, 24793 }, 24794 }, 24795 }}, 24796 }, 24797 }, { 24798 name: "HostProcess ephemeral container in Non-HostProcess pod should not validate", 24799 expectError: true, 24800 allowPrivileged: true, 24801 podSpec: &core.PodSpec{ 24802 Containers: []core.Container{{ 24803 Name: containerName, 24804 }}, 24805 EphemeralContainers: []core.EphemeralContainer{{ 24806 EphemeralContainerCommon: core.EphemeralContainerCommon{ 24807 SecurityContext: &core.SecurityContext{ 24808 WindowsOptions: &core.WindowsSecurityContextOptions{ 24809 HostProcess: &trueVar, 24810 }, 24811 }, 24812 }, 24813 }}, 24814 }, 24815 }, 24816 } 24817 24818 for _, testCase := range testCases { 24819 t.Run(testCase.name, func(t *testing.T) { 24820 24821 capabilities.SetForTests(capabilities.Capabilities{ 24822 AllowPrivileged: testCase.allowPrivileged, 24823 }) 24824 24825 errs := validateWindowsHostProcessPod(testCase.podSpec, field.NewPath("spec")) 24826 if testCase.expectError && len(errs) == 0 { 24827 t.Errorf("Unexpected success") 24828 } 24829 if !testCase.expectError && len(errs) != 0 { 24830 t.Errorf("Unexpected error(s): %v", errs) 24831 } 24832 }) 24833 } 24834 } 24835 24836 func TestValidateOS(t *testing.T) { 24837 testCases := []struct { 24838 name string 24839 expectError bool 24840 podSpec *core.PodSpec 24841 }{{ 24842 name: "no OS field, featuregate", 24843 expectError: false, 24844 podSpec: &core.PodSpec{OS: nil}, 24845 }, { 24846 name: "empty OS field, featuregate", 24847 expectError: true, 24848 podSpec: &core.PodSpec{OS: &core.PodOS{}}, 24849 }, { 24850 name: "OS field, featuregate, valid OS", 24851 expectError: false, 24852 podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Linux}}, 24853 }, { 24854 name: "OS field, featuregate, valid OS", 24855 expectError: false, 24856 podSpec: &core.PodSpec{OS: &core.PodOS{Name: core.Windows}}, 24857 }, { 24858 name: "OS field, featuregate, empty OS", 24859 expectError: true, 24860 podSpec: &core.PodSpec{OS: &core.PodOS{Name: ""}}, 24861 }, { 24862 name: "OS field, featuregate, invalid OS", 24863 expectError: true, 24864 podSpec: &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}}, 24865 }, 24866 } 24867 for _, testCase := range testCases { 24868 t.Run(testCase.name, func(t *testing.T) { 24869 errs := validateOS(testCase.podSpec, field.NewPath("spec"), PodValidationOptions{}) 24870 if testCase.expectError && len(errs) == 0 { 24871 t.Errorf("Unexpected success") 24872 } 24873 if !testCase.expectError && len(errs) != 0 { 24874 t.Errorf("Unexpected error(s): %v", errs) 24875 } 24876 }) 24877 } 24878 } 24879 24880 func TestValidateAppArmorProfileFormat(t *testing.T) { 24881 tests := []struct { 24882 profile string 24883 expectValid bool 24884 }{ 24885 {"", true}, 24886 {v1.AppArmorBetaProfileRuntimeDefault, true}, 24887 {v1.AppArmorBetaProfileNameUnconfined, true}, 24888 {"baz", false}, // Missing local prefix. 24889 {v1.AppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true}, 24890 {v1.AppArmorBetaProfileNamePrefix + "foo-bar", true}, 24891 } 24892 24893 for _, test := range tests { 24894 err := ValidateAppArmorProfileFormat(test.profile) 24895 if test.expectValid { 24896 assert.NoError(t, err, "Profile %s should be valid", test.profile) 24897 } else { 24898 assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile)) 24899 } 24900 } 24901 } 24902 24903 func TestValidateDownwardAPIHostIPs(t *testing.T) { 24904 testCases := []struct { 24905 name string 24906 expectError bool 24907 featureEnabled bool 24908 fieldSel *core.ObjectFieldSelector 24909 }{ 24910 { 24911 name: "has no hostIPs field, featuregate enabled", 24912 expectError: false, 24913 featureEnabled: true, 24914 fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIP"}, 24915 }, 24916 { 24917 name: "has hostIPs field, featuregate enabled", 24918 expectError: false, 24919 featureEnabled: true, 24920 fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIPs"}, 24921 }, 24922 { 24923 name: "has no hostIPs field, featuregate disabled", 24924 expectError: false, 24925 featureEnabled: false, 24926 fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIP"}, 24927 }, 24928 { 24929 name: "has hostIPs field, featuregate disabled", 24930 expectError: true, 24931 featureEnabled: false, 24932 fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIPs"}, 24933 }, 24934 } 24935 for _, testCase := range testCases { 24936 t.Run(testCase.name, func(t *testing.T) { 24937 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, testCase.featureEnabled)() 24938 24939 errs := validateDownwardAPIHostIPs(testCase.fieldSel, field.NewPath("fieldSel"), PodValidationOptions{AllowHostIPsField: testCase.featureEnabled}) 24940 if testCase.expectError && len(errs) == 0 { 24941 t.Errorf("Unexpected success") 24942 } 24943 if !testCase.expectError && len(errs) != 0 { 24944 t.Errorf("Unexpected error(s): %v", errs) 24945 } 24946 }) 24947 } 24948 } 24949 24950 func TestValidatePVSecretReference(t *testing.T) { 24951 rootFld := field.NewPath("name") 24952 type args struct { 24953 secretRef *core.SecretReference 24954 fldPath *field.Path 24955 } 24956 tests := []struct { 24957 name string 24958 args args 24959 expectError bool 24960 expectedError string 24961 }{{ 24962 name: "invalid secret ref name", 24963 args: args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld}, 24964 expectError: true, 24965 expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg, 24966 }, { 24967 name: "invalid secret ref namespace", 24968 args: args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld}, 24969 expectError: true, 24970 expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg, 24971 }, { 24972 name: "invalid secret: missing namespace", 24973 args: args{&core.SecretReference{Name: "valid"}, rootFld}, 24974 expectError: true, 24975 expectedError: "name.namespace: Required value", 24976 }, { 24977 name: "invalid secret : missing name", 24978 args: args{&core.SecretReference{Namespace: "default"}, rootFld}, 24979 expectError: true, 24980 expectedError: "name.name: Required value", 24981 }, { 24982 name: "valid secret", 24983 args: args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld}, 24984 expectError: false, 24985 expectedError: "", 24986 }, 24987 } 24988 for _, tt := range tests { 24989 t.Run(tt.name, func(t *testing.T) { 24990 errs := validatePVSecretReference(tt.args.secretRef, tt.args.fldPath) 24991 if tt.expectError && len(errs) == 0 { 24992 t.Errorf("Unexpected success") 24993 } 24994 if tt.expectError && len(errs) != 0 { 24995 str := errs[0].Error() 24996 if str != "" && !strings.Contains(str, tt.expectedError) { 24997 t.Errorf("%s: expected error detail either empty or %q, got %q", tt.name, tt.expectedError, str) 24998 } 24999 } 25000 if !tt.expectError && len(errs) != 0 { 25001 t.Errorf("Unexpected error(s): %v", errs) 25002 } 25003 }) 25004 } 25005 } 25006 25007 func TestValidateDynamicResourceAllocation(t *testing.T) { 25008 externalClaimName := "some-claim" 25009 externalClaimTemplateName := "some-claim-template" 25010 goodClaimSource := core.ClaimSource{ 25011 ResourceClaimName: &externalClaimName, 25012 } 25013 shortPodName := &metav1.ObjectMeta{ 25014 Name: "some-pod", 25015 } 25016 brokenPodName := &metav1.ObjectMeta{ 25017 Name: ".dot.com", 25018 } 25019 goodClaimTemplate := core.PodSpec{ 25020 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-template"}}}}}, 25021 RestartPolicy: core.RestartPolicyAlways, 25022 DNSPolicy: core.DNSClusterFirst, 25023 ResourceClaims: []core.PodResourceClaim{{ 25024 Name: "my-claim-template", 25025 Source: core.ClaimSource{ 25026 ResourceClaimTemplateName: &externalClaimTemplateName, 25027 }, 25028 }}, 25029 } 25030 goodClaimReference := core.PodSpec{ 25031 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-reference"}}}}}, 25032 RestartPolicy: core.RestartPolicyAlways, 25033 DNSPolicy: core.DNSClusterFirst, 25034 ResourceClaims: []core.PodResourceClaim{{ 25035 Name: "my-claim-reference", 25036 Source: core.ClaimSource{ 25037 ResourceClaimName: &externalClaimName, 25038 }, 25039 }}, 25040 } 25041 25042 successCases := map[string]core.PodSpec{ 25043 "resource claim reference": goodClaimTemplate, 25044 "resource claim template": goodClaimTemplate, 25045 "multiple claims": { 25046 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}}, 25047 RestartPolicy: core.RestartPolicyAlways, 25048 DNSPolicy: core.DNSClusterFirst, 25049 ResourceClaims: []core.PodResourceClaim{{ 25050 Name: "my-claim", 25051 Source: goodClaimSource, 25052 }, { 25053 Name: "another-claim", 25054 Source: goodClaimSource, 25055 }}, 25056 }, 25057 "init container": { 25058 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25059 InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25060 RestartPolicy: core.RestartPolicyAlways, 25061 DNSPolicy: core.DNSClusterFirst, 25062 ResourceClaims: []core.PodResourceClaim{{ 25063 Name: "my-claim", 25064 Source: goodClaimSource, 25065 }}, 25066 }, 25067 } 25068 for k, v := range successCases { 25069 t.Run(k, func(t *testing.T) { 25070 if errs := ValidatePodSpec(&v, shortPodName, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 { 25071 t.Errorf("expected success: %v", errs) 25072 } 25073 }) 25074 } 25075 25076 failureCases := map[string]core.PodSpec{ 25077 "pod claim name with prefix": { 25078 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 25079 RestartPolicy: core.RestartPolicyAlways, 25080 DNSPolicy: core.DNSClusterFirst, 25081 ResourceClaims: []core.PodResourceClaim{{ 25082 Name: "../my-claim", 25083 Source: goodClaimSource, 25084 }}, 25085 }, 25086 "pod claim name with path": { 25087 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 25088 RestartPolicy: core.RestartPolicyAlways, 25089 DNSPolicy: core.DNSClusterFirst, 25090 ResourceClaims: []core.PodResourceClaim{{ 25091 Name: "my/claim", 25092 Source: goodClaimSource, 25093 }}, 25094 }, 25095 "pod claim name empty": { 25096 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 25097 RestartPolicy: core.RestartPolicyAlways, 25098 DNSPolicy: core.DNSClusterFirst, 25099 ResourceClaims: []core.PodResourceClaim{{ 25100 Name: "", 25101 Source: goodClaimSource, 25102 }}, 25103 }, 25104 "duplicate pod claim entries": { 25105 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, 25106 RestartPolicy: core.RestartPolicyAlways, 25107 DNSPolicy: core.DNSClusterFirst, 25108 ResourceClaims: []core.PodResourceClaim{{ 25109 Name: "my-claim", 25110 Source: goodClaimSource, 25111 }, { 25112 Name: "my-claim", 25113 Source: goodClaimSource, 25114 }}, 25115 }, 25116 "resource claim source empty": { 25117 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25118 RestartPolicy: core.RestartPolicyAlways, 25119 DNSPolicy: core.DNSClusterFirst, 25120 ResourceClaims: []core.PodResourceClaim{{ 25121 Name: "my-claim", 25122 Source: core.ClaimSource{}, 25123 }}, 25124 }, 25125 "resource claim reference and template": { 25126 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25127 RestartPolicy: core.RestartPolicyAlways, 25128 DNSPolicy: core.DNSClusterFirst, 25129 ResourceClaims: []core.PodResourceClaim{{ 25130 Name: "my-claim", 25131 Source: core.ClaimSource{ 25132 ResourceClaimName: &externalClaimName, 25133 ResourceClaimTemplateName: &externalClaimTemplateName, 25134 }, 25135 }}, 25136 }, 25137 "claim not found": { 25138 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}}, 25139 RestartPolicy: core.RestartPolicyAlways, 25140 DNSPolicy: core.DNSClusterFirst, 25141 ResourceClaims: []core.PodResourceClaim{{ 25142 Name: "my-claim", 25143 Source: goodClaimSource, 25144 }}, 25145 }, 25146 "claim name empty": { 25147 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}}, 25148 RestartPolicy: core.RestartPolicyAlways, 25149 DNSPolicy: core.DNSClusterFirst, 25150 ResourceClaims: []core.PodResourceClaim{{ 25151 Name: "my-claim", 25152 Source: goodClaimSource, 25153 }}, 25154 }, 25155 "pod claim name duplicates": { 25156 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}}, 25157 RestartPolicy: core.RestartPolicyAlways, 25158 DNSPolicy: core.DNSClusterFirst, 25159 ResourceClaims: []core.PodResourceClaim{{ 25160 Name: "my-claim", 25161 Source: goodClaimSource, 25162 }}, 25163 }, 25164 "no claims defined": { 25165 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25166 RestartPolicy: core.RestartPolicyAlways, 25167 DNSPolicy: core.DNSClusterFirst, 25168 }, 25169 "duplicate pod claim name": { 25170 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25171 RestartPolicy: core.RestartPolicyAlways, 25172 DNSPolicy: core.DNSClusterFirst, 25173 ResourceClaims: []core.PodResourceClaim{{ 25174 Name: "my-claim", 25175 Source: goodClaimSource, 25176 }, { 25177 Name: "my-claim", 25178 Source: goodClaimSource, 25179 }}, 25180 }, 25181 "ephemeral container don't support resource requirements": { 25182 Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}}, 25183 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"}}, 25184 RestartPolicy: core.RestartPolicyAlways, 25185 DNSPolicy: core.DNSClusterFirst, 25186 ResourceClaims: []core.PodResourceClaim{{ 25187 Name: "my-claim", 25188 Source: goodClaimSource, 25189 }}, 25190 }, 25191 "invalid claim template name": func() core.PodSpec { 25192 spec := goodClaimTemplate.DeepCopy() 25193 notLabel := ".foo_bar" 25194 spec.ResourceClaims[0].Source.ResourceClaimTemplateName = ¬Label 25195 return *spec 25196 }(), 25197 "invalid claim reference name": func() core.PodSpec { 25198 spec := goodClaimReference.DeepCopy() 25199 notLabel := ".foo_bar" 25200 spec.ResourceClaims[0].Source.ResourceClaimName = ¬Label 25201 return *spec 25202 }(), 25203 } 25204 for k, v := range failureCases { 25205 if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 { 25206 t.Errorf("expected failure for %q", k) 25207 } 25208 } 25209 25210 t.Run("generated-claim-name", func(t *testing.T) { 25211 for _, spec := range []*core.PodSpec{&goodClaimTemplate, &goodClaimReference} { 25212 claimName := spec.ResourceClaims[0].Name 25213 t.Run(claimName, func(t *testing.T) { 25214 for _, podMeta := range []*metav1.ObjectMeta{shortPodName, brokenPodName} { 25215 t.Run(podMeta.Name, func(t *testing.T) { 25216 errs := ValidatePodSpec(spec, podMeta, field.NewPath("field"), PodValidationOptions{}) 25217 // Only one out of the four combinations fails. 25218 expectError := spec == &goodClaimTemplate && podMeta == brokenPodName 25219 if expectError && len(errs) == 0 { 25220 t.Error("did not get the expected failure") 25221 } 25222 if !expectError && len(errs) > 0 { 25223 t.Errorf("unexpected failures: %+v", errs) 25224 } 25225 }) 25226 } 25227 }) 25228 } 25229 }) 25230 } 25231 25232 func TestValidateLoadBalancerStatus(t *testing.T) { 25233 ipModeVIP := core.LoadBalancerIPModeVIP 25234 ipModeProxy := core.LoadBalancerIPModeProxy 25235 ipModeDummy := core.LoadBalancerIPMode("dummy") 25236 25237 testCases := []struct { 25238 name string 25239 ipModeEnabled bool 25240 nonLBAllowed bool 25241 tweakLBStatus func(s *core.LoadBalancerStatus) 25242 tweakSvcSpec func(s *core.ServiceSpec) 25243 numErrs int 25244 }{ 25245 { 25246 name: "type is not LB", 25247 nonLBAllowed: false, 25248 tweakSvcSpec: func(s *core.ServiceSpec) { 25249 s.Type = core.ServiceTypeClusterIP 25250 }, 25251 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25252 s.Ingress = []core.LoadBalancerIngress{{ 25253 IP: "1.2.3.4", 25254 }} 25255 }, 25256 numErrs: 1, 25257 }, { 25258 name: "type is not LB. back-compat", 25259 nonLBAllowed: true, 25260 tweakSvcSpec: func(s *core.ServiceSpec) { 25261 s.Type = core.ServiceTypeClusterIP 25262 }, 25263 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25264 s.Ingress = []core.LoadBalancerIngress{{ 25265 IP: "1.2.3.4", 25266 }} 25267 }, 25268 numErrs: 0, 25269 }, { 25270 name: "valid vip ipMode", 25271 ipModeEnabled: true, 25272 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25273 s.Ingress = []core.LoadBalancerIngress{{ 25274 IP: "1.2.3.4", 25275 IPMode: &ipModeVIP, 25276 }} 25277 }, 25278 numErrs: 0, 25279 }, { 25280 name: "valid proxy ipMode", 25281 ipModeEnabled: true, 25282 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25283 s.Ingress = []core.LoadBalancerIngress{{ 25284 IP: "1.2.3.4", 25285 IPMode: &ipModeProxy, 25286 }} 25287 }, 25288 numErrs: 0, 25289 }, { 25290 name: "invalid ipMode", 25291 ipModeEnabled: true, 25292 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25293 s.Ingress = []core.LoadBalancerIngress{{ 25294 IP: "1.2.3.4", 25295 IPMode: &ipModeDummy, 25296 }} 25297 }, 25298 numErrs: 1, 25299 }, { 25300 name: "missing ipMode with LoadbalancerIPMode enabled", 25301 ipModeEnabled: true, 25302 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25303 s.Ingress = []core.LoadBalancerIngress{{ 25304 IP: "1.2.3.4", 25305 }} 25306 }, 25307 numErrs: 1, 25308 }, { 25309 name: "missing ipMode with LoadbalancerIPMode disabled", 25310 ipModeEnabled: false, 25311 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25312 s.Ingress = []core.LoadBalancerIngress{{ 25313 IP: "1.2.3.4", 25314 }} 25315 }, 25316 numErrs: 0, 25317 }, { 25318 name: "missing ip with ipMode present", 25319 ipModeEnabled: true, 25320 tweakLBStatus: func(s *core.LoadBalancerStatus) { 25321 s.Ingress = []core.LoadBalancerIngress{{ 25322 IPMode: &ipModeProxy, 25323 }} 25324 }, 25325 numErrs: 1, 25326 }, 25327 } 25328 for _, tc := range testCases { 25329 t.Run(tc.name, func(t *testing.T) { 25330 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled)() 25331 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowServiceLBStatusOnNonLB, tc.nonLBAllowed)() 25332 status := core.LoadBalancerStatus{} 25333 tc.tweakLBStatus(&status) 25334 spec := core.ServiceSpec{Type: core.ServiceTypeLoadBalancer} 25335 if tc.tweakSvcSpec != nil { 25336 tc.tweakSvcSpec(&spec) 25337 } 25338 errs := ValidateLoadBalancerStatus(&status, field.NewPath("status"), &spec) 25339 if len(errs) != tc.numErrs { 25340 t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate()) 25341 } 25342 }) 25343 } 25344 } 25345 25346 func TestValidateSleepAction(t *testing.T) { 25347 fldPath := field.NewPath("root") 25348 getInvalidStr := func(gracePeriod int64) string { 25349 return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod) 25350 } 25351 25352 testCases := []struct { 25353 name string 25354 action *core.SleepAction 25355 gracePeriod int64 25356 expectErr field.ErrorList 25357 }{ 25358 { 25359 name: "valid setting", 25360 action: &core.SleepAction{ 25361 Seconds: 5, 25362 }, 25363 gracePeriod: 30, 25364 }, 25365 { 25366 name: "negative seconds", 25367 action: &core.SleepAction{ 25368 Seconds: -1, 25369 }, 25370 gracePeriod: 30, 25371 expectErr: field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))}, 25372 }, 25373 { 25374 name: "longer than gracePeriod", 25375 action: &core.SleepAction{ 25376 Seconds: 5, 25377 }, 25378 gracePeriod: 3, 25379 expectErr: field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))}, 25380 }, 25381 } 25382 25383 for _, tc := range testCases { 25384 t.Run(tc.name, func(t *testing.T) { 25385 errs := validateSleepAction(tc.action, tc.gracePeriod, fldPath) 25386 25387 if len(tc.expectErr) > 0 && len(errs) == 0 { 25388 t.Errorf("Unexpected success") 25389 } else if len(tc.expectErr) == 0 && len(errs) != 0 { 25390 t.Errorf("Unexpected error(s): %v", errs) 25391 } else if len(tc.expectErr) > 0 { 25392 if tc.expectErr[0].Error() != errs[0].Error() { 25393 t.Errorf("Unexpected error(s): %v", errs) 25394 } 25395 } 25396 }) 25397 } 25398 }