k8s.io/kubernetes@v1.29.3/test/e2e/common/node/kubelet_etc_hosts.go (about)

     1  /*
     2  Copyright 2016 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 node
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/onsi/ginkgo/v2"
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/klog/v2"
    28  	"k8s.io/kubernetes/test/e2e/framework"
    29  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    30  	admissionapi "k8s.io/pod-security-admission/api"
    31  )
    32  
    33  const (
    34  	etcHostsPodName            = "test-pod"
    35  	etcHostsHostNetworkPodName = "test-host-network-pod"
    36  	etcHostsPartialContent     = "# Kubernetes-managed hosts file."
    37  	etcHostsPath               = "/etc/hosts"
    38  	etcHostsOriginalPath       = "/etc/hosts-original"
    39  )
    40  
    41  // KubeletManagedHostConfig defines the types for running managed etc hosts test cases
    42  type KubeletManagedHostConfig struct {
    43  	hostNetworkPod *v1.Pod
    44  	pod            *v1.Pod
    45  	f              *framework.Framework
    46  }
    47  
    48  var _ = SIGDescribe("KubeletManagedEtcHosts", func() {
    49  	f := framework.NewDefaultFramework("e2e-kubelet-etc-hosts")
    50  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    51  	config := &KubeletManagedHostConfig{
    52  		f: f,
    53  	}
    54  
    55  	/*
    56  		Release: v1.9
    57  		Testname: Kubelet, managed etc hosts
    58  		Description: Create a Pod with containers with hostNetwork set to false, one of the containers mounts the /etc/hosts file form the host. Create a second Pod with hostNetwork set to true.
    59  			1. The Pod with hostNetwork=false MUST have /etc/hosts of containers managed by the Kubelet.
    60  			2. The Pod with hostNetwork=false but the container mounts /etc/hosts file from the host. The /etc/hosts file MUST not be managed by the Kubelet.
    61  			3. The Pod with hostNetwork=true , /etc/hosts file MUST not be managed by the Kubelet.
    62  		This test is marked LinuxOnly since Windows cannot mount individual files in Containers.
    63  	*/
    64  	framework.ConformanceIt("should test kubelet managed /etc/hosts file [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
    65  		ginkgo.By("Setting up the test")
    66  		config.setup(ctx)
    67  
    68  		ginkgo.By("Running the test")
    69  		config.verifyEtcHosts()
    70  	})
    71  })
    72  
    73  func (config *KubeletManagedHostConfig) verifyEtcHosts() {
    74  	ginkgo.By("Verifying /etc/hosts of container is kubelet-managed for pod with hostNetwork=false")
    75  	assertManagedStatus(config, etcHostsPodName, true, "busybox-1")
    76  	assertManagedStatus(config, etcHostsPodName, true, "busybox-2")
    77  
    78  	ginkgo.By("Verifying /etc/hosts of container is not kubelet-managed since container specifies /etc/hosts mount")
    79  	assertManagedStatus(config, etcHostsPodName, false, "busybox-3")
    80  
    81  	ginkgo.By("Verifying /etc/hosts content of container is not kubelet-managed for pod with hostNetwork=true")
    82  	assertManagedStatus(config, etcHostsHostNetworkPodName, false, "busybox-1")
    83  	assertManagedStatus(config, etcHostsHostNetworkPodName, false, "busybox-2")
    84  }
    85  
    86  func (config *KubeletManagedHostConfig) setup(ctx context.Context) {
    87  	ginkgo.By("Creating hostNetwork=false pod")
    88  	config.createPodWithoutHostNetwork(ctx)
    89  
    90  	ginkgo.By("Creating hostNetwork=true pod")
    91  	config.createPodWithHostNetwork(ctx)
    92  }
    93  
    94  func (config *KubeletManagedHostConfig) createPodWithoutHostNetwork(ctx context.Context) {
    95  	podSpec := config.createPodSpec(etcHostsPodName)
    96  	config.pod = e2epod.NewPodClient(config.f).CreateSync(ctx, podSpec)
    97  }
    98  
    99  func (config *KubeletManagedHostConfig) createPodWithHostNetwork(ctx context.Context) {
   100  	podSpec := config.createPodSpecWithHostNetwork(etcHostsHostNetworkPodName)
   101  	config.hostNetworkPod = e2epod.NewPodClient(config.f).CreateSync(ctx, podSpec)
   102  }
   103  
   104  func assertManagedStatus(
   105  	config *KubeletManagedHostConfig, podName string, expectedIsManaged bool, name string) {
   106  	// TODO: workaround for https://github.com/kubernetes/kubernetes/issues/34256
   107  	//
   108  	// Retry until timeout for the contents of /etc/hosts to show
   109  	// up. Note: if /etc/hosts is properly mounted, then this will
   110  	// succeed immediately.
   111  	const retryTimeout = 30 * time.Second
   112  
   113  	retryCount := 0
   114  	etcHostsContent := ""
   115  
   116  	for startTime := time.Now(); time.Since(startTime) < retryTimeout; {
   117  		etcHostsContent = config.getFileContents(podName, name, etcHostsPath)
   118  		etcHostsOriginalContent := config.getFileContents(podName, name, etcHostsOriginalPath)
   119  
   120  		// Make sure there is some content in both files
   121  		if len(etcHostsContent) > 0 && len(etcHostsOriginalContent) > 0 {
   122  			// if the files match, kubernetes did not touch the file at all
   123  			// if the file has the header, kubernetes is not using host network
   124  			// and is constructing the file based on Pod IP
   125  			isManaged := strings.HasPrefix(etcHostsContent, etcHostsPartialContent) &&
   126  				etcHostsContent != etcHostsOriginalContent
   127  			if expectedIsManaged == isManaged {
   128  				return
   129  			}
   130  		}
   131  
   132  		klog.Warningf(
   133  			"For pod: %s, name: %s, expected %t, (/etc/hosts was %q), (/etc/hosts-original was %q), retryCount: %d",
   134  			podName, name, expectedIsManaged, etcHostsContent, etcHostsOriginalContent, retryCount)
   135  
   136  		retryCount++
   137  		time.Sleep(100 * time.Millisecond)
   138  	}
   139  
   140  	if expectedIsManaged {
   141  		framework.Failf(
   142  			"/etc/hosts file should be kubelet managed (name: %s, retries: %d). /etc/hosts contains %q",
   143  			name, retryCount, etcHostsContent)
   144  	} else {
   145  		framework.Failf(
   146  			"/etc/hosts file should no be kubelet managed (name: %s, retries: %d). /etc/hosts contains %q",
   147  			name, retryCount, etcHostsContent)
   148  	}
   149  }
   150  
   151  func (config *KubeletManagedHostConfig) getFileContents(podName, containerName, path string) string {
   152  	return e2epod.ExecCommandInContainer(config.f, podName, containerName, "cat", path)
   153  }
   154  
   155  func (config *KubeletManagedHostConfig) createPodSpec(podName string) *v1.Pod {
   156  	hostPathType := new(v1.HostPathType)
   157  	*hostPathType = v1.HostPathType(string(v1.HostPathFileOrCreate))
   158  	mounts := []v1.VolumeMount{
   159  		{
   160  			Name:      "host-etc-hosts",
   161  			MountPath: etcHostsOriginalPath,
   162  		},
   163  	}
   164  	multipleMounts := []v1.VolumeMount{
   165  		mounts[0],
   166  		{
   167  			Name:      "host-etc-hosts",
   168  			MountPath: etcHostsPath,
   169  		},
   170  	}
   171  	pod := &v1.Pod{
   172  		ObjectMeta: metav1.ObjectMeta{
   173  			Name: podName,
   174  		},
   175  		Spec: v1.PodSpec{
   176  			Containers: []v1.Container{
   177  				e2epod.NewAgnhostContainer("busybox-1", mounts, nil),
   178  				e2epod.NewAgnhostContainer("busybox-2", mounts, nil),
   179  				e2epod.NewAgnhostContainer("busybox-3", multipleMounts, nil),
   180  			},
   181  			Volumes: []v1.Volume{
   182  				{
   183  					Name: "host-etc-hosts",
   184  					VolumeSource: v1.VolumeSource{
   185  						HostPath: &v1.HostPathVolumeSource{
   186  							Path: etcHostsPath,
   187  							Type: hostPathType,
   188  						},
   189  					},
   190  				},
   191  			},
   192  		},
   193  	}
   194  
   195  	return pod
   196  }
   197  
   198  func (config *KubeletManagedHostConfig) createPodSpecWithHostNetwork(podName string) *v1.Pod {
   199  	hostPathType := new(v1.HostPathType)
   200  	*hostPathType = v1.HostPathType(string(v1.HostPathFileOrCreate))
   201  	mounts := []v1.VolumeMount{
   202  		{
   203  			Name:      "host-etc-hosts",
   204  			MountPath: etcHostsOriginalPath,
   205  		},
   206  	}
   207  	pod := &v1.Pod{
   208  		ObjectMeta: metav1.ObjectMeta{
   209  			Name: podName,
   210  		},
   211  		Spec: v1.PodSpec{
   212  			HostNetwork:     true,
   213  			SecurityContext: &v1.PodSecurityContext{},
   214  			Containers: []v1.Container{
   215  				e2epod.NewAgnhostContainer("busybox-1", mounts, nil),
   216  				e2epod.NewAgnhostContainer("busybox-2", mounts, nil),
   217  			},
   218  			Volumes: []v1.Volume{
   219  				{
   220  					Name: "host-etc-hosts",
   221  					VolumeSource: v1.VolumeSource{
   222  						HostPath: &v1.HostPathVolumeSource{
   223  							Path: etcHostsPath,
   224  							Type: hostPathType,
   225  						},
   226  					},
   227  				},
   228  			},
   229  		},
   230  	}
   231  	return pod
   232  }