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 }