k8s.io/kubernetes@v1.29.3/pkg/api/pod/warnings_test.go (about) 1 /* 2 Copyright 2021 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 pod 18 19 import ( 20 "context" 21 "testing" 22 23 "k8s.io/apimachinery/pkg/api/resource" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/util/sets" 26 api "k8s.io/kubernetes/pkg/apis/core" 27 utilpointer "k8s.io/utils/pointer" 28 ) 29 30 func BenchmarkNoWarnings(b *testing.B) { 31 ctx := context.TODO() 32 resources := api.ResourceList{ 33 api.ResourceCPU: resource.MustParse("100m"), 34 api.ResourceMemory: resource.MustParse("4M"), 35 api.ResourceEphemeralStorage: resource.MustParse("4G"), 36 } 37 env := []api.EnvVar{ 38 {Name: "a"}, 39 {Name: "b"}, 40 } 41 pod := &api.Pod{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Annotations: map[string]string{`foo`: `bar`}, 44 }, 45 Spec: api.PodSpec{ 46 NodeSelector: map[string]string{"foo": "bar", "baz": "quux"}, 47 Affinity: &api.Affinity{ 48 NodeAffinity: &api.NodeAffinity{ 49 RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ 50 NodeSelectorTerms: []api.NodeSelectorTerm{ 51 {MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}}, 52 }, 53 }, 54 PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ 55 {Preference: api.NodeSelectorTerm{MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}}}, 56 }, 57 }, 58 }, 59 TopologySpreadConstraints: []api.TopologySpreadConstraint{ 60 {TopologyKey: `foo`}, 61 }, 62 HostAliases: []api.HostAlias{ 63 {IP: "1.1.1.1"}, 64 {IP: "2.2.2.2"}, 65 }, 66 ImagePullSecrets: []api.LocalObjectReference{ 67 {Name: "secret1"}, 68 {Name: "secret2"}, 69 }, 70 InitContainers: []api.Container{ 71 {Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 72 {Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 73 }, 74 Containers: []api.Container{ 75 {Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 76 {Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 77 }, 78 Overhead: resources, 79 Volumes: []api.Volume{ 80 {Name: "a"}, 81 {Name: "b"}, 82 }, 83 }, 84 } 85 oldPod := &api.Pod{} 86 b.ResetTimer() 87 for i := 0; i < b.N; i++ { 88 w := GetWarningsForPod(ctx, pod, oldPod) 89 if len(w) > 0 { 90 b.Fatalf("expected 0 warnings, got %q", w) 91 } 92 } 93 } 94 95 func BenchmarkWarnings(b *testing.B) { 96 ctx := context.TODO() 97 resources := api.ResourceList{ 98 api.ResourceCPU: resource.MustParse("100m"), 99 api.ResourceMemory: resource.MustParse("4m"), 100 api.ResourceEphemeralStorage: resource.MustParse("4m"), 101 } 102 env := []api.EnvVar{ 103 {Name: "a"}, 104 {Name: "a"}, 105 } 106 pod := &api.Pod{ 107 Spec: api.PodSpec{ 108 HostAliases: []api.HostAlias{ 109 {IP: "1.1.1.1"}, 110 {IP: "1.1.1.1"}, 111 }, 112 ImagePullSecrets: []api.LocalObjectReference{ 113 {Name: "secret1"}, 114 {Name: "secret1"}, 115 {Name: ""}, 116 }, 117 InitContainers: []api.Container{ 118 {Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 119 {Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 120 }, 121 Containers: []api.Container{ 122 {Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 123 {Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, 124 }, 125 Overhead: resources, 126 Volumes: []api.Volume{ 127 {Name: "a"}, 128 {Name: "a"}, 129 }, 130 }, 131 } 132 oldPod := &api.Pod{} 133 b.ResetTimer() 134 for i := 0; i < b.N; i++ { 135 GetWarningsForPod(ctx, pod, oldPod) 136 } 137 } 138 139 func TestWarnings(t *testing.T) { 140 resources := api.ResourceList{ 141 api.ResourceCPU: resource.MustParse("100m"), 142 api.ResourceMemory: resource.MustParse("4m"), 143 api.ResourceEphemeralStorage: resource.MustParse("4m"), 144 } 145 testcases := []struct { 146 name string 147 template *api.PodTemplateSpec 148 oldTemplate *api.PodTemplateSpec 149 expected []string 150 }{ 151 { 152 name: "null", 153 template: nil, 154 expected: nil, 155 }, 156 { 157 name: "photon", 158 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 159 Volumes: []api.Volume{ 160 {Name: "p", VolumeSource: api.VolumeSource{PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{}}}, 161 }}, 162 }, 163 expected: []string{`spec.volumes[0].photonPersistentDisk: deprecated in v1.11, non-functional in v1.16+`}, 164 }, 165 { 166 name: "gitRepo", 167 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 168 Volumes: []api.Volume{ 169 {Name: "s", VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{}}}, 170 }}, 171 }, 172 expected: []string{`spec.volumes[0].gitRepo: deprecated in v1.11`}, 173 }, 174 { 175 name: "scaleIO", 176 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 177 Volumes: []api.Volume{ 178 {Name: "s", VolumeSource: api.VolumeSource{ScaleIO: &api.ScaleIOVolumeSource{}}}, 179 }}, 180 }, 181 expected: []string{`spec.volumes[0].scaleIO: deprecated in v1.16, non-functional in v1.22+`}, 182 }, 183 { 184 name: "flocker", 185 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 186 Volumes: []api.Volume{ 187 {Name: "s", VolumeSource: api.VolumeSource{Flocker: &api.FlockerVolumeSource{}}}, 188 }}, 189 }, 190 expected: []string{`spec.volumes[0].flocker: deprecated in v1.22, non-functional in v1.25+`}, 191 }, 192 { 193 name: "storageOS", 194 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 195 Volumes: []api.Volume{ 196 {Name: "s", VolumeSource: api.VolumeSource{StorageOS: &api.StorageOSVolumeSource{}}}, 197 }}, 198 }, 199 expected: []string{`spec.volumes[0].storageOS: deprecated in v1.22, non-functional in v1.25+`}, 200 }, 201 { 202 name: "quobyte", 203 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 204 Volumes: []api.Volume{ 205 {Name: "s", VolumeSource: api.VolumeSource{Quobyte: &api.QuobyteVolumeSource{}}}, 206 }}, 207 }, 208 expected: []string{`spec.volumes[0].quobyte: deprecated in v1.22, non-functional in v1.25+`}, 209 }, 210 { 211 name: "glusterfs", 212 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 213 Volumes: []api.Volume{ 214 {Name: "s", VolumeSource: api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{}}}, 215 }}, 216 }, 217 expected: []string{`spec.volumes[0].glusterfs: deprecated in v1.25, non-functional in v1.26+`}, 218 }, { 219 name: "CephFS", 220 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 221 Volumes: []api.Volume{ 222 {Name: "s", VolumeSource: api.VolumeSource{CephFS: &api.CephFSVolumeSource{}}}, 223 }}, 224 }, 225 expected: []string{`spec.volumes[0].cephfs: deprecated in v1.28, non-functional in v1.31+`}, 226 }, 227 228 { 229 name: "rbd", 230 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 231 Volumes: []api.Volume{ 232 {Name: "s", VolumeSource: api.VolumeSource{RBD: &api.RBDVolumeSource{}}}, 233 }}, 234 }, 235 expected: []string{`spec.volumes[0].rbd: deprecated in v1.28, non-functional in v1.31+`}, 236 }, 237 { 238 name: "duplicate hostAlias", 239 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 240 HostAliases: []api.HostAlias{ 241 {IP: "1.1.1.1"}, 242 {IP: "1.1.1.1"}, 243 {IP: "1.1.1.1"}, 244 }}, 245 }, 246 expected: []string{ 247 `spec.hostAliases[1].ip: duplicate ip "1.1.1.1"`, 248 `spec.hostAliases[2].ip: duplicate ip "1.1.1.1"`, 249 }, 250 }, 251 { 252 name: "duplicate imagePullSecret", 253 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 254 ImagePullSecrets: []api.LocalObjectReference{ 255 {Name: "a"}, 256 {Name: "a"}, 257 {Name: "a"}, 258 }}, 259 }, 260 expected: []string{ 261 `spec.imagePullSecrets[1].name: duplicate name "a"`, 262 `spec.imagePullSecrets[2].name: duplicate name "a"`, 263 }, 264 }, 265 { 266 name: "empty imagePullSecret", 267 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 268 ImagePullSecrets: []api.LocalObjectReference{ 269 {Name: ""}, 270 }}, 271 }, 272 expected: []string{ 273 `spec.imagePullSecrets[0].name: invalid empty name ""`, 274 }, 275 }, 276 { 277 name: "duplicate env", 278 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 279 InitContainers: []api.Container{{Env: []api.EnvVar{ 280 {Name: "a", Value: "a"}, 281 {Name: "a", Value: "a"}, 282 {Name: "a", Value: "other"}, 283 {Name: "a", Value: ""}, 284 {Name: "a", Value: "$(a)"}, 285 {Name: "a", ValueFrom: &api.EnvVarSource{}}, 286 {Name: "a", Value: "$(a) $(a)"}, // no warning 287 }}}, 288 Containers: []api.Container{{Env: []api.EnvVar{ 289 {Name: "b", Value: "b"}, 290 {Name: "b", Value: "b"}, 291 {Name: "b", Value: "other"}, 292 {Name: "b", Value: ""}, 293 {Name: "b", Value: "$(b)"}, 294 {Name: "b", ValueFrom: &api.EnvVarSource{}}, 295 {Name: "b", Value: "$(b) $(b)"}, // no warning 296 }}}, 297 }}, 298 expected: []string{ 299 `spec.initContainers[0].env[1]: hides previous definition of "a"`, 300 `spec.initContainers[0].env[2]: hides previous definition of "a"`, 301 `spec.initContainers[0].env[3]: hides previous definition of "a"`, 302 `spec.initContainers[0].env[4]: hides previous definition of "a"`, 303 `spec.initContainers[0].env[5]: hides previous definition of "a"`, 304 `spec.containers[0].env[1]: hides previous definition of "b"`, 305 `spec.containers[0].env[2]: hides previous definition of "b"`, 306 `spec.containers[0].env[3]: hides previous definition of "b"`, 307 `spec.containers[0].env[4]: hides previous definition of "b"`, 308 `spec.containers[0].env[5]: hides previous definition of "b"`, 309 }, 310 }, 311 { 312 name: "fractional resources", 313 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 314 InitContainers: []api.Container{{ 315 Resources: api.ResourceRequirements{Requests: resources, Limits: resources}, 316 }}, 317 Containers: []api.Container{{ 318 Resources: api.ResourceRequirements{Requests: resources, Limits: resources}, 319 }}, 320 Overhead: resources, 321 }}, 322 expected: []string{ 323 `spec.initContainers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, 324 `spec.initContainers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`, 325 `spec.initContainers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, 326 `spec.initContainers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`, 327 `spec.containers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, 328 `spec.containers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`, 329 `spec.containers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, 330 `spec.containers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`, 331 `spec.overhead[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, 332 `spec.overhead[memory]: fractional byte value "4m" is invalid, must be an integer`, 333 }, 334 }, 335 { 336 name: "node labels in nodeSelector", 337 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 338 NodeSelector: map[string]string{ 339 `beta.kubernetes.io/arch`: `true`, 340 `beta.kubernetes.io/os`: `true`, 341 `failure-domain.beta.kubernetes.io/region`: `true`, 342 `failure-domain.beta.kubernetes.io/zone`: `true`, 343 `beta.kubernetes.io/instance-type`: `true`, 344 }, 345 }}, 346 expected: []string{ 347 `spec.nodeSelector[beta.kubernetes.io/arch]: deprecated since v1.14; use "kubernetes.io/arch" instead`, 348 `spec.nodeSelector[beta.kubernetes.io/instance-type]: deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, 349 `spec.nodeSelector[beta.kubernetes.io/os]: deprecated since v1.14; use "kubernetes.io/os" instead`, 350 `spec.nodeSelector[failure-domain.beta.kubernetes.io/region]: deprecated since v1.17; use "topology.kubernetes.io/region" instead`, 351 `spec.nodeSelector[failure-domain.beta.kubernetes.io/zone]: deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, 352 }, 353 }, 354 { 355 name: "node labels in affinity requiredDuringSchedulingIgnoredDuringExecution", 356 template: &api.PodTemplateSpec{ 357 Spec: api.PodSpec{ 358 Affinity: &api.Affinity{ 359 NodeAffinity: &api.NodeAffinity{ 360 RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ 361 NodeSelectorTerms: []api.NodeSelectorTerm{ 362 { 363 MatchExpressions: []api.NodeSelectorRequirement{ 364 {Key: `foo`}, 365 {Key: `beta.kubernetes.io/arch`}, 366 {Key: `beta.kubernetes.io/os`}, 367 {Key: `failure-domain.beta.kubernetes.io/region`}, 368 {Key: `failure-domain.beta.kubernetes.io/zone`}, 369 {Key: `beta.kubernetes.io/instance-type`}, 370 }, 371 }, 372 }, 373 }, 374 }, 375 }, 376 }, 377 }, 378 expected: []string{ 379 `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`, 380 `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`, 381 `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[3].key: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`, 382 `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[4].key: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, 383 `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[5].key: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, 384 }, 385 }, 386 { 387 name: "node labels in affinity preferredDuringSchedulingIgnoredDuringExecution", 388 template: &api.PodTemplateSpec{ 389 Spec: api.PodSpec{ 390 Affinity: &api.Affinity{ 391 NodeAffinity: &api.NodeAffinity{ 392 PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ 393 { 394 Preference: api.NodeSelectorTerm{ 395 MatchExpressions: []api.NodeSelectorRequirement{ 396 {Key: `foo`}, 397 {Key: `beta.kubernetes.io/arch`}, 398 {Key: `beta.kubernetes.io/os`}, 399 {Key: `failure-domain.beta.kubernetes.io/region`}, 400 {Key: `failure-domain.beta.kubernetes.io/zone`}, 401 {Key: `beta.kubernetes.io/instance-type`}, 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 }, 410 expected: []string{ 411 `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`, 412 `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`, 413 `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[3].key: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`, 414 `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[4].key: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, 415 `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[5].key: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, 416 }, 417 }, 418 { 419 name: "node labels in topologySpreadConstraints", 420 template: &api.PodTemplateSpec{ 421 Spec: api.PodSpec{ 422 TopologySpreadConstraints: []api.TopologySpreadConstraint{ 423 { 424 TopologyKey: `foo`, 425 LabelSelector: &metav1.LabelSelector{}, 426 }, 427 { 428 TopologyKey: `beta.kubernetes.io/arch`, 429 LabelSelector: &metav1.LabelSelector{}, 430 }, 431 { 432 TopologyKey: `beta.kubernetes.io/os`, 433 LabelSelector: &metav1.LabelSelector{}, 434 }, 435 { 436 TopologyKey: `failure-domain.beta.kubernetes.io/region`, 437 LabelSelector: &metav1.LabelSelector{}, 438 }, 439 { 440 TopologyKey: `failure-domain.beta.kubernetes.io/zone`, 441 LabelSelector: &metav1.LabelSelector{}, 442 }, 443 { 444 TopologyKey: `beta.kubernetes.io/instance-type`, 445 LabelSelector: &metav1.LabelSelector{}, 446 }, 447 }, 448 }, 449 }, 450 expected: []string{ 451 `spec.topologySpreadConstraints[1].topologyKey: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`, 452 `spec.topologySpreadConstraints[2].topologyKey: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`, 453 `spec.topologySpreadConstraints[3].topologyKey: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`, 454 `spec.topologySpreadConstraints[4].topologyKey: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, 455 `spec.topologySpreadConstraints[5].topologyKey: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, 456 }, 457 }, 458 { 459 name: "annotations", 460 template: &api.PodTemplateSpec{ 461 ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ 462 `foo`: `bar`, 463 `scheduler.alpha.kubernetes.io/critical-pod`: `true`, 464 `seccomp.security.alpha.kubernetes.io/pod`: `default`, 465 `container.seccomp.security.alpha.kubernetes.io/foo`: `default`, 466 `security.alpha.kubernetes.io/sysctls`: `a,b,c`, 467 `security.alpha.kubernetes.io/unsafe-sysctls`: `d,e,f`, 468 }}, 469 Spec: api.PodSpec{Containers: []api.Container{{Name: "foo"}}}, 470 }, 471 expected: []string{ 472 `metadata.annotations[scheduler.alpha.kubernetes.io/critical-pod]: non-functional in v1.16+; use the "priorityClassName" field instead`, 473 `metadata.annotations[seccomp.security.alpha.kubernetes.io/pod]: non-functional in v1.27+; use the "seccompProfile" field instead`, 474 `metadata.annotations[container.seccomp.security.alpha.kubernetes.io/foo]: non-functional in v1.27+; use the "seccompProfile" field instead`, 475 `metadata.annotations[security.alpha.kubernetes.io/sysctls]: non-functional in v1.11+; use the "sysctls" field instead`, 476 `metadata.annotations[security.alpha.kubernetes.io/unsafe-sysctls]: non-functional in v1.11+; use the "sysctls" field instead`, 477 }, 478 }, 479 { 480 name: "seccomp fields", 481 template: &api.PodTemplateSpec{ 482 ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ 483 `seccomp.security.alpha.kubernetes.io/pod`: `default`, 484 `container.seccomp.security.alpha.kubernetes.io/foo`: `default`, 485 }}, 486 Spec: api.PodSpec{ 487 SecurityContext: &api.PodSecurityContext{ 488 SeccompProfile: &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault}, 489 }, 490 Containers: []api.Container{{ 491 Name: "foo", 492 SecurityContext: &api.SecurityContext{ 493 SeccompProfile: &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault}, 494 }, 495 }}, 496 }, 497 }, 498 expected: []string{}, 499 }, 500 { 501 name: "pod with ephemeral volume source 200Mi", 502 template: &api.PodTemplateSpec{ 503 ObjectMeta: metav1.ObjectMeta{}, 504 Spec: api.PodSpec{Volumes: []api.Volume{ 505 {Name: "ephemeral-volume", VolumeSource: api.VolumeSource{Ephemeral: &api.EphemeralVolumeSource{ 506 VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{ 507 Spec: api.PersistentVolumeClaimSpec{Resources: api.VolumeResourceRequirements{ 508 Requests: api.ResourceList{api.ResourceStorage: resource.MustParse("200Mi")}}}, 509 }, 510 }}}}}, 511 }, 512 expected: []string{}, 513 }, 514 { 515 name: "pod with ephemeral volume source 200m", 516 template: &api.PodTemplateSpec{ 517 ObjectMeta: metav1.ObjectMeta{}, 518 Spec: api.PodSpec{Volumes: []api.Volume{ 519 {Name: "ephemeral-volume", VolumeSource: api.VolumeSource{Ephemeral: &api.EphemeralVolumeSource{ 520 VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{ 521 Spec: api.PersistentVolumeClaimSpec{Resources: api.VolumeResourceRequirements{ 522 Requests: api.ResourceList{api.ResourceStorage: resource.MustParse("200m")}}}, 523 }, 524 }}}}}, 525 }, 526 expected: []string{ 527 `spec.volumes[0].ephemeral.volumeClaimTemplate.spec.resources.requests[storage]: fractional byte value "200m" is invalid, must be an integer`, 528 }, 529 }, 530 { 531 name: "terminationGracePeriodSeconds is negative", 532 template: &api.PodTemplateSpec{ 533 ObjectMeta: metav1.ObjectMeta{}, 534 Spec: api.PodSpec{ 535 TerminationGracePeriodSeconds: utilpointer.Int64Ptr(-1), 536 }, 537 }, 538 expected: []string{ 539 `spec.terminationGracePeriodSeconds: must be >= 0; negative values are invalid and will be treated as 1`, 540 }, 541 }, 542 { 543 name: "null LabelSelector in topologySpreadConstraints", 544 template: &api.PodTemplateSpec{ 545 ObjectMeta: metav1.ObjectMeta{}, 546 Spec: api.PodSpec{ 547 TopologySpreadConstraints: []api.TopologySpreadConstraint{ 548 { 549 LabelSelector: &metav1.LabelSelector{}, 550 }, 551 { 552 LabelSelector: nil, 553 }, 554 }, 555 }, 556 }, 557 expected: []string{ 558 `spec.topologySpreadConstraints[1].labelSelector: a null labelSelector results in matching no pod`, 559 }, 560 }, 561 { 562 name: "null LabelSelector in PodAffinity", 563 template: &api.PodTemplateSpec{ 564 ObjectMeta: metav1.ObjectMeta{}, 565 Spec: api.PodSpec{ 566 Affinity: &api.Affinity{ 567 PodAffinity: &api.PodAffinity{ 568 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 569 { 570 LabelSelector: &metav1.LabelSelector{}, 571 }, 572 { 573 LabelSelector: nil, 574 }, 575 }, 576 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 577 { 578 PodAffinityTerm: api.PodAffinityTerm{ 579 LabelSelector: &metav1.LabelSelector{}, 580 }, 581 }, 582 { 583 PodAffinityTerm: api.PodAffinityTerm{ 584 LabelSelector: nil, 585 }, 586 }, 587 }, 588 }, 589 PodAntiAffinity: &api.PodAntiAffinity{ 590 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 591 { 592 LabelSelector: &metav1.LabelSelector{}, 593 }, 594 { 595 LabelSelector: nil, 596 }, 597 }, 598 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 599 { 600 PodAffinityTerm: api.PodAffinityTerm{ 601 LabelSelector: &metav1.LabelSelector{}, 602 }, 603 }, 604 { 605 PodAffinityTerm: api.PodAffinityTerm{ 606 LabelSelector: nil, 607 }, 608 }, 609 }, 610 }, 611 }, 612 }, 613 }, 614 expected: []string{ 615 `spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[1].labelSelector: a null labelSelector results in matching no pod`, 616 `spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].podAffinityTerm.labelSelector: a null labelSelector results in matching no pod`, 617 `spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[1].labelSelector: a null labelSelector results in matching no pod`, 618 `spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].podAffinityTerm.labelSelector: a null labelSelector results in matching no pod`, 619 }, 620 }, 621 { 622 name: "container no ports", 623 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 624 Containers: []api.Container{{ 625 Name: "foo", 626 Ports: []api.ContainerPort{}, 627 }}, 628 }}, 629 expected: []string{}, 630 }, 631 { 632 name: "one container, one port", 633 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 634 Containers: []api.Container{{ 635 Name: "foo", 636 Ports: []api.ContainerPort{ 637 {ContainerPort: 80, HostPort: 80}, 638 }, 639 }}, 640 }}, 641 expected: []string{}, 642 }, 643 { 644 name: "one container, two ports, same protocol, different ports", 645 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 646 Containers: []api.Container{{ 647 Name: "foo", 648 Ports: []api.ContainerPort{ 649 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 650 {ContainerPort: 81, Protocol: api.ProtocolUDP}, 651 }, 652 }}, 653 }}, 654 expected: []string{}, 655 }, 656 { 657 name: "one container, two ports, different protocols, same port", 658 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 659 Containers: []api.Container{{ 660 Name: "foo", 661 Ports: []api.ContainerPort{ 662 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 663 {ContainerPort: 80, Protocol: api.ProtocolTCP}, 664 }, 665 }}, 666 }}, 667 expected: []string{}, 668 }, 669 { 670 name: "one container, two ports, same protocol, same port, different hostport", 671 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 672 Containers: []api.Container{{ 673 Name: "foo", 674 Ports: []api.ContainerPort{ 675 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80}, 676 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 81}, 677 }, 678 }}, 679 }}, 680 expected: []string{}, 681 }, 682 { 683 name: "one container, two ports, same protocol, port and hostPort, different hostIP", 684 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 685 Containers: []api.Container{{ 686 Name: "foo", 687 Ports: []api.ContainerPort{ 688 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.1"}, 689 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.2"}, 690 }, 691 }}, 692 }}, 693 expected: []string{}, 694 }, 695 { 696 name: "two containers, one port each, same protocol, different ports", 697 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 698 Containers: []api.Container{{ 699 Name: "foo", 700 Ports: []api.ContainerPort{ 701 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 702 }, 703 }, { 704 Name: "bar", 705 Ports: []api.ContainerPort{ 706 {ContainerPort: 81, Protocol: api.ProtocolUDP}, 707 }, 708 }}, 709 }}, 710 expected: []string{}, 711 }, 712 { 713 name: "two containers, one port each, different protocols, same port", 714 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 715 Containers: []api.Container{{ 716 Name: "foo", 717 Ports: []api.ContainerPort{ 718 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 719 }, 720 }, { 721 Name: "bar", 722 Ports: []api.ContainerPort{ 723 {ContainerPort: 80, Protocol: api.ProtocolTCP}, 724 }, 725 }}, 726 }}, 727 expected: []string{}, 728 }, 729 { 730 name: "two containers, one port each, same protocol, same port, different hostport", 731 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 732 Containers: []api.Container{{ 733 Name: "foo", 734 Ports: []api.ContainerPort{ 735 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80}, 736 }, 737 }, { 738 Name: "bar", 739 Ports: []api.ContainerPort{ 740 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 81}, 741 }, 742 }}, 743 }}, 744 expected: []string{}, 745 }, 746 { 747 name: "two containers, one port each, same protocol, port and hostPort, different hostIP", 748 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 749 Containers: []api.Container{{ 750 Name: "foo", 751 Ports: []api.ContainerPort{ 752 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.1"}, 753 }, 754 }, { 755 Name: "bar", 756 Ports: []api.ContainerPort{ 757 {ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.2"}, 758 }, 759 }}, 760 }}, 761 expected: []string{}, 762 }, 763 { 764 name: "duplicate container ports with same port and protocol", 765 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 766 Containers: []api.Container{{ 767 Name: "foo", 768 Ports: []api.ContainerPort{ 769 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 770 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 771 }, 772 }}, 773 }}, 774 expected: []string{ 775 `spec.containers[0].ports[1]: duplicate port definition with spec.containers[0].ports[0]`, 776 }, 777 }, 778 { 779 name: "duplicate container ports with same port, hostPort and protocol", 780 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 781 Containers: []api.Container{{ 782 Name: "foo", 783 Ports: []api.ContainerPort{ 784 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 785 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 786 }, 787 }}, 788 }}, 789 expected: []string{ 790 `spec.containers[0].ports[1]: duplicate port definition with spec.containers[0].ports[0]`, 791 }, 792 }, 793 { 794 name: "duplicate container ports with same port, host port, host IP and protocol", 795 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 796 Containers: []api.Container{{ 797 Name: "foo", 798 Ports: []api.ContainerPort{ 799 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 800 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 801 }, 802 }}, 803 }}, 804 expected: []string{ 805 `spec.containers[0].ports[1]: duplicate port definition with spec.containers[0].ports[0]`, 806 }, 807 }, 808 { 809 name: "one container port hostIP set without host port set", 810 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 811 Containers: []api.Container{{ 812 Name: "foo", 813 Ports: []api.ContainerPort{ 814 {ContainerPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 815 }, 816 }}, 817 }}, 818 expected: []string{ 819 `spec.containers[0].ports[0]: hostIP set without hostPort: {Name: HostPort:0 ContainerPort:80 Protocol:UDP HostIP:10.0.0.1}`, 820 }, 821 }, 822 { 823 name: "duplicate container ports with one host port set and one without", 824 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 825 Containers: []api.Container{{ 826 Name: "foo", 827 Ports: []api.ContainerPort{ 828 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 829 {ContainerPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 830 }, 831 }}, 832 }}, 833 expected: []string{ 834 `spec.containers[0].ports[1]: overlapping port definition with spec.containers[0].ports[0]`, 835 `spec.containers[0].ports[1]: hostIP set without hostPort: {Name: HostPort:0 ContainerPort:80 Protocol:UDP HostIP:10.0.0.1}`, 836 }, 837 }, 838 { 839 name: "duplicate container ports without one host IP set and two with", 840 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 841 Containers: []api.Container{{ 842 Name: "foo", 843 Ports: []api.ContainerPort{ 844 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 845 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 846 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.2"}, 847 }, 848 }}, 849 }}, 850 expected: []string{ 851 `spec.containers[0].ports[1]: dangerously ambiguous port definition with spec.containers[0].ports[0]`, 852 `spec.containers[0].ports[2]: dangerously ambiguous port definition with spec.containers[0].ports[1]`, 853 }, 854 }, 855 { 856 name: "duplicate container ports with one host IP set and one without", 857 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 858 Containers: []api.Container{{ 859 Name: "foo", 860 Ports: []api.ContainerPort{ 861 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 862 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 863 }, 864 }}, 865 }}, 866 expected: []string{ 867 `spec.containers[0].ports[1]: dangerously ambiguous port definition with spec.containers[0].ports[0]`, 868 }, 869 }, 870 { 871 name: "duplicate containers with same port and protocol", 872 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 873 Containers: []api.Container{{ 874 Name: "foo", 875 Ports: []api.ContainerPort{ 876 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 877 }, 878 }, { 879 Name: "bar", 880 Ports: []api.ContainerPort{ 881 {ContainerPort: 80, Protocol: api.ProtocolUDP}, 882 }, 883 }}, 884 }}, 885 expected: []string{ 886 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`, 887 }, 888 }, 889 { 890 name: "duplicate containers with same port, hostPort and protocol", 891 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 892 Containers: []api.Container{{ 893 Name: "foo", 894 Ports: []api.ContainerPort{ 895 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 896 }, 897 }, { 898 Name: "bar", 899 Ports: []api.ContainerPort{ 900 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 901 }, 902 }}, 903 }}, 904 expected: []string{ 905 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`, 906 }, 907 }, 908 { 909 name: "duplicate containers with same port, host port, host IP and protocol", 910 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 911 Containers: []api.Container{{ 912 Name: "foo", 913 Ports: []api.ContainerPort{ 914 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 915 }, 916 }, { 917 Name: "bar", 918 Ports: []api.ContainerPort{ 919 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 920 }, 921 }}, 922 }}, 923 expected: []string{ 924 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`, 925 }, 926 }, 927 { 928 name: "duplicate containers with one host port set and one without", 929 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 930 Containers: []api.Container{{ 931 Name: "foo", 932 Ports: []api.ContainerPort{ 933 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 934 }, 935 }, { 936 Name: "bar", 937 Ports: []api.ContainerPort{ 938 {ContainerPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 939 }, 940 }}, 941 }}, 942 expected: []string{ 943 `spec.containers[1].ports[0]: overlapping port definition with spec.containers[0].ports[0]`, 944 `spec.containers[1].ports[0]: hostIP set without hostPort: {Name: HostPort:0 ContainerPort:80 Protocol:UDP HostIP:10.0.0.1}`, 945 }, 946 }, 947 { 948 name: "duplicate container ports without one host IP set and one with", 949 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 950 Containers: []api.Container{{ 951 Name: "foo", 952 Ports: []api.ContainerPort{ 953 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 954 }, 955 }, { 956 Name: "bar", 957 Ports: []api.ContainerPort{ 958 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 959 }, 960 }}, 961 }}, 962 expected: []string{ 963 `spec.containers[1].ports[0]: dangerously ambiguous port definition with spec.containers[0].ports[0]`, 964 }, 965 }, 966 { 967 name: "duplicate container ports with one host IP set and one without", 968 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 969 Containers: []api.Container{{ 970 Name: "foo", 971 Ports: []api.ContainerPort{ 972 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"}, 973 }, 974 }, { 975 Name: "bar", 976 Ports: []api.ContainerPort{ 977 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 978 }, 979 }}, 980 }}, 981 expected: []string{ 982 `spec.containers[1].ports[0]: dangerously ambiguous port definition with spec.containers[0].ports[0]`, 983 }, 984 }, 985 { 986 name: "create duplicate container ports in two containers", 987 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 988 Containers: []api.Container{ 989 { 990 Name: "foo1", 991 Ports: []api.ContainerPort{ 992 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 993 {ContainerPort: 180, HostPort: 80, Protocol: api.ProtocolUDP}, 994 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 995 }, 996 }, 997 { 998 Name: "foo", 999 Ports: []api.ContainerPort{ 1000 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 1001 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP}, 1002 }, 1003 }}, 1004 }}, 1005 expected: []string{ 1006 `spec.containers[0].ports[2]: duplicate port definition with spec.containers[0].ports[0]`, 1007 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`, 1008 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[2]`, 1009 `spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[0]`, 1010 `spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[2]`, 1011 `spec.containers[1].ports[1]: duplicate port definition with spec.containers[1].ports[0]`, 1012 }, 1013 }, 1014 { 1015 name: "update duplicate container ports in two containers", 1016 template: &api.PodTemplateSpec{Spec: api.PodSpec{ 1017 Containers: []api.Container{ 1018 { 1019 Name: "foo1", 1020 Ports: []api.ContainerPort{ 1021 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP}, 1022 {ContainerPort: 180, HostPort: 80, Protocol: api.ProtocolTCP}, 1023 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP}, 1024 }, 1025 }, 1026 { 1027 Name: "foo", 1028 Ports: []api.ContainerPort{ 1029 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP}, 1030 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP}, 1031 }, 1032 }}, 1033 }}, 1034 oldTemplate: &api.PodTemplateSpec{Spec: api.PodSpec{ 1035 Containers: []api.Container{ 1036 { 1037 Name: "foo1", 1038 Ports: []api.ContainerPort{ 1039 {ContainerPort: 80, HostPort: 180, Protocol: api.ProtocolTCP}, 1040 {ContainerPort: 180, HostPort: 80, Protocol: api.ProtocolTCP}, 1041 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP}, 1042 }, 1043 }, 1044 { 1045 Name: "foo", 1046 Ports: []api.ContainerPort{ 1047 {ContainerPort: 80, HostPort: 180, Protocol: api.ProtocolTCP}, 1048 {ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP}, 1049 }, 1050 }}, 1051 }}, 1052 expected: []string{ 1053 `spec.containers[0].ports[2]: duplicate port definition with spec.containers[0].ports[0]`, 1054 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`, 1055 `spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[2]`, 1056 `spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[0]`, 1057 `spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[2]`, 1058 `spec.containers[1].ports[1]: duplicate port definition with spec.containers[1].ports[0]`, 1059 }, 1060 }, 1061 } 1062 1063 for _, tc := range testcases { 1064 t.Run("podspec_"+tc.name, func(t *testing.T) { 1065 var oldTemplate *api.PodTemplateSpec 1066 if tc.oldTemplate != nil { 1067 oldTemplate = tc.oldTemplate 1068 } 1069 actual := sets.NewString(GetWarningsForPodTemplate(context.TODO(), nil, tc.template, oldTemplate)...) 1070 expected := sets.NewString(tc.expected...) 1071 for _, missing := range expected.Difference(actual).List() { 1072 t.Errorf("missing: %s", missing) 1073 } 1074 for _, extra := range actual.Difference(expected).List() { 1075 t.Errorf("extra: %s", extra) 1076 } 1077 }) 1078 1079 t.Run("pod_"+tc.name, func(t *testing.T) { 1080 var pod *api.Pod 1081 if tc.template != nil { 1082 pod = &api.Pod{ 1083 ObjectMeta: tc.template.ObjectMeta, 1084 Spec: tc.template.Spec, 1085 } 1086 } 1087 actual := sets.NewString(GetWarningsForPod(context.TODO(), pod, &api.Pod{})...) 1088 expected := sets.NewString(tc.expected...) 1089 for _, missing := range expected.Difference(actual).List() { 1090 t.Errorf("missing: %s", missing) 1091 } 1092 for _, extra := range actual.Difference(expected).List() { 1093 t.Errorf("extra: %s", extra) 1094 } 1095 }) 1096 } 1097 }