k8s.io/kubernetes@v1.29.3/test/e2e_node/pod_hostnamefqdn_test.go (about) 1 /* 2 Copyright 2020 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 /* This test check that setHostnameAsFQDN PodSpec field works as 18 * expected. 19 */ 20 21 package e2enode 22 23 import ( 24 "context" 25 "crypto/rand" 26 "fmt" 27 "math/big" 28 "time" 29 30 v1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/fields" 33 "k8s.io/kubernetes/pkg/kubelet/events" 34 admissionapi "k8s.io/pod-security-admission/api" 35 36 "k8s.io/kubernetes/test/e2e/framework" 37 e2eevents "k8s.io/kubernetes/test/e2e/framework/events" 38 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 39 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 40 imageutils "k8s.io/kubernetes/test/utils/image" 41 42 "github.com/onsi/ginkgo/v2" 43 "github.com/onsi/gomega" 44 ) 45 46 func generatePodName(base string) string { 47 id, err := rand.Int(rand.Reader, big.NewInt(214748)) 48 if err != nil { 49 return base 50 } 51 return fmt.Sprintf("%s-%d", base, id) 52 } 53 54 func testPod(podnamebase string) *v1.Pod { 55 podName := generatePodName(podnamebase) 56 pod := &v1.Pod{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Name: podName, 59 Labels: map[string]string{"name": podName}, 60 Annotations: map[string]string{}, 61 }, 62 Spec: v1.PodSpec{ 63 Containers: []v1.Container{ 64 { 65 Name: "test-container", 66 Image: imageutils.GetE2EImage(imageutils.BusyBox), 67 }, 68 }, 69 RestartPolicy: v1.RestartPolicyNever, 70 }, 71 } 72 73 return pod 74 } 75 76 var _ = SIGDescribe("Hostname of Pod", framework.WithNodeConformance(), func() { 77 f := framework.NewDefaultFramework("hostfqdn") 78 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 79 /* 80 Release: v1.19 81 Testname: Create Pod without fully qualified domain name (FQDN) 82 Description: A Pod that does not define the subdomain field in it spec, does not have FQDN. 83 */ 84 ginkgo.It("a pod without subdomain field does not have FQDN", func(ctx context.Context) { 85 pod := testPod("hostfqdn") 86 pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"} 87 output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, pod.ObjectMeta.Name)} 88 // Create Pod 89 e2eoutput.TestContainerOutput(ctx, f, "shortname only", pod, 0, output) 90 }) 91 92 /* 93 Release: v1.19 94 Testname: Create Pod without FQDN, setHostnameAsFQDN field set to true 95 Description: A Pod that does not define the subdomain field in it spec, does not have FQDN. 96 Hence, setHostnameAsFQDN field has no effect. 97 */ 98 ginkgo.It("a pod without FQDN is not affected by SetHostnameAsFQDN field", func(ctx context.Context) { 99 pod := testPod("hostfqdn") 100 // Setting setHostnameAsFQDN field to true should have no effect. 101 setHostnameAsFQDN := true 102 pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN 103 pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"} 104 output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, pod.ObjectMeta.Name)} 105 // Create Pod 106 e2eoutput.TestContainerOutput(ctx, f, "shortname only", pod, 0, output) 107 }) 108 109 /* 110 Release: v1.19 111 Testname: Create Pod with FQDN, setHostnameAsFQDN field not defined. 112 Description: A Pod that defines the subdomain field in it spec has FQDN. 113 hostname command returns shortname (pod name in this case), and hostname -f returns FQDN. 114 */ 115 ginkgo.It("a pod with subdomain field has FQDN, hostname is shortname", func(ctx context.Context) { 116 pod := testPod("hostfqdn") 117 pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"} 118 subdomain := "t" 119 // Set PodSpec subdomain field to generate FQDN for pod 120 pod.Spec.Subdomain = subdomain 121 // Expected Pod FQDN 122 hostFQDN := fmt.Sprintf("%s.%s.%s.svc.%s", pod.ObjectMeta.Name, subdomain, f.Namespace.Name, framework.TestContext.ClusterDNSDomain) 123 output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, hostFQDN)} 124 // Create Pod 125 e2eoutput.TestContainerOutput(ctx, f, "shortname and fqdn", pod, 0, output) 126 }) 127 128 /* 129 Release: v1.19 130 Testname: Create Pod with FQDN, setHostnameAsFQDN field set to true. 131 Description: A Pod that defines the subdomain field in it spec has FQDN. When setHostnameAsFQDN: true, the 132 hostname is set to be the FQDN. In this case, both commands hostname and hostname -f return the FQDN of the Pod. 133 */ 134 ginkgo.It("a pod with subdomain field has FQDN, when setHostnameAsFQDN is set to true, the FQDN is set as hostname", func(ctx context.Context) { 135 pod := testPod("hostfqdn") 136 pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"} 137 subdomain := "t" 138 // Set PodSpec subdomain field to generate FQDN for pod 139 pod.Spec.Subdomain = subdomain 140 // Set PodSpec setHostnameAsFQDN to set FQDN as hostname 141 setHostnameAsFQDN := true 142 pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN 143 // Expected Pod FQDN 144 hostFQDN := fmt.Sprintf("%s.%s.%s.svc.%s", pod.ObjectMeta.Name, subdomain, f.Namespace.Name, framework.TestContext.ClusterDNSDomain) 145 // Fail if FQDN is longer than 64 characters, otherwise the Pod will remain pending until test timeout. 146 // In Linux, 64 characters is the limit of the hostname kernel field, which this test sets to the pod FQDN. 147 gomega.Expect(len(hostFQDN)).Should(gomega.BeNumerically("<=", 64), "The FQDN of the Pod cannot be longer than 64 characters, requested %s which is %d characters long.", hostFQDN, len(hostFQDN)) 148 output := []string{fmt.Sprintf("%s;%s;", hostFQDN, hostFQDN)} 149 // Create Pod 150 e2eoutput.TestContainerOutput(ctx, f, "fqdn and fqdn", pod, 0, output) 151 }) 152 153 /* 154 Release: v1.20 155 Testname: Fail to Create Pod with longer than 64 bytes FQDN when setHostnameAsFQDN field set to true. 156 Description: A Pod that defines the subdomain field in it spec has FQDN. 157 When setHostnameAsFQDN: true, the hostname is set to be 158 the FQDN. Since kernel limit is 64 bytes for hostname field, 159 if pod FQDN is longer than 64 bytes it will generate events 160 regarding FailedCreatePodSandBox. 161 */ 162 163 ginkgo.It("a pod configured to set FQDN as hostname will remain in Pending "+ 164 "state generating FailedCreatePodSandBox events when the FQDN is "+ 165 "longer than 64 bytes", func(ctx context.Context) { 166 // 55 characters for name plus -<int>.t.svc.cluster.local is way more than 64 bytes 167 pod := testPod("hostfqdnveryveryveryverylongforfqdntobemorethan64bytes") 168 pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"} 169 subdomain := "t" 170 // Set PodSpec subdomain field to generate FQDN for pod 171 pod.Spec.Subdomain = subdomain 172 // Set PodSpec setHostnameAsFQDN to set FQDN as hostname 173 setHostnameAsFQDN := true 174 pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN 175 // Create Pod 176 launchedPod := e2epod.NewPodClient(f).Create(ctx, pod) 177 // Ensure we delete pod 178 ginkgo.DeferCleanup(e2epod.NewPodClient(f).DeleteSync, launchedPod.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout) 179 180 // Pod should remain in the pending state generating events with reason FailedCreatePodSandBox 181 // Expected Message Error Event 182 expectedMessage := "Failed to create pod sandbox: failed " + 183 "to construct FQDN from pod hostname and cluster domain, FQDN " 184 framework.Logf("Waiting for Pod to generate FailedCreatePodSandBox event.") 185 // Wait for event with reason FailedCreatePodSandBox 186 expectSandboxFailureEvent(ctx, f, launchedPod, expectedMessage) 187 // Check Pod is in Pending Phase 188 err := checkPodIsPending(ctx, f, launchedPod.ObjectMeta.Name, launchedPod.ObjectMeta.Namespace) 189 framework.ExpectNoError(err) 190 191 }) 192 }) 193 194 // expectSandboxFailureEvent polls for an event with reason "FailedCreatePodSandBox" containing the 195 // expected message string. 196 func expectSandboxFailureEvent(ctx context.Context, f *framework.Framework, pod *v1.Pod, msg string) { 197 eventSelector := fields.Set{ 198 "involvedObject.kind": "Pod", 199 "involvedObject.name": pod.Name, 200 "involvedObject.namespace": f.Namespace.Name, 201 "reason": events.FailedCreatePodSandBox, 202 }.AsSelector().String() 203 framework.ExpectNoError(e2eevents.WaitTimeoutForEvent(ctx, 204 f.ClientSet, f.Namespace.Name, eventSelector, msg, framework.PodEventTimeout)) 205 } 206 207 func checkPodIsPending(ctx context.Context, f *framework.Framework, podName, namespace string) error { 208 c := f.ClientSet 209 // we call this function after we saw event failing to create Pod, hence 210 // pod has already been created and it should be in Pending status. Giving 211 // 30 seconds to fetch the pod to avoid failing for transient issues getting 212 // pods. 213 fetchPodTimeout := 30 * time.Second 214 return e2epod.WaitForPodCondition(ctx, c, namespace, podName, "Failed to Create Pod", fetchPodTimeout, func(pod *v1.Pod) (bool, error) { 215 // We are looking for the pod to be scheduled and in Pending state 216 if pod.Status.Phase == v1.PodPending { 217 for _, cond := range pod.Status.Conditions { 218 if cond.Type == v1.PodScheduled && cond.Status == v1.ConditionTrue { 219 return true, nil 220 } 221 } 222 } 223 // If pod gets to this status, it is likely that FQDN is shorter than 64bytes. 224 if pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { 225 return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v", podName, namespace, pod.Status.Phase) 226 } 227 return false, nil 228 }) 229 }