kubelet_etc_hosts.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package common
  14. import (
  15. "strings"
  16. "time"
  17. "github.com/onsi/ginkgo"
  18. v1 "k8s.io/api/core/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/klog"
  21. "k8s.io/kubernetes/test/e2e/framework"
  22. imageutils "k8s.io/kubernetes/test/utils/image"
  23. )
  24. const (
  25. etcHostsPodName = "test-pod"
  26. etcHostsHostNetworkPodName = "test-host-network-pod"
  27. etcHostsPartialContent = "# Kubernetes-managed hosts file."
  28. etcHostsPath = "/etc/hosts"
  29. etcHostsOriginalPath = "/etc/hosts-original"
  30. )
  31. var etcHostsImageName = imageutils.GetE2EImage(imageutils.Agnhost)
  32. type KubeletManagedHostConfig struct {
  33. hostNetworkPod *v1.Pod
  34. pod *v1.Pod
  35. f *framework.Framework
  36. }
  37. var _ = framework.KubeDescribe("KubeletManagedEtcHosts", func() {
  38. f := framework.NewDefaultFramework("e2e-kubelet-etc-hosts")
  39. config := &KubeletManagedHostConfig{
  40. f: f,
  41. }
  42. /*
  43. Release : v1.9
  44. Testname: Kubelet, managed etc hosts
  45. 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.
  46. 1. The Pod with hostNetwork=false MUST have /etc/hosts of containers managed by the Kubelet.
  47. 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.
  48. 3. The Pod with hostNetwork=true , /etc/hosts file MUST not be managed by the Kubelet.
  49. This test is marked LinuxOnly since Windows cannot mount individual files in Containers.
  50. */
  51. framework.ConformanceIt("should test kubelet managed /etc/hosts file [LinuxOnly] [NodeConformance]", func() {
  52. ginkgo.By("Setting up the test")
  53. config.setup()
  54. ginkgo.By("Running the test")
  55. config.verifyEtcHosts()
  56. })
  57. })
  58. func (config *KubeletManagedHostConfig) verifyEtcHosts() {
  59. ginkgo.By("Verifying /etc/hosts of container is kubelet-managed for pod with hostNetwork=false")
  60. assertManagedStatus(config, etcHostsPodName, true, "busybox-1")
  61. assertManagedStatus(config, etcHostsPodName, true, "busybox-2")
  62. ginkgo.By("Verifying /etc/hosts of container is not kubelet-managed since container specifies /etc/hosts mount")
  63. assertManagedStatus(config, etcHostsPodName, false, "busybox-3")
  64. ginkgo.By("Verifying /etc/hosts content of container is not kubelet-managed for pod with hostNetwork=true")
  65. assertManagedStatus(config, etcHostsHostNetworkPodName, false, "busybox-1")
  66. assertManagedStatus(config, etcHostsHostNetworkPodName, false, "busybox-2")
  67. }
  68. func (config *KubeletManagedHostConfig) setup() {
  69. ginkgo.By("Creating hostNetwork=false pod")
  70. config.createPodWithoutHostNetwork()
  71. ginkgo.By("Creating hostNetwork=true pod")
  72. config.createPodWithHostNetwork()
  73. }
  74. func (config *KubeletManagedHostConfig) createPodWithoutHostNetwork() {
  75. podSpec := config.createPodSpec(etcHostsPodName)
  76. config.pod = config.f.PodClient().CreateSync(podSpec)
  77. }
  78. func (config *KubeletManagedHostConfig) createPodWithHostNetwork() {
  79. podSpec := config.createPodSpecWithHostNetwork(etcHostsHostNetworkPodName)
  80. config.hostNetworkPod = config.f.PodClient().CreateSync(podSpec)
  81. }
  82. func assertManagedStatus(
  83. config *KubeletManagedHostConfig, podName string, expectedIsManaged bool, name string) {
  84. // TODO: workaround for https://github.com/kubernetes/kubernetes/issues/34256
  85. //
  86. // Retry until timeout for the contents of /etc/hosts to show
  87. // up. Note: if /etc/hosts is properly mounted, then this will
  88. // succeed immediately.
  89. const retryTimeout = 30 * time.Second
  90. retryCount := 0
  91. etcHostsContent := ""
  92. for startTime := time.Now(); time.Since(startTime) < retryTimeout; {
  93. etcHostsContent = config.getFileContents(podName, name, etcHostsPath)
  94. etcHostsOriginalContent := config.getFileContents(podName, name, etcHostsOriginalPath)
  95. // Make sure there is some content in both files
  96. if len(etcHostsContent) > 0 && len(etcHostsOriginalContent) > 0 {
  97. // if the files match, kubernetes did not touch the file at all
  98. // if the file has the header, kubernetes is not using host network
  99. // and is constructing the file based on Pod IP
  100. isManaged := strings.HasPrefix(etcHostsContent, etcHostsPartialContent) &&
  101. etcHostsContent != etcHostsOriginalContent
  102. if expectedIsManaged == isManaged {
  103. return
  104. }
  105. }
  106. klog.Warningf(
  107. "For pod: %s, name: %s, expected %t, (/etc/hosts was %q), (/etc/hosts-original was %q), retryCount: %d",
  108. podName, name, expectedIsManaged, etcHostsContent, etcHostsOriginalContent, retryCount)
  109. retryCount++
  110. time.Sleep(100 * time.Millisecond)
  111. }
  112. if expectedIsManaged {
  113. framework.Failf(
  114. "/etc/hosts file should be kubelet managed (name: %s, retries: %d). /etc/hosts contains %q",
  115. name, retryCount, etcHostsContent)
  116. } else {
  117. framework.Failf(
  118. "/etc/hosts file should no be kubelet managed (name: %s, retries: %d). /etc/hosts contains %q",
  119. name, retryCount, etcHostsContent)
  120. }
  121. }
  122. func (config *KubeletManagedHostConfig) getFileContents(podName, containerName, path string) string {
  123. return config.f.ExecCommandInContainer(podName, containerName, "cat", path)
  124. }
  125. func (config *KubeletManagedHostConfig) createPodSpec(podName string) *v1.Pod {
  126. hostPathType := new(v1.HostPathType)
  127. *hostPathType = v1.HostPathType(string(v1.HostPathFileOrCreate))
  128. pod := &v1.Pod{
  129. ObjectMeta: metav1.ObjectMeta{
  130. Name: podName,
  131. },
  132. Spec: v1.PodSpec{
  133. Containers: []v1.Container{
  134. {
  135. Name: "busybox-1",
  136. Image: etcHostsImageName,
  137. ImagePullPolicy: v1.PullIfNotPresent,
  138. Command: []string{
  139. "sleep",
  140. "900",
  141. },
  142. VolumeMounts: []v1.VolumeMount{
  143. {
  144. Name: "host-etc-hosts",
  145. MountPath: etcHostsOriginalPath,
  146. },
  147. },
  148. },
  149. {
  150. Name: "busybox-2",
  151. Image: etcHostsImageName,
  152. ImagePullPolicy: v1.PullIfNotPresent,
  153. Command: []string{
  154. "sleep",
  155. "900",
  156. },
  157. VolumeMounts: []v1.VolumeMount{
  158. {
  159. Name: "host-etc-hosts",
  160. MountPath: etcHostsOriginalPath,
  161. },
  162. },
  163. },
  164. {
  165. Name: "busybox-3",
  166. Image: etcHostsImageName,
  167. ImagePullPolicy: v1.PullIfNotPresent,
  168. Command: []string{
  169. "sleep",
  170. "900",
  171. },
  172. VolumeMounts: []v1.VolumeMount{
  173. {
  174. Name: "host-etc-hosts",
  175. MountPath: etcHostsPath,
  176. },
  177. {
  178. Name: "host-etc-hosts",
  179. MountPath: etcHostsOriginalPath,
  180. },
  181. },
  182. },
  183. },
  184. Volumes: []v1.Volume{
  185. {
  186. Name: "host-etc-hosts",
  187. VolumeSource: v1.VolumeSource{
  188. HostPath: &v1.HostPathVolumeSource{
  189. Path: etcHostsPath,
  190. Type: hostPathType,
  191. },
  192. },
  193. },
  194. },
  195. },
  196. }
  197. return pod
  198. }
  199. func (config *KubeletManagedHostConfig) createPodSpecWithHostNetwork(podName string) *v1.Pod {
  200. hostPathType := new(v1.HostPathType)
  201. *hostPathType = v1.HostPathType(string(v1.HostPathFileOrCreate))
  202. pod := &v1.Pod{
  203. ObjectMeta: metav1.ObjectMeta{
  204. Name: podName,
  205. },
  206. Spec: v1.PodSpec{
  207. HostNetwork: true,
  208. SecurityContext: &v1.PodSecurityContext{},
  209. Containers: []v1.Container{
  210. {
  211. Name: "busybox-1",
  212. Image: etcHostsImageName,
  213. ImagePullPolicy: v1.PullIfNotPresent,
  214. Command: []string{
  215. "sleep",
  216. "900",
  217. },
  218. VolumeMounts: []v1.VolumeMount{
  219. {
  220. Name: "host-etc-hosts",
  221. MountPath: etcHostsOriginalPath,
  222. },
  223. },
  224. },
  225. {
  226. Name: "busybox-2",
  227. Image: etcHostsImageName,
  228. ImagePullPolicy: v1.PullIfNotPresent,
  229. Command: []string{
  230. "sleep",
  231. "900",
  232. },
  233. VolumeMounts: []v1.VolumeMount{
  234. {
  235. Name: "host-etc-hosts",
  236. MountPath: etcHostsOriginalPath,
  237. },
  238. },
  239. },
  240. },
  241. Volumes: []v1.Volume{
  242. {
  243. Name: "host-etc-hosts",
  244. VolumeSource: v1.VolumeSource{
  245. HostPath: &v1.HostPathVolumeSource{
  246. Path: etcHostsPath,
  247. Type: hostPathType,
  248. },
  249. },
  250. },
  251. },
  252. },
  253. }
  254. return pod
  255. }