mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1018 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1018 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2015 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package pod
 | |
| 
 | |
| import (
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/google/go-cmp/cmp"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	v1 "k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/util/intstr"
 | |
| 	"k8s.io/apimachinery/pkg/util/sets"
 | |
| 	"k8s.io/apimachinery/pkg/util/validation/field"
 | |
| )
 | |
| 
 | |
| func TestFindPort(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		name       string
 | |
| 		containers []v1.Container
 | |
| 		port       intstr.IntOrString
 | |
| 		expected   int
 | |
| 		pass       bool
 | |
| 	}{{
 | |
| 		name:       "valid int, no ports",
 | |
| 		containers: []v1.Container{{}},
 | |
| 		port:       intstr.FromInt32(93),
 | |
| 		expected:   93,
 | |
| 		pass:       true,
 | |
| 	}, {
 | |
| 		name: "valid int, with ports",
 | |
| 		containers: []v1.Container{{Ports: []v1.ContainerPort{{
 | |
| 			Name:          "",
 | |
| 			ContainerPort: 11,
 | |
| 			Protocol:      "TCP",
 | |
| 		}, {
 | |
| 			Name:          "p",
 | |
| 			ContainerPort: 22,
 | |
| 			Protocol:      "TCP",
 | |
| 		}}}},
 | |
| 		port:     intstr.FromInt32(93),
 | |
| 		expected: 93,
 | |
| 		pass:     true,
 | |
| 	}, {
 | |
| 		name:       "valid str, no ports",
 | |
| 		containers: []v1.Container{{}},
 | |
| 		port:       intstr.FromString("p"),
 | |
| 		expected:   0,
 | |
| 		pass:       false,
 | |
| 	}, {
 | |
| 		name: "valid str, one ctr with ports",
 | |
| 		containers: []v1.Container{{Ports: []v1.ContainerPort{{
 | |
| 			Name:          "",
 | |
| 			ContainerPort: 11,
 | |
| 			Protocol:      "UDP",
 | |
| 		}, {
 | |
| 			Name:          "p",
 | |
| 			ContainerPort: 22,
 | |
| 			Protocol:      "TCP",
 | |
| 		}, {
 | |
| 			Name:          "q",
 | |
| 			ContainerPort: 33,
 | |
| 			Protocol:      "TCP",
 | |
| 		}}}},
 | |
| 		port:     intstr.FromString("q"),
 | |
| 		expected: 33,
 | |
| 		pass:     true,
 | |
| 	}, {
 | |
| 		name: "valid str, two ctr with ports",
 | |
| 		containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 			Name:          "",
 | |
| 			ContainerPort: 11,
 | |
| 			Protocol:      "UDP",
 | |
| 		}, {
 | |
| 			Name:          "p",
 | |
| 			ContainerPort: 22,
 | |
| 			Protocol:      "TCP",
 | |
| 		}, {
 | |
| 			Name:          "q",
 | |
| 			ContainerPort: 33,
 | |
| 			Protocol:      "TCP",
 | |
| 		}}}},
 | |
| 		port:     intstr.FromString("q"),
 | |
| 		expected: 33,
 | |
| 		pass:     true,
 | |
| 	}, {
 | |
| 		name: "valid str, two ctr with same port",
 | |
| 		containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 			Name:          "",
 | |
| 			ContainerPort: 11,
 | |
| 			Protocol:      "UDP",
 | |
| 		}, {
 | |
| 			Name:          "p",
 | |
| 			ContainerPort: 22,
 | |
| 			Protocol:      "TCP",
 | |
| 		}, {
 | |
| 			Name:          "q",
 | |
| 			ContainerPort: 22,
 | |
| 			Protocol:      "TCP",
 | |
| 		}}}},
 | |
| 		port:     intstr.FromString("q"),
 | |
| 		expected: 22,
 | |
| 		pass:     true,
 | |
| 	}, {
 | |
| 		name: "valid str, invalid protocol",
 | |
| 		containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 			Name:          "a",
 | |
| 			ContainerPort: 11,
 | |
| 			Protocol:      "snmp",
 | |
| 		},
 | |
| 		}}},
 | |
| 		port:     intstr.FromString("a"),
 | |
| 		expected: 0,
 | |
| 		pass:     false,
 | |
| 	}, {
 | |
| 		name: "valid hostPort",
 | |
| 		containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 			Name:          "a",
 | |
| 			ContainerPort: 11,
 | |
| 			HostPort:      81,
 | |
| 			Protocol:      "TCP",
 | |
| 		},
 | |
| 		}}},
 | |
| 		port:     intstr.FromString("a"),
 | |
| 		expected: 11,
 | |
| 		pass:     true,
 | |
| 	},
 | |
| 		{
 | |
| 			name: "invalid hostPort",
 | |
| 			containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 				Name:          "a",
 | |
| 				ContainerPort: 11,
 | |
| 				HostPort:      -1,
 | |
| 				Protocol:      "TCP",
 | |
| 			},
 | |
| 			}}},
 | |
| 			port:     intstr.FromString("a"),
 | |
| 			expected: 11,
 | |
| 			pass:     true,
 | |
| 			//this should fail but passes.
 | |
| 		},
 | |
| 		{
 | |
| 			name: "invalid ContainerPort",
 | |
| 			containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 				Name:          "a",
 | |
| 				ContainerPort: -1,
 | |
| 				Protocol:      "TCP",
 | |
| 			},
 | |
| 			}}},
 | |
| 			port:     intstr.FromString("a"),
 | |
| 			expected: -1,
 | |
| 			pass:     true,
 | |
| 			//this should fail but passes
 | |
| 		},
 | |
| 		{
 | |
| 			name: "HostIP Address",
 | |
| 			containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
 | |
| 				Name:          "a",
 | |
| 				ContainerPort: 11,
 | |
| 				HostIP:        "192.168.1.1",
 | |
| 				Protocol:      "TCP",
 | |
| 			},
 | |
| 			}}},
 | |
| 			port:     intstr.FromString("a"),
 | |
| 			expected: 11,
 | |
| 			pass:     true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		port, err := FindPort(&v1.Pod{Spec: v1.PodSpec{Containers: tc.containers}},
 | |
| 			&v1.ServicePort{Protocol: "TCP", TargetPort: tc.port})
 | |
| 		if err != nil && tc.pass {
 | |
| 			t.Errorf("unexpected error for %s: %v", tc.name, err)
 | |
| 		}
 | |
| 		if err == nil && !tc.pass {
 | |
| 			t.Errorf("unexpected non-error for %s: %d", tc.name, port)
 | |
| 		}
 | |
| 		if port != tc.expected {
 | |
| 			t.Errorf("wrong result for %s: expected %d, got %d", tc.name, tc.expected, port)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVisitContainers(t *testing.T) {
 | |
| 	setAllFeatureEnabledContainersDuringTest := ContainerType(0)
 | |
| 	testCases := []struct {
 | |
| 		desc           string
 | |
| 		spec           *v1.PodSpec
 | |
| 		wantContainers []string
 | |
| 		mask           ContainerType
 | |
| 	}{
 | |
| 		{
 | |
| 			desc:           "empty podspec",
 | |
| 			spec:           &v1.PodSpec{},
 | |
| 			wantContainers: []string{},
 | |
| 			mask:           AllContainers,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "regular containers",
 | |
| 			spec: &v1.PodSpec{
 | |
| 				Containers: []v1.Container{
 | |
| 					{Name: "c1"},
 | |
| 					{Name: "c2"},
 | |
| 				},
 | |
| 				InitContainers: []v1.Container{
 | |
| 					{Name: "i1"},
 | |
| 					{Name: "i2"},
 | |
| 				},
 | |
| 				EphemeralContainers: []v1.EphemeralContainer{
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
 | |
| 				},
 | |
| 			},
 | |
| 			wantContainers: []string{"c1", "c2"},
 | |
| 			mask:           Containers,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "init containers",
 | |
| 			spec: &v1.PodSpec{
 | |
| 				Containers: []v1.Container{
 | |
| 					{Name: "c1"},
 | |
| 					{Name: "c2"},
 | |
| 				},
 | |
| 				InitContainers: []v1.Container{
 | |
| 					{Name: "i1"},
 | |
| 					{Name: "i2"},
 | |
| 				},
 | |
| 				EphemeralContainers: []v1.EphemeralContainer{
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
 | |
| 				},
 | |
| 			},
 | |
| 			wantContainers: []string{"i1", "i2"},
 | |
| 			mask:           InitContainers,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "ephemeral containers",
 | |
| 			spec: &v1.PodSpec{
 | |
| 				Containers: []v1.Container{
 | |
| 					{Name: "c1"},
 | |
| 					{Name: "c2"},
 | |
| 				},
 | |
| 				InitContainers: []v1.Container{
 | |
| 					{Name: "i1"},
 | |
| 					{Name: "i2"},
 | |
| 				},
 | |
| 				EphemeralContainers: []v1.EphemeralContainer{
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
 | |
| 				},
 | |
| 			},
 | |
| 			wantContainers: []string{"e1", "e2"},
 | |
| 			mask:           EphemeralContainers,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "all container types",
 | |
| 			spec: &v1.PodSpec{
 | |
| 				Containers: []v1.Container{
 | |
| 					{Name: "c1"},
 | |
| 					{Name: "c2"},
 | |
| 				},
 | |
| 				InitContainers: []v1.Container{
 | |
| 					{Name: "i1"},
 | |
| 					{Name: "i2"},
 | |
| 				},
 | |
| 				EphemeralContainers: []v1.EphemeralContainer{
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
 | |
| 				},
 | |
| 			},
 | |
| 			wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"},
 | |
| 			mask:           AllContainers,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "all feature enabled container types",
 | |
| 			spec: &v1.PodSpec{
 | |
| 				Containers: []v1.Container{
 | |
| 					{Name: "c1"},
 | |
| 					{Name: "c2", SecurityContext: &v1.SecurityContext{}},
 | |
| 				},
 | |
| 				InitContainers: []v1.Container{
 | |
| 					{Name: "i1"},
 | |
| 					{Name: "i2", SecurityContext: &v1.SecurityContext{}},
 | |
| 				},
 | |
| 				EphemeralContainers: []v1.EphemeralContainer{
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
 | |
| 				},
 | |
| 			},
 | |
| 			wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"},
 | |
| 			mask:           setAllFeatureEnabledContainersDuringTest,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "dropping fields",
 | |
| 			spec: &v1.PodSpec{
 | |
| 				Containers: []v1.Container{
 | |
| 					{Name: "c1"},
 | |
| 					{Name: "c2", SecurityContext: &v1.SecurityContext{}},
 | |
| 				},
 | |
| 				InitContainers: []v1.Container{
 | |
| 					{Name: "i1"},
 | |
| 					{Name: "i2", SecurityContext: &v1.SecurityContext{}},
 | |
| 				},
 | |
| 				EphemeralContainers: []v1.EphemeralContainer{
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
 | |
| 					{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2", SecurityContext: &v1.SecurityContext{}}},
 | |
| 				},
 | |
| 			},
 | |
| 			wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"},
 | |
| 			mask:           AllContainers,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			if tc.mask == setAllFeatureEnabledContainersDuringTest {
 | |
| 				tc.mask = AllFeatureEnabledContainers()
 | |
| 			}
 | |
| 
 | |
| 			gotContainers := []string{}
 | |
| 			VisitContainers(tc.spec, tc.mask, func(c *v1.Container, containerType ContainerType) bool {
 | |
| 				gotContainers = append(gotContainers, c.Name)
 | |
| 				if c.SecurityContext != nil {
 | |
| 					c.SecurityContext = nil
 | |
| 				}
 | |
| 				return true
 | |
| 			})
 | |
| 			if !cmp.Equal(gotContainers, tc.wantContainers) {
 | |
| 				t.Errorf("VisitContainers() = %+v, want %+v", gotContainers, tc.wantContainers)
 | |
| 			}
 | |
| 			for _, c := range tc.spec.Containers {
 | |
| 				if c.SecurityContext != nil {
 | |
| 					t.Errorf("VisitContainers() did not drop SecurityContext for container %q", c.Name)
 | |
| 				}
 | |
| 			}
 | |
| 			for _, c := range tc.spec.InitContainers {
 | |
| 				if c.SecurityContext != nil {
 | |
| 					t.Errorf("VisitContainers() did not drop SecurityContext for init container %q", c.Name)
 | |
| 				}
 | |
| 			}
 | |
| 			for _, c := range tc.spec.EphemeralContainers {
 | |
| 				if c.SecurityContext != nil {
 | |
| 					t.Errorf("VisitContainers() did not drop SecurityContext for ephemeral container %q", c.Name)
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPodSecrets(t *testing.T) {
 | |
| 	// Stub containing all possible secret references in a pod.
 | |
| 	// The names of the referenced secrets match struct paths detected by reflection.
 | |
| 	pod := &v1.Pod{
 | |
| 		Spec: v1.PodSpec{
 | |
| 			Containers: []v1.Container{{
 | |
| 				EnvFrom: []v1.EnvFromSource{{
 | |
| 					SecretRef: &v1.SecretEnvSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: "Spec.Containers[*].EnvFrom[*].SecretRef"}}}},
 | |
| 				Env: []v1.EnvVar{{
 | |
| 					ValueFrom: &v1.EnvVarSource{
 | |
| 						SecretKeyRef: &v1.SecretKeySelector{
 | |
| 							LocalObjectReference: v1.LocalObjectReference{
 | |
| 								Name: "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}},
 | |
| 			ImagePullSecrets: []v1.LocalObjectReference{{
 | |
| 				Name: "Spec.ImagePullSecrets"}},
 | |
| 			InitContainers: []v1.Container{{
 | |
| 				EnvFrom: []v1.EnvFromSource{{
 | |
| 					SecretRef: &v1.SecretEnvSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: "Spec.InitContainers[*].EnvFrom[*].SecretRef"}}}},
 | |
| 				Env: []v1.EnvVar{{
 | |
| 					ValueFrom: &v1.EnvVarSource{
 | |
| 						SecretKeyRef: &v1.SecretKeySelector{
 | |
| 							LocalObjectReference: v1.LocalObjectReference{
 | |
| 								Name: "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}},
 | |
| 			Volumes: []v1.Volume{{
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					AzureFile: &v1.AzureFileVolumeSource{
 | |
| 						SecretName: "Spec.Volumes[*].VolumeSource.AzureFile.SecretName"}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					CephFS: &v1.CephFSVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.CephFS.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					Cinder: &v1.CinderVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.Cinder.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					FlexVolume: &v1.FlexVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					Projected: &v1.ProjectedVolumeSource{
 | |
| 						Sources: []v1.VolumeProjection{{
 | |
| 							Secret: &v1.SecretProjection{
 | |
| 								LocalObjectReference: v1.LocalObjectReference{
 | |
| 									Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret"}}}}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					RBD: &v1.RBDVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.RBD.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					Secret: &v1.SecretVolumeSource{
 | |
| 						SecretName: "Spec.Volumes[*].VolumeSource.Secret.SecretName"}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					Secret: &v1.SecretVolumeSource{
 | |
| 						SecretName: "Spec.Volumes[*].VolumeSource.Secret"}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					ScaleIO: &v1.ScaleIOVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					ISCSI: &v1.ISCSIVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					StorageOS: &v1.StorageOSVolumeSource{
 | |
| 						SecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					CSI: &v1.CSIVolumeSource{
 | |
| 						NodePublishSecretRef: &v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}},
 | |
| 			EphemeralContainers: []v1.EphemeralContainer{{
 | |
| 				EphemeralContainerCommon: v1.EphemeralContainerCommon{
 | |
| 					EnvFrom: []v1.EnvFromSource{{
 | |
| 						SecretRef: &v1.SecretEnvSource{
 | |
| 							LocalObjectReference: v1.LocalObjectReference{
 | |
| 								Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef"}}}},
 | |
| 					Env: []v1.EnvVar{{
 | |
| 						ValueFrom: &v1.EnvVarSource{
 | |
| 							SecretKeyRef: &v1.SecretKeySelector{
 | |
| 								LocalObjectReference: v1.LocalObjectReference{
 | |
| 									Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef"}}}}}}}},
 | |
| 		},
 | |
| 	}
 | |
| 	extractedNames := sets.New[string]()
 | |
| 	VisitPodSecretNames(pod, func(name string) bool {
 | |
| 		extractedNames.Insert(name)
 | |
| 		return true
 | |
| 	})
 | |
| 
 | |
| 	// excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects
 | |
| 	excludedSecretPaths := sets.New[string](
 | |
| 		"Spec.Volumes[*].VolumeSource.CephFS.SecretFile",
 | |
| 	)
 | |
| 	// expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects.
 | |
| 	// every path here should be represented as an example in the Pod stub above, with the secret name set to the path.
 | |
| 	expectedSecretPaths := sets.New[string](
 | |
| 		"Spec.Containers[*].EnvFrom[*].SecretRef",
 | |
| 		"Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
 | |
| 		"Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef",
 | |
| 		"Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef",
 | |
| 		"Spec.ImagePullSecrets",
 | |
| 		"Spec.InitContainers[*].EnvFrom[*].SecretRef",
 | |
| 		"Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.AzureFile.SecretName",
 | |
| 		"Spec.Volumes[*].VolumeSource.CephFS.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.Cinder.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret",
 | |
| 		"Spec.Volumes[*].VolumeSource.RBD.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.Secret",
 | |
| 		"Spec.Volumes[*].VolumeSource.Secret.SecretName",
 | |
| 		"Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.ISCSI.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.StorageOS.SecretRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef",
 | |
| 	)
 | |
| 	secretPaths := collectResourcePaths(t, "secret", nil, "", reflect.TypeOf(&v1.Pod{}))
 | |
| 	secretPaths = secretPaths.Difference(excludedSecretPaths)
 | |
| 	if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 {
 | |
| 		t.Logf("Missing expected secret paths:\n%s", strings.Join(sets.List[string](missingPaths), "\n"))
 | |
| 		t.Error("Missing expected secret paths. Verify VisitPodSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths")
 | |
| 	}
 | |
| 	if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 {
 | |
| 		t.Logf("Extra secret paths:\n%s", strings.Join(sets.List(extraPaths), "\n"))
 | |
| 		t.Error("Extra fields with 'secret' in the name found. Verify VisitPodSecretNames() is including these fields if appropriate, then correct expectedSecretPaths")
 | |
| 	}
 | |
| 
 | |
| 	if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 {
 | |
| 		t.Logf("Missing expected secret names:\n%s", strings.Join(sets.List[string](missingNames), "\n"))
 | |
| 		t.Error("Missing expected secret names. Verify the pod stub above includes these references, then verify VisitPodSecretNames() is correctly finding the missing names")
 | |
| 	}
 | |
| 	if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 {
 | |
| 		t.Logf("Extra secret names:\n%s", strings.Join(sets.List[string](extraNames), "\n"))
 | |
| 		t.Error("Extra secret names extracted. Verify VisitPodSecretNames() is correctly extracting secret names")
 | |
| 	}
 | |
| 
 | |
| 	// emptyPod is a stub containing empty object names
 | |
| 	emptyPod := &v1.Pod{
 | |
| 		Spec: v1.PodSpec{
 | |
| 			Containers: []v1.Container{{
 | |
| 				EnvFrom: []v1.EnvFromSource{{
 | |
| 					SecretRef: &v1.SecretEnvSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: ""}}}}}},
 | |
| 		},
 | |
| 	}
 | |
| 	VisitPodSecretNames(emptyPod, func(name string) bool {
 | |
| 		t.Fatalf("expected no empty names collected, got %q", name)
 | |
| 		return false
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // collectResourcePaths traverses the object, computing all the struct paths that lead to fields with resourcename in the name.
 | |
| func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, name string, tp reflect.Type) sets.Set[string] {
 | |
| 	resourcename = strings.ToLower(resourcename)
 | |
| 	resourcePaths := sets.New[string]()
 | |
| 
 | |
| 	if tp.Kind() == reflect.Pointer {
 | |
| 		resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path, name, tp.Elem()))...)
 | |
| 		return resourcePaths
 | |
| 	}
 | |
| 
 | |
| 	if strings.Contains(strings.ToLower(name), resourcename) {
 | |
| 		resourcePaths.Insert(path.String())
 | |
| 	}
 | |
| 
 | |
| 	switch tp.Kind() {
 | |
| 	case reflect.Pointer:
 | |
| 		resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path, name, tp.Elem()))...)
 | |
| 	case reflect.Struct:
 | |
| 		// ObjectMeta is generic and therefore should never have a field with a specific resource's name;
 | |
| 		// it contains cycles so it's easiest to just skip it.
 | |
| 		if name == "ObjectMeta" {
 | |
| 			break
 | |
| 		}
 | |
| 		for i := 0; i < tp.NumField(); i++ {
 | |
| 			field := tp.Field(i)
 | |
| 			resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type))...)
 | |
| 		}
 | |
| 	case reflect.Interface:
 | |
| 		t.Errorf("cannot find %s fields in interface{} field %s", resourcename, path.String())
 | |
| 	case reflect.Map:
 | |
| 		resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()))...)
 | |
| 	case reflect.Slice:
 | |
| 		resourcePaths.Insert(sets.List[string](collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()))...)
 | |
| 	default:
 | |
| 		// all primitive types
 | |
| 	}
 | |
| 
 | |
| 	return resourcePaths
 | |
| }
 | |
| 
 | |
| func TestPodConfigmaps(t *testing.T) {
 | |
| 	// Stub containing all possible ConfigMap references in a pod.
 | |
| 	// The names of the referenced ConfigMaps match struct paths detected by reflection.
 | |
| 	pod := &v1.Pod{
 | |
| 		Spec: v1.PodSpec{
 | |
| 			Containers: []v1.Container{{
 | |
| 				EnvFrom: []v1.EnvFromSource{{
 | |
| 					ConfigMapRef: &v1.ConfigMapEnvSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: "Spec.Containers[*].EnvFrom[*].ConfigMapRef"}}}},
 | |
| 				Env: []v1.EnvVar{{
 | |
| 					ValueFrom: &v1.EnvVarSource{
 | |
| 						ConfigMapKeyRef: &v1.ConfigMapKeySelector{
 | |
| 							LocalObjectReference: v1.LocalObjectReference{
 | |
| 								Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
 | |
| 			EphemeralContainers: []v1.EphemeralContainer{{
 | |
| 				EphemeralContainerCommon: v1.EphemeralContainerCommon{
 | |
| 					EnvFrom: []v1.EnvFromSource{{
 | |
| 						ConfigMapRef: &v1.ConfigMapEnvSource{
 | |
| 							LocalObjectReference: v1.LocalObjectReference{
 | |
| 								Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef"}}}},
 | |
| 					Env: []v1.EnvVar{{
 | |
| 						ValueFrom: &v1.EnvVarSource{
 | |
| 							ConfigMapKeyRef: &v1.ConfigMapKeySelector{
 | |
| 								LocalObjectReference: v1.LocalObjectReference{
 | |
| 									Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}},
 | |
| 			InitContainers: []v1.Container{{
 | |
| 				EnvFrom: []v1.EnvFromSource{{
 | |
| 					ConfigMapRef: &v1.ConfigMapEnvSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef"}}}},
 | |
| 				Env: []v1.EnvVar{{
 | |
| 					ValueFrom: &v1.EnvVarSource{
 | |
| 						ConfigMapKeyRef: &v1.ConfigMapKeySelector{
 | |
| 							LocalObjectReference: v1.LocalObjectReference{
 | |
| 								Name: "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
 | |
| 			Volumes: []v1.Volume{{
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					Projected: &v1.ProjectedVolumeSource{
 | |
| 						Sources: []v1.VolumeProjection{{
 | |
| 							ConfigMap: &v1.ConfigMapProjection{
 | |
| 								LocalObjectReference: v1.LocalObjectReference{
 | |
| 									Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap"}}}}}}}, {
 | |
| 				VolumeSource: v1.VolumeSource{
 | |
| 					ConfigMap: &v1.ConfigMapVolumeSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: "Spec.Volumes[*].VolumeSource.ConfigMap"}}}}},
 | |
| 		},
 | |
| 	}
 | |
| 	extractedNames := sets.New[string]()
 | |
| 	VisitPodConfigmapNames(pod, func(name string) bool {
 | |
| 		extractedNames.Insert(name)
 | |
| 		return true
 | |
| 	})
 | |
| 
 | |
| 	// expectedPaths holds struct paths to fields with "ConfigMap" in the name that are references to ConfigMap API objects.
 | |
| 	// every path here should be represented as an example in the Pod stub above, with the ConfigMap name set to the path.
 | |
| 	expectedPaths := sets.New[string](
 | |
| 		"Spec.Containers[*].EnvFrom[*].ConfigMapRef",
 | |
| 		"Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef",
 | |
| 		"Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef",
 | |
| 		"Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef",
 | |
| 		"Spec.InitContainers[*].EnvFrom[*].ConfigMapRef",
 | |
| 		"Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef",
 | |
| 		"Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap",
 | |
| 		"Spec.Volumes[*].VolumeSource.ConfigMap",
 | |
| 	)
 | |
| 	collectPaths := collectResourcePaths(t, "ConfigMap", nil, "", reflect.TypeOf(&v1.Pod{}))
 | |
| 	if missingPaths := expectedPaths.Difference(collectPaths); len(missingPaths) > 0 {
 | |
| 		t.Logf("Missing expected paths:\n%s", strings.Join(sets.List[string](missingPaths), "\n"))
 | |
| 		t.Error("Missing expected paths. Verify VisitPodConfigmapNames() is correctly finding the missing paths, then correct expectedPaths")
 | |
| 	}
 | |
| 	if extraPaths := collectPaths.Difference(expectedPaths); len(extraPaths) > 0 {
 | |
| 		t.Logf("Extra paths:\n%s", strings.Join(sets.List[string](extraPaths), "\n"))
 | |
| 		t.Error("Extra fields with resource in the name found. Verify VisitPodConfigmapNames() is including these fields if appropriate, then correct expectedPaths")
 | |
| 	}
 | |
| 
 | |
| 	if missingNames := expectedPaths.Difference(extractedNames); len(missingNames) > 0 {
 | |
| 		t.Logf("Missing expected names:\n%s", strings.Join(sets.List[string](missingNames), "\n"))
 | |
| 		t.Error("Missing expected names. Verify the pod stub above includes these references, then verify VisitPodConfigmapNames() is correctly finding the missing names")
 | |
| 	}
 | |
| 	if extraNames := extractedNames.Difference(expectedPaths); len(extraNames) > 0 {
 | |
| 		t.Logf("Extra names:\n%s", strings.Join(sets.List[string](extraNames), "\n"))
 | |
| 		t.Error("Extra names extracted. Verify VisitPodConfigmapNames() is correctly extracting resource names")
 | |
| 	}
 | |
| 
 | |
| 	// emptyPod is a stub containing empty object names
 | |
| 	emptyPod := &v1.Pod{
 | |
| 		Spec: v1.PodSpec{
 | |
| 			Containers: []v1.Container{{
 | |
| 				EnvFrom: []v1.EnvFromSource{{
 | |
| 					ConfigMapRef: &v1.ConfigMapEnvSource{
 | |
| 						LocalObjectReference: v1.LocalObjectReference{
 | |
| 							Name: ""}}}}}},
 | |
| 		},
 | |
| 	}
 | |
| 	VisitPodConfigmapNames(emptyPod, func(name string) bool {
 | |
| 		t.Fatalf("expected no empty names collected, got %q", name)
 | |
| 		return false
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func newPod(now metav1.Time, ready bool, beforeSec int) *v1.Pod {
 | |
| 	conditionStatus := v1.ConditionFalse
 | |
| 	if ready {
 | |
| 		conditionStatus = v1.ConditionTrue
 | |
| 	}
 | |
| 	return &v1.Pod{
 | |
| 		Status: v1.PodStatus{
 | |
| 			Conditions: []v1.PodCondition{
 | |
| 				{
 | |
| 					Type:               v1.PodReady,
 | |
| 					LastTransitionTime: metav1.NewTime(now.Time.Add(-1 * time.Duration(beforeSec) * time.Second)),
 | |
| 					Status:             conditionStatus,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIsPodAvailable(t *testing.T) {
 | |
| 	now := metav1.Now()
 | |
| 	tests := []struct {
 | |
| 		pod             *v1.Pod
 | |
| 		minReadySeconds int32
 | |
| 		expected        bool
 | |
| 	}{
 | |
| 		{
 | |
| 			pod:             newPod(now, false, 0),
 | |
| 			minReadySeconds: 0,
 | |
| 			expected:        false,
 | |
| 		},
 | |
| 		{
 | |
| 			pod:             newPod(now, true, 0),
 | |
| 			minReadySeconds: 1,
 | |
| 			expected:        false,
 | |
| 		},
 | |
| 		{
 | |
| 			pod:             newPod(now, true, 0),
 | |
| 			minReadySeconds: 0,
 | |
| 			expected:        true,
 | |
| 		},
 | |
| 		{
 | |
| 			pod:             newPod(now, true, 51),
 | |
| 			minReadySeconds: 50,
 | |
| 			expected:        true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for i, test := range tests {
 | |
| 		isAvailable := IsPodAvailable(test.pod, test.minReadySeconds, now)
 | |
| 		if isAvailable != test.expected {
 | |
| 			t.Errorf("[tc #%d] expected available pod: %t, got: %t", i, test.expected, isAvailable)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIsPodTerminal(t *testing.T) {
 | |
| 	now := metav1.Now()
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		podPhase v1.PodPhase
 | |
| 		expected bool
 | |
| 	}{
 | |
| 		{
 | |
| 			podPhase: v1.PodFailed,
 | |
| 			expected: true,
 | |
| 		},
 | |
| 		{
 | |
| 			podPhase: v1.PodSucceeded,
 | |
| 			expected: true,
 | |
| 		},
 | |
| 		{
 | |
| 			podPhase: v1.PodUnknown,
 | |
| 			expected: false,
 | |
| 		},
 | |
| 		{
 | |
| 			podPhase: v1.PodPending,
 | |
| 			expected: false,
 | |
| 		},
 | |
| 		{
 | |
| 			podPhase: v1.PodRunning,
 | |
| 			expected: false,
 | |
| 		},
 | |
| 		{
 | |
| 			expected: false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for i, test := range tests {
 | |
| 		pod := newPod(now, true, 0)
 | |
| 		pod.Status.Phase = test.podPhase
 | |
| 		isTerminal := IsPodTerminal(pod)
 | |
| 		if isTerminal != test.expected {
 | |
| 			t.Errorf("[tc #%d] expected terminal pod: %t, got: %t", i, test.expected, isTerminal)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGetContainerStatus(t *testing.T) {
 | |
| 	type ExpectedStruct struct {
 | |
| 		status v1.ContainerStatus
 | |
| 		exists bool
 | |
| 	}
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		status   []v1.ContainerStatus
 | |
| 		name     string
 | |
| 		expected ExpectedStruct
 | |
| 		desc     string
 | |
| 	}{
 | |
| 		{
 | |
| 			status:   []v1.ContainerStatus{{Name: "test1", Ready: false, Image: "image1"}, {Name: "test2", Ready: true, Image: "image1"}},
 | |
| 			name:     "test1",
 | |
| 			expected: ExpectedStruct{status: v1.ContainerStatus{Name: "test1", Ready: false, Image: "image1"}, exists: true},
 | |
| 			desc:     "retrieve ContainerStatus with Name=\"test1\"",
 | |
| 		},
 | |
| 		{
 | |
| 			status:   []v1.ContainerStatus{{Name: "test2", Ready: false, Image: "image2"}},
 | |
| 			name:     "test1",
 | |
| 			expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false},
 | |
| 			desc:     "no matching ContainerStatus with Name=\"test1\"",
 | |
| 		},
 | |
| 		{
 | |
| 			status:   []v1.ContainerStatus{{Name: "test3", Ready: false, Image: "image3"}},
 | |
| 			name:     "",
 | |
| 			expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false},
 | |
| 			desc:     "retrieve an empty ContainerStatus with container name empty",
 | |
| 		},
 | |
| 		{
 | |
| 			status:   nil,
 | |
| 			name:     "",
 | |
| 			expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false},
 | |
| 			desc:     "retrieve an empty ContainerStatus with status nil",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		resultStatus, exists := GetContainerStatus(test.status, test.name)
 | |
| 		assert.Equal(t, test.expected.status, resultStatus, "GetContainerStatus: "+test.desc)
 | |
| 		assert.Equal(t, test.expected.exists, exists, "GetContainerStatus: "+test.desc)
 | |
| 
 | |
| 		resultStatus = GetExistingContainerStatus(test.status, test.name)
 | |
| 		assert.Equal(t, test.expected.status, resultStatus, "GetExistingContainerStatus: "+test.desc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGetIndexOfContainerStatus(t *testing.T) {
 | |
| 	testStatus := []v1.ContainerStatus{
 | |
| 		{
 | |
| 			Name:  "c1",
 | |
| 			Ready: false,
 | |
| 			Image: "image1",
 | |
| 		},
 | |
| 		{
 | |
| 			Name:  "c2",
 | |
| 			Ready: true,
 | |
| 			Image: "image1",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		desc           string
 | |
| 		containerName  string
 | |
| 		expectedExists bool
 | |
| 		expectedIndex  int
 | |
| 	}{
 | |
| 		{
 | |
| 			desc:           "first container",
 | |
| 			containerName:  "c1",
 | |
| 			expectedExists: true,
 | |
| 			expectedIndex:  0,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:           "second container",
 | |
| 			containerName:  "c2",
 | |
| 			expectedExists: true,
 | |
| 			expectedIndex:  1,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:           "non-existent container",
 | |
| 			containerName:  "c3",
 | |
| 			expectedExists: false,
 | |
| 			expectedIndex:  0,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		idx, exists := GetIndexOfContainerStatus(testStatus, test.containerName)
 | |
| 		assert.Equal(t, test.expectedExists, exists, "GetIndexOfContainerStatus: "+test.desc)
 | |
| 		assert.Equal(t, test.expectedIndex, idx, "GetIndexOfContainerStatus: "+test.desc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestUpdatePodCondition(t *testing.T) {
 | |
| 	time := metav1.Now()
 | |
| 
 | |
| 	podStatus := v1.PodStatus{
 | |
| 		Conditions: []v1.PodCondition{
 | |
| 			{
 | |
| 				Type:               v1.PodReady,
 | |
| 				Status:             v1.ConditionTrue,
 | |
| 				Reason:             "successfully",
 | |
| 				Message:            "sync pod successfully",
 | |
| 				LastProbeTime:      time,
 | |
| 				LastTransitionTime: metav1.NewTime(time.Add(1000)),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		status     *v1.PodStatus
 | |
| 		conditions v1.PodCondition
 | |
| 		expected   bool
 | |
| 		desc       string
 | |
| 	}{
 | |
| 		{
 | |
| 			status: &podStatus,
 | |
| 			conditions: v1.PodCondition{
 | |
| 				Type:               v1.PodReady,
 | |
| 				Status:             v1.ConditionTrue,
 | |
| 				Reason:             "successfully",
 | |
| 				Message:            "sync pod successfully",
 | |
| 				LastProbeTime:      time,
 | |
| 				LastTransitionTime: metav1.NewTime(time.Add(1000))},
 | |
| 			expected: false,
 | |
| 			desc:     "all equal, no update",
 | |
| 		},
 | |
| 		{
 | |
| 			status: &podStatus,
 | |
| 			conditions: v1.PodCondition{
 | |
| 				Type:               v1.PodScheduled,
 | |
| 				Status:             v1.ConditionTrue,
 | |
| 				Reason:             "successfully",
 | |
| 				Message:            "sync pod successfully",
 | |
| 				LastProbeTime:      time,
 | |
| 				LastTransitionTime: metav1.NewTime(time.Add(1000))},
 | |
| 			expected: true,
 | |
| 			desc:     "not equal Type, should get updated",
 | |
| 		},
 | |
| 		{
 | |
| 			status: &podStatus,
 | |
| 			conditions: v1.PodCondition{
 | |
| 				Type:               v1.PodReady,
 | |
| 				Status:             v1.ConditionFalse,
 | |
| 				Reason:             "successfully",
 | |
| 				Message:            "sync pod successfully",
 | |
| 				LastProbeTime:      time,
 | |
| 				LastTransitionTime: metav1.NewTime(time.Add(1000))},
 | |
| 			expected: true,
 | |
| 			desc:     "not equal Status, should get updated",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		resultStatus := UpdatePodCondition(test.status, &test.conditions)
 | |
| 
 | |
| 		assert.Equal(t, test.expected, resultStatus, test.desc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGetContainersReadyCondition(t *testing.T) {
 | |
| 	time := metav1.Now()
 | |
| 
 | |
| 	containersReadyCondition := v1.PodCondition{
 | |
| 		Type:               v1.ContainersReady,
 | |
| 		Status:             v1.ConditionTrue,
 | |
| 		Reason:             "successfully",
 | |
| 		Message:            "sync pod successfully",
 | |
| 		LastProbeTime:      time,
 | |
| 		LastTransitionTime: metav1.NewTime(time.Add(1000)),
 | |
| 	}
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		desc              string
 | |
| 		podStatus         v1.PodStatus
 | |
| 		expectedCondition *v1.PodCondition
 | |
| 	}{
 | |
| 		{
 | |
| 			desc: "containers ready condition exists",
 | |
| 			podStatus: v1.PodStatus{
 | |
| 				Conditions: []v1.PodCondition{containersReadyCondition},
 | |
| 			},
 | |
| 			expectedCondition: &containersReadyCondition,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "containers ready condition does not exist",
 | |
| 			podStatus: v1.PodStatus{
 | |
| 				Conditions: []v1.PodCondition{},
 | |
| 			},
 | |
| 			expectedCondition: nil,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		containersReadyCondition := GetContainersReadyCondition(test.podStatus)
 | |
| 		assert.Equal(t, test.expectedCondition, containersReadyCondition, test.desc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIsContainersReadyConditionTrue(t *testing.T) {
 | |
| 	time := metav1.Now()
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		desc      string
 | |
| 		podStatus v1.PodStatus
 | |
| 		expected  bool
 | |
| 	}{
 | |
| 		{
 | |
| 			desc: "containers ready condition is true",
 | |
| 			podStatus: v1.PodStatus{
 | |
| 				Conditions: []v1.PodCondition{
 | |
| 					{
 | |
| 						Type:               v1.ContainersReady,
 | |
| 						Status:             v1.ConditionTrue,
 | |
| 						Reason:             "successfully",
 | |
| 						Message:            "sync pod successfully",
 | |
| 						LastProbeTime:      time,
 | |
| 						LastTransitionTime: metav1.NewTime(time.Add(1000)),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			expected: true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "containers ready condition is false",
 | |
| 			podStatus: v1.PodStatus{
 | |
| 				Conditions: []v1.PodCondition{
 | |
| 					{
 | |
| 						Type:               v1.ContainersReady,
 | |
| 						Status:             v1.ConditionFalse,
 | |
| 						Reason:             "successfully",
 | |
| 						Message:            "sync pod successfully",
 | |
| 						LastProbeTime:      time,
 | |
| 						LastTransitionTime: metav1.NewTime(time.Add(1000)),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			expected: false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "containers ready condition is empty",
 | |
| 			podStatus: v1.PodStatus{
 | |
| 				Conditions: []v1.PodCondition{},
 | |
| 			},
 | |
| 			expected: false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		isContainersReady := IsContainersReadyConditionTrue(test.podStatus)
 | |
| 		assert.Equal(t, test.expected, isContainersReady, test.desc)
 | |
| 	}
 | |
| }
 |