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  }