k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/api/v1/pod/util_test.go (about) 1 /* 2 Copyright 2015 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 "reflect" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/stretchr/testify/assert" 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/intstr" 30 "k8s.io/apimachinery/pkg/util/sets" 31 "k8s.io/apimachinery/pkg/util/validation/field" 32 ) 33 34 func TestFindPort(t *testing.T) { 35 testCases := []struct { 36 name string 37 containers []v1.Container 38 port intstr.IntOrString 39 expected int 40 pass bool 41 }{{ 42 name: "valid int, no ports", 43 containers: []v1.Container{{}}, 44 port: intstr.FromInt32(93), 45 expected: 93, 46 pass: true, 47 }, { 48 name: "valid int, with ports", 49 containers: []v1.Container{{Ports: []v1.ContainerPort{{ 50 Name: "", 51 ContainerPort: 11, 52 Protocol: "TCP", 53 }, { 54 Name: "p", 55 ContainerPort: 22, 56 Protocol: "TCP", 57 }}}}, 58 port: intstr.FromInt32(93), 59 expected: 93, 60 pass: true, 61 }, { 62 name: "valid str, no ports", 63 containers: []v1.Container{{}}, 64 port: intstr.FromString("p"), 65 expected: 0, 66 pass: false, 67 }, { 68 name: "valid str, one ctr with ports", 69 containers: []v1.Container{{Ports: []v1.ContainerPort{{ 70 Name: "", 71 ContainerPort: 11, 72 Protocol: "UDP", 73 }, { 74 Name: "p", 75 ContainerPort: 22, 76 Protocol: "TCP", 77 }, { 78 Name: "q", 79 ContainerPort: 33, 80 Protocol: "TCP", 81 }}}}, 82 port: intstr.FromString("q"), 83 expected: 33, 84 pass: true, 85 }, { 86 name: "valid str, two ctr with ports", 87 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 88 Name: "", 89 ContainerPort: 11, 90 Protocol: "UDP", 91 }, { 92 Name: "p", 93 ContainerPort: 22, 94 Protocol: "TCP", 95 }, { 96 Name: "q", 97 ContainerPort: 33, 98 Protocol: "TCP", 99 }}}}, 100 port: intstr.FromString("q"), 101 expected: 33, 102 pass: true, 103 }, { 104 name: "valid str, two ctr with same port", 105 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 106 Name: "", 107 ContainerPort: 11, 108 Protocol: "UDP", 109 }, { 110 Name: "p", 111 ContainerPort: 22, 112 Protocol: "TCP", 113 }, { 114 Name: "q", 115 ContainerPort: 22, 116 Protocol: "TCP", 117 }}}}, 118 port: intstr.FromString("q"), 119 expected: 22, 120 pass: true, 121 }, { 122 name: "valid str, invalid protocol", 123 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 124 Name: "a", 125 ContainerPort: 11, 126 Protocol: "snmp", 127 }, 128 }}}, 129 port: intstr.FromString("a"), 130 expected: 0, 131 pass: false, 132 }, { 133 name: "valid hostPort", 134 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 135 Name: "a", 136 ContainerPort: 11, 137 HostPort: 81, 138 Protocol: "TCP", 139 }, 140 }}}, 141 port: intstr.FromString("a"), 142 expected: 11, 143 pass: true, 144 }, 145 { 146 name: "invalid hostPort", 147 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 148 Name: "a", 149 ContainerPort: 11, 150 HostPort: -1, 151 Protocol: "TCP", 152 }, 153 }}}, 154 port: intstr.FromString("a"), 155 expected: 11, 156 pass: true, 157 //this should fail but passes. 158 }, 159 { 160 name: "invalid ContainerPort", 161 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 162 Name: "a", 163 ContainerPort: -1, 164 Protocol: "TCP", 165 }, 166 }}}, 167 port: intstr.FromString("a"), 168 expected: -1, 169 pass: true, 170 //this should fail but passes 171 }, 172 { 173 name: "HostIP Address", 174 containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{ 175 Name: "a", 176 ContainerPort: 11, 177 HostIP: "192.168.1.1", 178 Protocol: "TCP", 179 }, 180 }}}, 181 port: intstr.FromString("a"), 182 expected: 11, 183 pass: true, 184 }, 185 } 186 187 for _, tc := range testCases { 188 port, err := FindPort(&v1.Pod{Spec: v1.PodSpec{Containers: tc.containers}}, 189 &v1.ServicePort{Protocol: "TCP", TargetPort: tc.port}) 190 if err != nil && tc.pass { 191 t.Errorf("unexpected error for %s: %v", tc.name, err) 192 } 193 if err == nil && !tc.pass { 194 t.Errorf("unexpected non-error for %s: %d", tc.name, port) 195 } 196 if port != tc.expected { 197 t.Errorf("wrong result for %s: expected %d, got %d", tc.name, tc.expected, port) 198 } 199 } 200 } 201 202 func TestVisitContainers(t *testing.T) { 203 setAllFeatureEnabledContainersDuringTest := ContainerType(0) 204 testCases := []struct { 205 desc string 206 spec *v1.PodSpec 207 wantContainers []string 208 mask ContainerType 209 }{ 210 { 211 desc: "empty podspec", 212 spec: &v1.PodSpec{}, 213 wantContainers: []string{}, 214 mask: AllContainers, 215 }, 216 { 217 desc: "regular containers", 218 spec: &v1.PodSpec{ 219 Containers: []v1.Container{ 220 {Name: "c1"}, 221 {Name: "c2"}, 222 }, 223 InitContainers: []v1.Container{ 224 {Name: "i1"}, 225 {Name: "i2"}, 226 }, 227 EphemeralContainers: []v1.EphemeralContainer{ 228 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}}, 229 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}}, 230 }, 231 }, 232 wantContainers: []string{"c1", "c2"}, 233 mask: Containers, 234 }, 235 { 236 desc: "init containers", 237 spec: &v1.PodSpec{ 238 Containers: []v1.Container{ 239 {Name: "c1"}, 240 {Name: "c2"}, 241 }, 242 InitContainers: []v1.Container{ 243 {Name: "i1"}, 244 {Name: "i2"}, 245 }, 246 EphemeralContainers: []v1.EphemeralContainer{ 247 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}}, 248 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}}, 249 }, 250 }, 251 wantContainers: []string{"i1", "i2"}, 252 mask: InitContainers, 253 }, 254 { 255 desc: "ephemeral containers", 256 spec: &v1.PodSpec{ 257 Containers: []v1.Container{ 258 {Name: "c1"}, 259 {Name: "c2"}, 260 }, 261 InitContainers: []v1.Container{ 262 {Name: "i1"}, 263 {Name: "i2"}, 264 }, 265 EphemeralContainers: []v1.EphemeralContainer{ 266 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}}, 267 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}}, 268 }, 269 }, 270 wantContainers: []string{"e1", "e2"}, 271 mask: EphemeralContainers, 272 }, 273 { 274 desc: "all container types", 275 spec: &v1.PodSpec{ 276 Containers: []v1.Container{ 277 {Name: "c1"}, 278 {Name: "c2"}, 279 }, 280 InitContainers: []v1.Container{ 281 {Name: "i1"}, 282 {Name: "i2"}, 283 }, 284 EphemeralContainers: []v1.EphemeralContainer{ 285 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}}, 286 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}}, 287 }, 288 }, 289 wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"}, 290 mask: AllContainers, 291 }, 292 { 293 desc: "all feature enabled container types", 294 spec: &v1.PodSpec{ 295 Containers: []v1.Container{ 296 {Name: "c1"}, 297 {Name: "c2", SecurityContext: &v1.SecurityContext{}}, 298 }, 299 InitContainers: []v1.Container{ 300 {Name: "i1"}, 301 {Name: "i2", SecurityContext: &v1.SecurityContext{}}, 302 }, 303 EphemeralContainers: []v1.EphemeralContainer{ 304 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}}, 305 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}}, 306 }, 307 }, 308 wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"}, 309 mask: setAllFeatureEnabledContainersDuringTest, 310 }, 311 { 312 desc: "dropping fields", 313 spec: &v1.PodSpec{ 314 Containers: []v1.Container{ 315 {Name: "c1"}, 316 {Name: "c2", SecurityContext: &v1.SecurityContext{}}, 317 }, 318 InitContainers: []v1.Container{ 319 {Name: "i1"}, 320 {Name: "i2", SecurityContext: &v1.SecurityContext{}}, 321 }, 322 EphemeralContainers: []v1.EphemeralContainer{ 323 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}}, 324 {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2", SecurityContext: &v1.SecurityContext{}}}, 325 }, 326 }, 327 wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"}, 328 mask: AllContainers, 329 }, 330 } 331 332 for _, tc := range testCases { 333 t.Run(tc.desc, func(t *testing.T) { 334 if tc.mask == setAllFeatureEnabledContainersDuringTest { 335 tc.mask = AllFeatureEnabledContainers() 336 } 337 338 gotContainers := []string{} 339 VisitContainers(tc.spec, tc.mask, func(c *v1.Container, containerType ContainerType) bool { 340 gotContainers = append(gotContainers, c.Name) 341 if c.SecurityContext != nil { 342 c.SecurityContext = nil 343 } 344 return true 345 }) 346 if !cmp.Equal(gotContainers, tc.wantContainers) { 347 t.Errorf("VisitContainers() = %+v, want %+v", gotContainers, tc.wantContainers) 348 } 349 for _, c := range tc.spec.Containers { 350 if c.SecurityContext != nil { 351 t.Errorf("VisitContainers() did not drop SecurityContext for container %q", c.Name) 352 } 353 } 354 for _, c := range tc.spec.InitContainers { 355 if c.SecurityContext != nil { 356 t.Errorf("VisitContainers() did not drop SecurityContext for init container %q", c.Name) 357 } 358 } 359 for _, c := range tc.spec.EphemeralContainers { 360 if c.SecurityContext != nil { 361 t.Errorf("VisitContainers() did not drop SecurityContext for ephemeral container %q", c.Name) 362 } 363 } 364 }) 365 } 366 } 367 368 func TestPodSecrets(t *testing.T) { 369 // Stub containing all possible secret references in a pod. 370 // The names of the referenced secrets match struct paths detected by reflection. 371 pod := &v1.Pod{ 372 Spec: v1.PodSpec{ 373 Containers: []v1.Container{{ 374 EnvFrom: []v1.EnvFromSource{{ 375 SecretRef: &v1.SecretEnvSource{ 376 LocalObjectReference: v1.LocalObjectReference{ 377 Name: "Spec.Containers[*].EnvFrom[*].SecretRef"}}}}, 378 Env: []v1.EnvVar{{ 379 ValueFrom: &v1.EnvVarSource{ 380 SecretKeyRef: &v1.SecretKeySelector{ 381 LocalObjectReference: v1.LocalObjectReference{ 382 Name: "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}}, 383 ImagePullSecrets: []v1.LocalObjectReference{{ 384 Name: "Spec.ImagePullSecrets"}}, 385 InitContainers: []v1.Container{{ 386 EnvFrom: []v1.EnvFromSource{{ 387 SecretRef: &v1.SecretEnvSource{ 388 LocalObjectReference: v1.LocalObjectReference{ 389 Name: "Spec.InitContainers[*].EnvFrom[*].SecretRef"}}}}, 390 Env: []v1.EnvVar{{ 391 ValueFrom: &v1.EnvVarSource{ 392 SecretKeyRef: &v1.SecretKeySelector{ 393 LocalObjectReference: v1.LocalObjectReference{ 394 Name: "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}}, 395 Volumes: []v1.Volume{{ 396 VolumeSource: v1.VolumeSource{ 397 AzureFile: &v1.AzureFileVolumeSource{ 398 SecretName: "Spec.Volumes[*].VolumeSource.AzureFile.SecretName"}}}, { 399 VolumeSource: v1.VolumeSource{ 400 CephFS: &v1.CephFSVolumeSource{ 401 SecretRef: &v1.LocalObjectReference{ 402 Name: "Spec.Volumes[*].VolumeSource.CephFS.SecretRef"}}}}, { 403 VolumeSource: v1.VolumeSource{ 404 Cinder: &v1.CinderVolumeSource{ 405 SecretRef: &v1.LocalObjectReference{ 406 Name: "Spec.Volumes[*].VolumeSource.Cinder.SecretRef"}}}}, { 407 VolumeSource: v1.VolumeSource{ 408 FlexVolume: &v1.FlexVolumeSource{ 409 SecretRef: &v1.LocalObjectReference{ 410 Name: "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef"}}}}, { 411 VolumeSource: v1.VolumeSource{ 412 Projected: &v1.ProjectedVolumeSource{ 413 Sources: []v1.VolumeProjection{{ 414 Secret: &v1.SecretProjection{ 415 LocalObjectReference: v1.LocalObjectReference{ 416 Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret"}}}}}}}, { 417 VolumeSource: v1.VolumeSource{ 418 RBD: &v1.RBDVolumeSource{ 419 SecretRef: &v1.LocalObjectReference{ 420 Name: "Spec.Volumes[*].VolumeSource.RBD.SecretRef"}}}}, { 421 VolumeSource: v1.VolumeSource{ 422 Secret: &v1.SecretVolumeSource{ 423 SecretName: "Spec.Volumes[*].VolumeSource.Secret.SecretName"}}}, { 424 VolumeSource: v1.VolumeSource{ 425 Secret: &v1.SecretVolumeSource{ 426 SecretName: "Spec.Volumes[*].VolumeSource.Secret"}}}, { 427 VolumeSource: v1.VolumeSource{ 428 ScaleIO: &v1.ScaleIOVolumeSource{ 429 SecretRef: &v1.LocalObjectReference{ 430 Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, { 431 VolumeSource: v1.VolumeSource{ 432 ISCSI: &v1.ISCSIVolumeSource{ 433 SecretRef: &v1.LocalObjectReference{ 434 Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, { 435 VolumeSource: v1.VolumeSource{ 436 StorageOS: &v1.StorageOSVolumeSource{ 437 SecretRef: &v1.LocalObjectReference{ 438 Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}, { 439 VolumeSource: v1.VolumeSource{ 440 CSI: &v1.CSIVolumeSource{ 441 NodePublishSecretRef: &v1.LocalObjectReference{ 442 Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}}, 443 EphemeralContainers: []v1.EphemeralContainer{{ 444 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 445 EnvFrom: []v1.EnvFromSource{{ 446 SecretRef: &v1.SecretEnvSource{ 447 LocalObjectReference: v1.LocalObjectReference{ 448 Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef"}}}}, 449 Env: []v1.EnvVar{{ 450 ValueFrom: &v1.EnvVarSource{ 451 SecretKeyRef: &v1.SecretKeySelector{ 452 LocalObjectReference: v1.LocalObjectReference{ 453 Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef"}}}}}}}}, 454 }, 455 } 456 extractedNames := sets.New[string]() 457 VisitPodSecretNames(pod, func(name string) bool { 458 extractedNames.Insert(name) 459 return true 460 }) 461 462 // excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects 463 excludedSecretPaths := sets.New[string]( 464 "Spec.Volumes[*].VolumeSource.CephFS.SecretFile", 465 ) 466 // expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects. 467 // every path here should be represented as an example in the Pod stub above, with the secret name set to the path. 468 expectedSecretPaths := sets.New[string]( 469 "Spec.Containers[*].EnvFrom[*].SecretRef", 470 "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef", 471 "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef", 472 "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef", 473 "Spec.ImagePullSecrets", 474 "Spec.InitContainers[*].EnvFrom[*].SecretRef", 475 "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef", 476 "Spec.Volumes[*].VolumeSource.AzureFile.SecretName", 477 "Spec.Volumes[*].VolumeSource.CephFS.SecretRef", 478 "Spec.Volumes[*].VolumeSource.Cinder.SecretRef", 479 "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef", 480 "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret", 481 "Spec.Volumes[*].VolumeSource.RBD.SecretRef", 482 "Spec.Volumes[*].VolumeSource.Secret", 483 "Spec.Volumes[*].VolumeSource.Secret.SecretName", 484 "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef", 485 "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef", 486 "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef", 487 "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef", 488 ) 489 secretPaths := collectResourcePaths(t, "secret", nil, "", reflect.TypeOf(&v1.Pod{})) 490 secretPaths = secretPaths.Difference(excludedSecretPaths) 491 if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 { 492 t.Logf("Missing expected secret paths:\n%s", strings.Join(sets.List[string](missingPaths), "\n")) 493 t.Error("Missing expected secret paths. Verify VisitPodSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths") 494 } 495 if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 { 496 t.Logf("Extra secret paths:\n%s", strings.Join(sets.List(extraPaths), "\n")) 497 t.Error("Extra fields with 'secret' in the name found. Verify VisitPodSecretNames() is including these fields if appropriate, then correct expectedSecretPaths") 498 } 499 500 if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 { 501 t.Logf("Missing expected secret names:\n%s", strings.Join(sets.List[string](missingNames), "\n")) 502 t.Error("Missing expected secret names. Verify the pod stub above includes these references, then verify VisitPodSecretNames() is correctly finding the missing names") 503 } 504 if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 { 505 t.Logf("Extra secret names:\n%s", strings.Join(sets.List[string](extraNames), "\n")) 506 t.Error("Extra secret names extracted. Verify VisitPodSecretNames() is correctly extracting secret names") 507 } 508 509 // emptyPod is a stub containing empty object names 510 emptyPod := &v1.Pod{ 511 Spec: v1.PodSpec{ 512 Containers: []v1.Container{{ 513 EnvFrom: []v1.EnvFromSource{{ 514 SecretRef: &v1.SecretEnvSource{ 515 LocalObjectReference: v1.LocalObjectReference{ 516 Name: ""}}}}}}, 517 }, 518 } 519 VisitPodSecretNames(emptyPod, func(name string) bool { 520 t.Fatalf("expected no empty names collected, got %q", name) 521 return false 522 }) 523 } 524 525 // collectResourcePaths traverses the object, computing all the struct paths that lead to fields with resourcename in the name. 526 func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, name string, tp reflect.Type) sets.Set[string] { 527 resourcename = strings.ToLower(resourcename) 528 resourcePaths := sets.New[string]() 529 530 if tp.Kind() == reflect.Pointer { 531 resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path, name, tp.Elem()))...) 532 return resourcePaths 533 } 534 535 if strings.Contains(strings.ToLower(name), resourcename) { 536 resourcePaths.Insert(path.String()) 537 } 538 539 switch tp.Kind() { 540 case reflect.Pointer: 541 resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path, name, tp.Elem()))...) 542 case reflect.Struct: 543 // ObjectMeta is generic and therefore should never have a field with a specific resource's name; 544 // it contains cycles so it's easiest to just skip it. 545 if name == "ObjectMeta" { 546 break 547 } 548 for i := 0; i < tp.NumField(); i++ { 549 field := tp.Field(i) 550 resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type))...) 551 } 552 case reflect.Interface: 553 t.Errorf("cannot find %s fields in interface{} field %s", resourcename, path.String()) 554 case reflect.Map: 555 resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()))...) 556 case reflect.Slice: 557 resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()))...) 558 default: 559 // all primitive types 560 } 561 562 return resourcePaths 563 } 564 565 func TestPodConfigmaps(t *testing.T) { 566 // Stub containing all possible ConfigMap references in a pod. 567 // The names of the referenced ConfigMaps match struct paths detected by reflection. 568 pod := &v1.Pod{ 569 Spec: v1.PodSpec{ 570 Containers: []v1.Container{{ 571 EnvFrom: []v1.EnvFromSource{{ 572 ConfigMapRef: &v1.ConfigMapEnvSource{ 573 LocalObjectReference: v1.LocalObjectReference{ 574 Name: "Spec.Containers[*].EnvFrom[*].ConfigMapRef"}}}}, 575 Env: []v1.EnvVar{{ 576 ValueFrom: &v1.EnvVarSource{ 577 ConfigMapKeyRef: &v1.ConfigMapKeySelector{ 578 LocalObjectReference: v1.LocalObjectReference{ 579 Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}, 580 EphemeralContainers: []v1.EphemeralContainer{{ 581 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 582 EnvFrom: []v1.EnvFromSource{{ 583 ConfigMapRef: &v1.ConfigMapEnvSource{ 584 LocalObjectReference: v1.LocalObjectReference{ 585 Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef"}}}}, 586 Env: []v1.EnvVar{{ 587 ValueFrom: &v1.EnvVarSource{ 588 ConfigMapKeyRef: &v1.ConfigMapKeySelector{ 589 LocalObjectReference: v1.LocalObjectReference{ 590 Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}}, 591 InitContainers: []v1.Container{{ 592 EnvFrom: []v1.EnvFromSource{{ 593 ConfigMapRef: &v1.ConfigMapEnvSource{ 594 LocalObjectReference: v1.LocalObjectReference{ 595 Name: "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef"}}}}, 596 Env: []v1.EnvVar{{ 597 ValueFrom: &v1.EnvVarSource{ 598 ConfigMapKeyRef: &v1.ConfigMapKeySelector{ 599 LocalObjectReference: v1.LocalObjectReference{ 600 Name: "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}, 601 Volumes: []v1.Volume{{ 602 VolumeSource: v1.VolumeSource{ 603 Projected: &v1.ProjectedVolumeSource{ 604 Sources: []v1.VolumeProjection{{ 605 ConfigMap: &v1.ConfigMapProjection{ 606 LocalObjectReference: v1.LocalObjectReference{ 607 Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap"}}}}}}}, { 608 VolumeSource: v1.VolumeSource{ 609 ConfigMap: &v1.ConfigMapVolumeSource{ 610 LocalObjectReference: v1.LocalObjectReference{ 611 Name: "Spec.Volumes[*].VolumeSource.ConfigMap"}}}}}, 612 }, 613 } 614 extractedNames := sets.New[string]() 615 VisitPodConfigmapNames(pod, func(name string) bool { 616 extractedNames.Insert(name) 617 return true 618 }) 619 620 // expectedPaths holds struct paths to fields with "ConfigMap" in the name that are references to ConfigMap API objects. 621 // every path here should be represented as an example in the Pod stub above, with the ConfigMap name set to the path. 622 expectedPaths := sets.New[string]( 623 "Spec.Containers[*].EnvFrom[*].ConfigMapRef", 624 "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef", 625 "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef", 626 "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef", 627 "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef", 628 "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef", 629 "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap", 630 "Spec.Volumes[*].VolumeSource.ConfigMap", 631 ) 632 collectPaths := collectResourcePaths(t, "ConfigMap", nil, "", reflect.TypeOf(&v1.Pod{})) 633 if missingPaths := expectedPaths.Difference(collectPaths); len(missingPaths) > 0 { 634 t.Logf("Missing expected paths:\n%s", strings.Join(sets.List[string](missingPaths), "\n")) 635 t.Error("Missing expected paths. Verify VisitPodConfigmapNames() is correctly finding the missing paths, then correct expectedPaths") 636 } 637 if extraPaths := collectPaths.Difference(expectedPaths); len(extraPaths) > 0 { 638 t.Logf("Extra paths:\n%s", strings.Join(sets.List[string](extraPaths), "\n")) 639 t.Error("Extra fields with resource in the name found. Verify VisitPodConfigmapNames() is including these fields if appropriate, then correct expectedPaths") 640 } 641 642 if missingNames := expectedPaths.Difference(extractedNames); len(missingNames) > 0 { 643 t.Logf("Missing expected names:\n%s", strings.Join(sets.List[string](missingNames), "\n")) 644 t.Error("Missing expected names. Verify the pod stub above includes these references, then verify VisitPodConfigmapNames() is correctly finding the missing names") 645 } 646 if extraNames := extractedNames.Difference(expectedPaths); len(extraNames) > 0 { 647 t.Logf("Extra names:\n%s", strings.Join(sets.List[string](extraNames), "\n")) 648 t.Error("Extra names extracted. Verify VisitPodConfigmapNames() is correctly extracting resource names") 649 } 650 651 // emptyPod is a stub containing empty object names 652 emptyPod := &v1.Pod{ 653 Spec: v1.PodSpec{ 654 Containers: []v1.Container{{ 655 EnvFrom: []v1.EnvFromSource{{ 656 ConfigMapRef: &v1.ConfigMapEnvSource{ 657 LocalObjectReference: v1.LocalObjectReference{ 658 Name: ""}}}}}}, 659 }, 660 } 661 VisitPodConfigmapNames(emptyPod, func(name string) bool { 662 t.Fatalf("expected no empty names collected, got %q", name) 663 return false 664 }) 665 } 666 667 func newPod(now metav1.Time, ready bool, beforeSec int) *v1.Pod { 668 conditionStatus := v1.ConditionFalse 669 if ready { 670 conditionStatus = v1.ConditionTrue 671 } 672 return &v1.Pod{ 673 Status: v1.PodStatus{ 674 Conditions: []v1.PodCondition{ 675 { 676 Type: v1.PodReady, 677 LastTransitionTime: metav1.NewTime(now.Time.Add(-1 * time.Duration(beforeSec) * time.Second)), 678 Status: conditionStatus, 679 }, 680 }, 681 }, 682 } 683 } 684 685 func TestIsPodAvailable(t *testing.T) { 686 now := metav1.Now() 687 tests := []struct { 688 pod *v1.Pod 689 minReadySeconds int32 690 expected bool 691 }{ 692 { 693 pod: newPod(now, false, 0), 694 minReadySeconds: 0, 695 expected: false, 696 }, 697 { 698 pod: newPod(now, true, 0), 699 minReadySeconds: 1, 700 expected: false, 701 }, 702 { 703 pod: newPod(now, true, 0), 704 minReadySeconds: 0, 705 expected: true, 706 }, 707 { 708 pod: newPod(now, true, 51), 709 minReadySeconds: 50, 710 expected: true, 711 }, 712 } 713 714 for i, test := range tests { 715 isAvailable := IsPodAvailable(test.pod, test.minReadySeconds, now) 716 if isAvailable != test.expected { 717 t.Errorf("[tc #%d] expected available pod: %t, got: %t", i, test.expected, isAvailable) 718 } 719 } 720 } 721 722 func TestIsPodTerminal(t *testing.T) { 723 now := metav1.Now() 724 725 tests := []struct { 726 podPhase v1.PodPhase 727 expected bool 728 }{ 729 { 730 podPhase: v1.PodFailed, 731 expected: true, 732 }, 733 { 734 podPhase: v1.PodSucceeded, 735 expected: true, 736 }, 737 { 738 podPhase: v1.PodUnknown, 739 expected: false, 740 }, 741 { 742 podPhase: v1.PodPending, 743 expected: false, 744 }, 745 { 746 podPhase: v1.PodRunning, 747 expected: false, 748 }, 749 { 750 expected: false, 751 }, 752 } 753 754 for i, test := range tests { 755 pod := newPod(now, true, 0) 756 pod.Status.Phase = test.podPhase 757 isTerminal := IsPodTerminal(pod) 758 if isTerminal != test.expected { 759 t.Errorf("[tc #%d] expected terminal pod: %t, got: %t", i, test.expected, isTerminal) 760 } 761 } 762 } 763 764 func TestGetContainerStatus(t *testing.T) { 765 type ExpectedStruct struct { 766 status v1.ContainerStatus 767 exists bool 768 } 769 770 tests := []struct { 771 status []v1.ContainerStatus 772 name string 773 expected ExpectedStruct 774 desc string 775 }{ 776 { 777 status: []v1.ContainerStatus{{Name: "test1", Ready: false, Image: "image1"}, {Name: "test2", Ready: true, Image: "image1"}}, 778 name: "test1", 779 expected: ExpectedStruct{status: v1.ContainerStatus{Name: "test1", Ready: false, Image: "image1"}, exists: true}, 780 desc: "retrieve ContainerStatus with Name=\"test1\"", 781 }, 782 { 783 status: []v1.ContainerStatus{{Name: "test2", Ready: false, Image: "image2"}}, 784 name: "test1", 785 expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false}, 786 desc: "no matching ContainerStatus with Name=\"test1\"", 787 }, 788 { 789 status: []v1.ContainerStatus{{Name: "test3", Ready: false, Image: "image3"}}, 790 name: "", 791 expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false}, 792 desc: "retrieve an empty ContainerStatus with container name empty", 793 }, 794 { 795 status: nil, 796 name: "", 797 expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false}, 798 desc: "retrieve an empty ContainerStatus with status nil", 799 }, 800 } 801 802 for _, test := range tests { 803 resultStatus, exists := GetContainerStatus(test.status, test.name) 804 assert.Equal(t, test.expected.status, resultStatus, "GetContainerStatus: "+test.desc) 805 assert.Equal(t, test.expected.exists, exists, "GetContainerStatus: "+test.desc) 806 807 resultStatus = GetExistingContainerStatus(test.status, test.name) 808 assert.Equal(t, test.expected.status, resultStatus, "GetExistingContainerStatus: "+test.desc) 809 } 810 } 811 812 func TestGetIndexOfContainerStatus(t *testing.T) { 813 testStatus := []v1.ContainerStatus{ 814 { 815 Name: "c1", 816 Ready: false, 817 Image: "image1", 818 }, 819 { 820 Name: "c2", 821 Ready: true, 822 Image: "image1", 823 }, 824 } 825 826 tests := []struct { 827 desc string 828 containerName string 829 expectedExists bool 830 expectedIndex int 831 }{ 832 { 833 desc: "first container", 834 containerName: "c1", 835 expectedExists: true, 836 expectedIndex: 0, 837 }, 838 { 839 desc: "second container", 840 containerName: "c2", 841 expectedExists: true, 842 expectedIndex: 1, 843 }, 844 { 845 desc: "non-existent container", 846 containerName: "c3", 847 expectedExists: false, 848 expectedIndex: 0, 849 }, 850 } 851 852 for _, test := range tests { 853 idx, exists := GetIndexOfContainerStatus(testStatus, test.containerName) 854 assert.Equal(t, test.expectedExists, exists, "GetIndexOfContainerStatus: "+test.desc) 855 assert.Equal(t, test.expectedIndex, idx, "GetIndexOfContainerStatus: "+test.desc) 856 } 857 } 858 859 func TestUpdatePodCondition(t *testing.T) { 860 time := metav1.Now() 861 862 podStatus := v1.PodStatus{ 863 Conditions: []v1.PodCondition{ 864 { 865 Type: v1.PodReady, 866 Status: v1.ConditionTrue, 867 Reason: "successfully", 868 Message: "sync pod successfully", 869 LastProbeTime: time, 870 LastTransitionTime: metav1.NewTime(time.Add(1000)), 871 }, 872 }, 873 } 874 tests := []struct { 875 status *v1.PodStatus 876 conditions v1.PodCondition 877 expected bool 878 desc string 879 }{ 880 { 881 status: &podStatus, 882 conditions: v1.PodCondition{ 883 Type: v1.PodReady, 884 Status: v1.ConditionTrue, 885 Reason: "successfully", 886 Message: "sync pod successfully", 887 LastProbeTime: time, 888 LastTransitionTime: metav1.NewTime(time.Add(1000))}, 889 expected: false, 890 desc: "all equal, no update", 891 }, 892 { 893 status: &podStatus, 894 conditions: v1.PodCondition{ 895 Type: v1.PodScheduled, 896 Status: v1.ConditionTrue, 897 Reason: "successfully", 898 Message: "sync pod successfully", 899 LastProbeTime: time, 900 LastTransitionTime: metav1.NewTime(time.Add(1000))}, 901 expected: true, 902 desc: "not equal Type, should get updated", 903 }, 904 { 905 status: &podStatus, 906 conditions: v1.PodCondition{ 907 Type: v1.PodReady, 908 Status: v1.ConditionFalse, 909 Reason: "successfully", 910 Message: "sync pod successfully", 911 LastProbeTime: time, 912 LastTransitionTime: metav1.NewTime(time.Add(1000))}, 913 expected: true, 914 desc: "not equal Status, should get updated", 915 }, 916 } 917 918 for _, test := range tests { 919 resultStatus := UpdatePodCondition(test.status, &test.conditions) 920 921 assert.Equal(t, test.expected, resultStatus, test.desc) 922 } 923 } 924 925 func TestGetContainersReadyCondition(t *testing.T) { 926 time := metav1.Now() 927 928 containersReadyCondition := v1.PodCondition{ 929 Type: v1.ContainersReady, 930 Status: v1.ConditionTrue, 931 Reason: "successfully", 932 Message: "sync pod successfully", 933 LastProbeTime: time, 934 LastTransitionTime: metav1.NewTime(time.Add(1000)), 935 } 936 937 tests := []struct { 938 desc string 939 podStatus v1.PodStatus 940 expectedCondition *v1.PodCondition 941 }{ 942 { 943 desc: "containers ready condition exists", 944 podStatus: v1.PodStatus{ 945 Conditions: []v1.PodCondition{containersReadyCondition}, 946 }, 947 expectedCondition: &containersReadyCondition, 948 }, 949 { 950 desc: "containers ready condition does not exist", 951 podStatus: v1.PodStatus{ 952 Conditions: []v1.PodCondition{}, 953 }, 954 expectedCondition: nil, 955 }, 956 } 957 958 for _, test := range tests { 959 containersReadyCondition := GetContainersReadyCondition(test.podStatus) 960 assert.Equal(t, test.expectedCondition, containersReadyCondition, test.desc) 961 } 962 } 963 964 func TestIsContainersReadyConditionTrue(t *testing.T) { 965 time := metav1.Now() 966 967 tests := []struct { 968 desc string 969 podStatus v1.PodStatus 970 expected bool 971 }{ 972 { 973 desc: "containers ready condition is true", 974 podStatus: v1.PodStatus{ 975 Conditions: []v1.PodCondition{ 976 { 977 Type: v1.ContainersReady, 978 Status: v1.ConditionTrue, 979 Reason: "successfully", 980 Message: "sync pod successfully", 981 LastProbeTime: time, 982 LastTransitionTime: metav1.NewTime(time.Add(1000)), 983 }, 984 }, 985 }, 986 expected: true, 987 }, 988 { 989 desc: "containers ready condition is false", 990 podStatus: v1.PodStatus{ 991 Conditions: []v1.PodCondition{ 992 { 993 Type: v1.ContainersReady, 994 Status: v1.ConditionFalse, 995 Reason: "successfully", 996 Message: "sync pod successfully", 997 LastProbeTime: time, 998 LastTransitionTime: metav1.NewTime(time.Add(1000)), 999 }, 1000 }, 1001 }, 1002 expected: false, 1003 }, 1004 { 1005 desc: "containers ready condition is empty", 1006 podStatus: v1.PodStatus{ 1007 Conditions: []v1.PodCondition{}, 1008 }, 1009 expected: false, 1010 }, 1011 } 1012 1013 for _, test := range tests { 1014 isContainersReady := IsContainersReadyConditionTrue(test.podStatus) 1015 assert.Equal(t, test.expected, isContainersReady, test.desc) 1016 } 1017 }