k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e_node/pod_host_ips.go (about) 1 /* 2 Copyright 2023 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 e2enode 18 19 import ( 20 "context" 21 "fmt" 22 23 "github.com/onsi/ginkgo/v2" 24 "github.com/onsi/gomega" 25 26 v1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/uuid" 30 netutils "k8s.io/utils/net" 31 32 utilfeature "k8s.io/apiserver/pkg/util/feature" 33 kubefeatures "k8s.io/kubernetes/pkg/features" 34 utilnode "k8s.io/kubernetes/pkg/util/node" 35 "k8s.io/kubernetes/test/e2e/framework" 36 e2enetwork "k8s.io/kubernetes/test/e2e/framework/network" 37 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 38 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 39 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 40 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 41 "k8s.io/kubernetes/test/e2e/network/common" 42 imageutils "k8s.io/kubernetes/test/utils/image" 43 admissionapi "k8s.io/pod-security-admission/api" 44 ) 45 46 var _ = common.SIGDescribe("Pod Host IPs", framework.WithSerial(), func() { 47 f := framework.NewDefaultFramework("host-ips") 48 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 49 50 ginkgo.Context("when creating a Pod", func() { 51 ginkgo.It("should add node IPs of all supported families to hostIPs of pod-network pod", func(ctx context.Context) { 52 podName := "pod-dualstack-host-ips" 53 54 pod := genPodHostIPs(podName+string(uuid.NewUUID()), false) 55 56 ginkgo.By("submitting the pod to kubernetes") 57 podClient := e2epod.NewPodClient(f) 58 p := podClient.CreateSync(ctx, pod) 59 60 gomega.Expect(p.Status.HostIP).ShouldNot(gomega.BeEquivalentTo("")) 61 gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil()) 62 63 // validate first ip in HostIPs is same as HostIP 64 gomega.Expect(p.Status.HostIP).Should(gomega.Equal(p.Status.HostIPs[0].IP)) 65 if len(p.Status.HostIPs) > 1 { 66 // assert 2 host ips belong to different families 67 if netutils.IsIPv4String(p.Status.HostIPs[0].IP) == netutils.IsIPv4String(p.Status.HostIPs[1].IP) { 68 framework.Failf("both internalIPs %s and %s belong to the same families", p.Status.HostIPs[0].IP, p.Status.HostIPs[1].IP) 69 } 70 } 71 72 ginkgo.By("comparing pod.Status.HostIPs against node.Status.Addresses") 73 hostIPs, err := genHostIPsForNode(ctx, f, p.Spec.NodeName) 74 framework.ExpectNoError(err, "failed to fetch node IPs") 75 gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(hostIPs)) 76 77 ginkgo.By("deleting the pod") 78 err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) 79 framework.ExpectNoError(err, "failed to delete pod") 80 }) 81 82 ginkgo.It("should add node IPs of all supported families to hostIPs of host-network pod", func(ctx context.Context) { 83 podName := "pod-dualstack-host-ips" 84 85 pod := genPodHostIPs(podName+string(uuid.NewUUID()), true) 86 87 ginkgo.By("submitting the pod to kubernetes") 88 podClient := e2epod.NewPodClient(f) 89 p := podClient.CreateSync(ctx, pod) 90 91 gomega.Expect(p.Status.HostIP).ShouldNot(gomega.BeEquivalentTo("")) 92 gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil()) 93 94 // validate first ip in HostIPs is same as HostIP 95 gomega.Expect(p.Status.HostIP).Should(gomega.Equal(p.Status.HostIPs[0].IP)) 96 if len(p.Status.HostIPs) > 1 { 97 // assert 2 host ips belong to different families 98 if netutils.IsIPv4String(p.Status.HostIPs[0].IP) == netutils.IsIPv4String(p.Status.HostIPs[1].IP) { 99 framework.Failf("both internalIPs %s and %s belong to the same families", p.Status.HostIPs[0].IP, p.Status.HostIPs[1].IP) 100 } 101 } 102 103 ginkgo.By("comparing pod.Status.HostIPs against node.Status.Addresses") 104 hostIPs, err := genHostIPsForNode(ctx, f, p.Spec.NodeName) 105 framework.ExpectNoError(err, "failed to fetch node IPs") 106 gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(hostIPs)) 107 108 ginkgo.By("deleting the pod") 109 err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) 110 framework.ExpectNoError(err, "failed to delete pod") 111 }) 112 113 ginkgo.It("should provide hostIPs as an env var", func(ctx context.Context) { 114 if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodHostIPs) { 115 e2eskipper.Skipf("PodHostIPs feature is not enabled") 116 } 117 118 podName := "downward-api-" + string(uuid.NewUUID()) 119 env := []v1.EnvVar{ 120 { 121 Name: "HOST_IPS", 122 ValueFrom: &v1.EnvVarSource{ 123 FieldRef: &v1.ObjectFieldSelector{ 124 APIVersion: "v1", 125 FieldPath: "status.hostIPs", 126 }, 127 }, 128 }, 129 } 130 131 expectations := []string{ 132 fmt.Sprintf("HOST_IPS=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6), 133 } 134 135 testDownwardAPI(ctx, f, podName, env, expectations) 136 }) 137 }) 138 }) 139 140 func genPodHostIPs(podName string, hostNetwork bool) *v1.Pod { 141 return &v1.Pod{ 142 ObjectMeta: metav1.ObjectMeta{ 143 Name: podName, 144 Labels: map[string]string{"test": "dualstack-host-ips"}, 145 }, 146 Spec: v1.PodSpec{ 147 Containers: []v1.Container{ 148 { 149 Name: "test-container", 150 Image: imageutils.GetE2EImage(imageutils.Agnhost), 151 }, 152 }, 153 RestartPolicy: v1.RestartPolicyNever, 154 HostNetwork: hostNetwork, 155 }, 156 } 157 } 158 159 func genHostIPsForNode(ctx context.Context, f *framework.Framework, nodeName string) ([]v1.HostIP, error) { 160 nodeList, err := e2enode.GetReadySchedulableNodes(ctx, f.ClientSet) 161 if err != nil { 162 return nil, err 163 } 164 for _, node := range nodeList.Items { 165 if node.Name == nodeName { 166 nodeIPs, err := utilnode.GetNodeHostIPs(&node) 167 if err != nil { 168 return nil, err 169 } 170 hostIPs := []v1.HostIP{} 171 for _, ip := range nodeIPs { 172 hostIPs = append(hostIPs, v1.HostIP{IP: ip.String()}) 173 } 174 return hostIPs, nil 175 } 176 } 177 return nil, fmt.Errorf("no such node %q", nodeName) 178 } 179 180 func testDownwardAPI(ctx context.Context, f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) { 181 pod := &v1.Pod{ 182 ObjectMeta: metav1.ObjectMeta{ 183 Name: podName, 184 Labels: map[string]string{"name": podName}, 185 }, 186 Spec: v1.PodSpec{ 187 Containers: []v1.Container{ 188 { 189 Name: "dapi-container", 190 Image: imageutils.GetE2EImage(imageutils.BusyBox), 191 Command: []string{"sh", "-c", "env"}, 192 Resources: v1.ResourceRequirements{ 193 Requests: v1.ResourceList{ 194 v1.ResourceCPU: resource.MustParse("250m"), 195 v1.ResourceMemory: resource.MustParse("32Mi"), 196 }, 197 Limits: v1.ResourceList{ 198 v1.ResourceCPU: resource.MustParse("1250m"), 199 v1.ResourceMemory: resource.MustParse("64Mi"), 200 }, 201 }, 202 Env: env, 203 }, 204 }, 205 RestartPolicy: v1.RestartPolicyNever, 206 }, 207 } 208 209 e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward api env vars", pod, 0, expectations) 210 }