1
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 "k8s.io/kubernetes/test/e2e/feature"
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 "k8s.io/kubernetes/test/e2e/nodefeature"
43 imageutils "k8s.io/kubernetes/test/utils/image"
44 admissionapi "k8s.io/pod-security-admission/api"
45 )
46
47 var _ = common.SIGDescribe("DualStack Host IP", framework.WithSerial(), nodefeature.PodHostIPs, feature.PodHostIPs, func() {
48 f := framework.NewDefaultFramework("dualstack")
49 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
50
51 ginkgo.Context("when creating a Pod", func() {
52 ginkgo.It("should create pod, add ipv6 and ipv4 ip to host ips", func(ctx context.Context) {
53
54 podName := "pod-dualstack-host-ips"
55
56 pod := genPodHostIPs(podName+string(uuid.NewUUID()), false)
57
58 ginkgo.By("submitting the pod to kubernetes")
59 podClient := e2epod.NewPodClient(f)
60 p := podClient.CreateSync(ctx, pod)
61
62 gomega.Expect(p.Status.HostIP).ShouldNot(gomega.BeEquivalentTo(""))
63 gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil())
64
65
66 gomega.Expect(p.Status.HostIP).Should(gomega.Equal(p.Status.HostIPs[0].IP))
67 if len(p.Status.HostIPs) > 1 {
68
69 if netutils.IsIPv4String(p.Status.HostIPs[0].IP) == netutils.IsIPv4String(p.Status.HostIPs[1].IP) {
70 framework.Failf("both internalIPs %s and %s belong to the same families", p.Status.HostIPs[0].IP, p.Status.HostIPs[1].IP)
71 }
72 }
73
74 nodeList, err := e2enode.GetReadySchedulableNodes(ctx, f.ClientSet)
75 framework.ExpectNoError(err)
76 for _, node := range nodeList.Items {
77 if node.Name == p.Spec.NodeName {
78 nodeIPs := []v1.HostIP{}
79 for _, address := range node.Status.Addresses {
80 if address.Type == v1.NodeInternalIP {
81 nodeIPs = append(nodeIPs, v1.HostIP{IP: address.Address})
82 }
83 }
84 gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(nodeIPs))
85 break
86 }
87 }
88
89 ginkgo.By("deleting the pod")
90 err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1))
91 framework.ExpectNoError(err, "failed to delete pod")
92 })
93
94 ginkgo.It("should create pod with hostNetwork, add ipv6 and ipv4 ip to host ips", func(ctx context.Context) {
95
96 podName := "pod-dualstack-host-ips"
97
98 pod := genPodHostIPs(podName+string(uuid.NewUUID()), true)
99
100 ginkgo.By("submitting the pod to kubernetes")
101 podClient := e2epod.NewPodClient(f)
102 p := podClient.CreateSync(ctx, pod)
103
104 gomega.Expect(p.Status.HostIP).ShouldNot(gomega.BeEquivalentTo(""))
105 gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil())
106
107
108 gomega.Expect(p.Status.HostIP).Should(gomega.Equal(p.Status.HostIPs[0].IP))
109 if len(p.Status.HostIPs) > 1 {
110
111 if netutils.IsIPv4String(p.Status.HostIPs[0].IP) == netutils.IsIPv4String(p.Status.HostIPs[1].IP) {
112 framework.Failf("both internalIPs %s and %s belong to the same families", p.Status.HostIPs[0].IP, p.Status.HostIPs[1].IP)
113 }
114 }
115
116 nodeList, err := e2enode.GetReadySchedulableNodes(ctx, f.ClientSet)
117 framework.ExpectNoError(err)
118 for _, node := range nodeList.Items {
119 if node.Name == p.Spec.NodeName {
120 nodeIPs := []v1.HostIP{}
121 for _, address := range node.Status.Addresses {
122 if address.Type == v1.NodeInternalIP {
123 nodeIPs = append(nodeIPs, v1.HostIP{IP: address.Address})
124 }
125 }
126 gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(nodeIPs))
127 break
128 }
129 }
130
131 ginkgo.By("deleting the pod")
132 err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1))
133 framework.ExpectNoError(err, "failed to delete pod")
134 })
135
136 ginkgo.It("should provide hostIPs as an env var", func(ctx context.Context) {
137 if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.PodHostIPs) {
138 e2eskipper.Skipf("PodHostIPs feature is not enabled")
139 }
140
141 podName := "downward-api-" + string(uuid.NewUUID())
142 env := []v1.EnvVar{
143 {
144 Name: "HOST_IPS",
145 ValueFrom: &v1.EnvVarSource{
146 FieldRef: &v1.ObjectFieldSelector{
147 APIVersion: "v1",
148 FieldPath: "status.hostIPs",
149 },
150 },
151 },
152 }
153
154 expectations := []string{
155 fmt.Sprintf("HOST_IPS=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
156 }
157
158 testDownwardAPI(ctx, f, podName, env, expectations)
159 })
160 })
161 })
162
163 func genPodHostIPs(podName string, hostNetwork bool) *v1.Pod {
164 return &v1.Pod{
165 ObjectMeta: metav1.ObjectMeta{
166 Name: podName,
167 Labels: map[string]string{"test": "dualstack-host-ips"},
168 },
169 Spec: v1.PodSpec{
170 Containers: []v1.Container{
171 {
172 Name: "test-container",
173 Image: imageutils.GetE2EImage(imageutils.Agnhost),
174 },
175 },
176 RestartPolicy: v1.RestartPolicyNever,
177 HostNetwork: hostNetwork,
178 },
179 }
180 }
181
182 func testDownwardAPI(ctx context.Context, f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) {
183 pod := &v1.Pod{
184 ObjectMeta: metav1.ObjectMeta{
185 Name: podName,
186 Labels: map[string]string{"name": podName},
187 },
188 Spec: v1.PodSpec{
189 Containers: []v1.Container{
190 {
191 Name: "dapi-container",
192 Image: imageutils.GetE2EImage(imageutils.BusyBox),
193 Command: []string{"sh", "-c", "env"},
194 Resources: v1.ResourceRequirements{
195 Requests: v1.ResourceList{
196 v1.ResourceCPU: resource.MustParse("250m"),
197 v1.ResourceMemory: resource.MustParse("32Mi"),
198 },
199 Limits: v1.ResourceList{
200 v1.ResourceCPU: resource.MustParse("1250m"),
201 v1.ResourceMemory: resource.MustParse("64Mi"),
202 },
203 },
204 Env: env,
205 },
206 },
207 RestartPolicy: v1.RestartPolicyNever,
208 },
209 }
210
211 e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward api env vars", pod, 0, expectations)
212 }
213
View as plain text