k8s.io/kubernetes@v1.29.3/test/e2e/network/example_cluster_dns.go (about)

     1  /*
     2  Copyright 2015 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 network
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/onsi/ginkgo/v2"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	clientset "k8s.io/client-go/kubernetes"
    33  	api "k8s.io/kubernetes/pkg/apis/core"
    34  	"k8s.io/kubernetes/test/e2e/feature"
    35  	"k8s.io/kubernetes/test/e2e/framework"
    36  	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
    37  	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
    38  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    39  	e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    40  	e2eresource "k8s.io/kubernetes/test/e2e/framework/resource"
    41  	e2eservice "k8s.io/kubernetes/test/e2e/framework/service"
    42  	e2etestfiles "k8s.io/kubernetes/test/e2e/framework/testfiles"
    43  	"k8s.io/kubernetes/test/e2e/network/common"
    44  	admissionapi "k8s.io/pod-security-admission/api"
    45  )
    46  
    47  const (
    48  	dnsReadyTimeout = time.Minute
    49  
    50  	// RespondingTimeout is how long to wait for a service to be responding.
    51  	RespondingTimeout = 2 * time.Minute
    52  )
    53  
    54  const queryDNSPythonTemplate string = `
    55  import socket
    56  try:
    57  	socket.gethostbyname('%s')
    58  	print('ok')
    59  except:
    60  	print('err')`
    61  
    62  var _ = common.SIGDescribe("ClusterDns", feature.Example, func() {
    63  	f := framework.NewDefaultFramework("cluster-dns")
    64  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    65  
    66  	var c clientset.Interface
    67  	ginkgo.BeforeEach(func() {
    68  		c = f.ClientSet
    69  	})
    70  
    71  	read := func(file string) string {
    72  		data, err := e2etestfiles.Read(file)
    73  		if err != nil {
    74  			framework.Fail(err.Error())
    75  		}
    76  		return string(data)
    77  	}
    78  
    79  	ginkgo.It("should create pod that uses dns", func(ctx context.Context) {
    80  		// contrary to the example, this test does not use contexts, for simplicity
    81  		// namespaces are passed directly.
    82  		// Also, for simplicity, we don't use yamls with namespaces, but we
    83  		// create testing namespaces instead.
    84  
    85  		backendName := "dns-backend"
    86  		frontendName := "dns-frontend"
    87  		clusterDnsPath := "test/e2e/testing-manifests/cluster-dns"
    88  		podOutput := "Hello World!"
    89  
    90  		// we need two namespaces anyway, so let's forget about
    91  		// the one created in BeforeEach and create two new ones.
    92  		namespaces := []*v1.Namespace{nil, nil}
    93  		for i := range namespaces {
    94  			var err error
    95  			namespaceName := fmt.Sprintf("dnsexample%d", i)
    96  			namespaces[i], err = f.CreateNamespace(ctx, namespaceName, nil)
    97  			framework.ExpectNoError(err, "failed to create namespace: %s", namespaceName)
    98  		}
    99  
   100  		for _, ns := range namespaces {
   101  			e2ekubectl.RunKubectlOrDieInput(ns.Name, read(filepath.Join(clusterDnsPath, "dns-backend-rc.yaml")), "create", "-f", "-")
   102  		}
   103  
   104  		for _, ns := range namespaces {
   105  			e2ekubectl.RunKubectlOrDieInput(ns.Name, read(filepath.Join(clusterDnsPath, "dns-backend-service.yaml")), "create", "-f", "-")
   106  		}
   107  
   108  		// wait for objects
   109  		for _, ns := range namespaces {
   110  			e2eresource.WaitForControlledPodsRunning(ctx, c, ns.Name, backendName, api.Kind("ReplicationController"))
   111  			framework.ExpectNoError(e2enetwork.WaitForService(ctx, c, ns.Name, backendName, true, framework.Poll, framework.ServiceStartTimeout))
   112  		}
   113  		// it is not enough that pods are running because they may be set to running, but
   114  		// the application itself may have not been initialized. Just query the application.
   115  		for _, ns := range namespaces {
   116  			label := labels.SelectorFromSet(labels.Set(map[string]string{"name": backendName}))
   117  			options := metav1.ListOptions{LabelSelector: label.String()}
   118  			pods, err := c.CoreV1().Pods(ns.Name).List(ctx, options)
   119  			framework.ExpectNoError(err, "failed to list pods in namespace: %s", ns.Name)
   120  			err = e2epod.WaitForPodsResponding(ctx, c, ns.Name, backendName, false, 0, pods)
   121  			framework.ExpectNoError(err, "waiting for all pods to respond")
   122  			framework.Logf("found %d backend pods responding in namespace %s", len(pods.Items), ns.Name)
   123  
   124  			err = waitForServiceResponding(ctx, c, ns.Name, backendName)
   125  			framework.ExpectNoError(err, "waiting for the service to respond")
   126  		}
   127  
   128  		// Now another tricky part:
   129  		// It may happen that the service name is not yet in DNS.
   130  		// So if we start our pod, it will fail. We must make sure
   131  		// the name is already resolvable. So let's try to query DNS from
   132  		// the pod we have, until we find our service name.
   133  		// This complicated code may be removed if the pod itself retried after
   134  		// dns error or timeout.
   135  		// This code is probably unnecessary, but let's stay on the safe side.
   136  		label := labels.SelectorFromSet(labels.Set(map[string]string{"name": backendName}))
   137  		options := metav1.ListOptions{LabelSelector: label.String()}
   138  		pods, err := c.CoreV1().Pods(namespaces[0].Name).List(ctx, options)
   139  
   140  		if err != nil || pods == nil || len(pods.Items) == 0 {
   141  			framework.Failf("no running pods found")
   142  		}
   143  		podName := pods.Items[0].Name
   144  
   145  		queryDNS := fmt.Sprintf(queryDNSPythonTemplate, backendName+"."+namespaces[0].Name)
   146  		_, err = e2eoutput.LookForStringInPodExec(namespaces[0].Name, podName, []string{"python", "-c", queryDNS}, "ok", dnsReadyTimeout)
   147  		framework.ExpectNoError(err, "waiting for output from pod exec")
   148  
   149  		updatedPodYaml := strings.Replace(read(filepath.Join(clusterDnsPath, "dns-frontend-pod.yaml")), fmt.Sprintf("dns-backend.development.svc.%s", framework.TestContext.ClusterDNSDomain), fmt.Sprintf("dns-backend.%s.svc.%s", namespaces[0].Name, framework.TestContext.ClusterDNSDomain), 1)
   150  
   151  		// create a pod in each namespace
   152  		for _, ns := range namespaces {
   153  			e2ekubectl.RunKubectlOrDieInput(ns.Name, updatedPodYaml, "create", "-f", "-")
   154  		}
   155  
   156  		// wait until the pods have been scheduler, i.e. are not Pending anymore. Remember
   157  		// that we cannot wait for the pods to be running because our pods terminate by themselves.
   158  		for _, ns := range namespaces {
   159  			err := e2epod.WaitForPodNotPending(ctx, c, ns.Name, frontendName)
   160  			framework.ExpectNoError(err)
   161  		}
   162  
   163  		// wait for pods to print their result
   164  		for _, ns := range namespaces {
   165  			_, err := e2eoutput.LookForStringInLog(ns.Name, frontendName, frontendName, podOutput, framework.PodStartTimeout)
   166  			framework.ExpectNoError(err, "pod %s failed to print result in logs", frontendName)
   167  		}
   168  	})
   169  })
   170  
   171  // waitForServiceResponding waits for the service to be responding.
   172  func waitForServiceResponding(ctx context.Context, c clientset.Interface, ns, name string) error {
   173  	ginkgo.By(fmt.Sprintf("trying to dial the service %s.%s via the proxy", ns, name))
   174  
   175  	return wait.PollUntilContextTimeout(ctx, framework.Poll, RespondingTimeout, true, func(ctx context.Context) (done bool, err error) {
   176  		proxyRequest, errProxy := e2eservice.GetServicesProxyRequest(c, c.CoreV1().RESTClient().Get())
   177  		if errProxy != nil {
   178  			framework.Logf("Failed to get services proxy request: %v:", errProxy)
   179  			return false, nil
   180  		}
   181  
   182  		ctx, cancel := context.WithTimeout(ctx, framework.SingleCallTimeout)
   183  		defer cancel()
   184  
   185  		body, err := proxyRequest.Namespace(ns).
   186  			Name(name).
   187  			Do(ctx).
   188  			Raw()
   189  		if err != nil {
   190  			if ctx.Err() != nil {
   191  				framework.Failf("Failed to GET from service %s: %v", name, err)
   192  				return true, err
   193  			}
   194  			framework.Logf("Failed to GET from service %s: %v:", name, err)
   195  			return false, nil
   196  		}
   197  		got := string(body)
   198  		if len(got) == 0 {
   199  			framework.Logf("Service %s: expected non-empty response", name)
   200  			return false, err // stop polling
   201  		}
   202  		framework.Logf("Service %s: found nonempty answer: %s", name, got)
   203  		return true, nil
   204  	})
   205  }