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  }