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

     1  /*
     2  Copyright 2017 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  	"regexp"
    23  	"strings"
    24  	"time"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  
    30  	"k8s.io/apimachinery/pkg/fields"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	"k8s.io/apimachinery/pkg/util/intstr"
    33  	"k8s.io/apimachinery/pkg/util/uuid"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	clientset "k8s.io/client-go/kubernetes"
    36  	"k8s.io/kubernetes/test/e2e/framework"
    37  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    38  	imageutils "k8s.io/kubernetes/test/utils/image"
    39  	dnsclient "k8s.io/kubernetes/third_party/forked/golang/net"
    40  	admissionapi "k8s.io/pod-security-admission/api"
    41  
    42  	"github.com/onsi/ginkgo/v2"
    43  	"github.com/onsi/gomega"
    44  )
    45  
    46  // Windows output can contain additional \r
    47  var newLineRegexp = regexp.MustCompile("\r?\n")
    48  
    49  type dnsTestCommon struct {
    50  	f    *framework.Framework
    51  	c    clientset.Interface
    52  	ns   string
    53  	name string
    54  
    55  	dnsPod       *v1.Pod
    56  	utilPod      *v1.Pod
    57  	utilService  *v1.Service
    58  	dnsServerPod *v1.Pod
    59  
    60  	cm *v1.ConfigMap
    61  }
    62  
    63  func newDNSTestCommon() dnsTestCommon {
    64  	framework := framework.NewDefaultFramework("dns-config-map")
    65  	framework.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    66  	return dnsTestCommon{
    67  		f:  framework,
    68  		ns: "kube-system",
    69  	}
    70  }
    71  
    72  func (t *dnsTestCommon) init(ctx context.Context) {
    73  	ginkgo.By("Finding a DNS pod")
    74  	label := labels.SelectorFromSet(labels.Set(map[string]string{"k8s-app": "kube-dns"}))
    75  	options := metav1.ListOptions{LabelSelector: label.String()}
    76  
    77  	namespace := "kube-system"
    78  	pods, err := t.f.ClientSet.CoreV1().Pods(namespace).List(ctx, options)
    79  	framework.ExpectNoError(err, "failed to list pods in namespace: %s", namespace)
    80  	gomega.Expect(pods.Items).ToNot(gomega.BeEmpty())
    81  
    82  	t.dnsPod = &pods.Items[0]
    83  	framework.Logf("Using DNS pod: %v", t.dnsPod.Name)
    84  
    85  	if strings.Contains(t.dnsPod.Name, "coredns") {
    86  		t.name = "coredns"
    87  	} else {
    88  		t.name = "kube-dns"
    89  	}
    90  }
    91  
    92  func (t *dnsTestCommon) checkDNSRecordFrom(name string, predicate func([]string) bool, target string, timeout time.Duration) {
    93  	var actual []string
    94  
    95  	err := wait.PollImmediate(
    96  		time.Duration(1)*time.Second,
    97  		timeout,
    98  		func() (bool, error) {
    99  			actual = t.runDig(name, target)
   100  			if predicate(actual) {
   101  				return true, nil
   102  			}
   103  			return false, nil
   104  		})
   105  
   106  	if err != nil {
   107  		framework.Failf("dig result did not match: %#v after %v",
   108  			actual, timeout)
   109  	}
   110  }
   111  
   112  // runDig queries for `dnsName`. Returns a list of responses.
   113  func (t *dnsTestCommon) runDig(dnsName, target string) []string {
   114  	cmd := []string{"dig", "+short"}
   115  	switch target {
   116  	case "coredns":
   117  		cmd = append(cmd, "@"+t.dnsPod.Status.PodIP)
   118  	case "kube-dns":
   119  		cmd = append(cmd, "@"+t.dnsPod.Status.PodIP, "-p", "10053")
   120  	case "ptr-record":
   121  		cmd = append(cmd, "-x")
   122  	case "cluster-dns":
   123  	case "cluster-dns-ipv6":
   124  		cmd = append(cmd, "AAAA")
   125  	default:
   126  		panic(fmt.Errorf("invalid target: " + target))
   127  	}
   128  	cmd = append(cmd, dnsName)
   129  
   130  	stdout, stderr, err := e2epod.ExecWithOptions(t.f, e2epod.ExecOptions{
   131  		Command:       cmd,
   132  		Namespace:     t.f.Namespace.Name,
   133  		PodName:       t.utilPod.Name,
   134  		ContainerName: t.utilPod.Spec.Containers[0].Name,
   135  		CaptureStdout: true,
   136  		CaptureStderr: true,
   137  	})
   138  
   139  	framework.Logf("Running dig: %v, stdout: %q, stderr: %q, err: %v",
   140  		cmd, stdout, stderr, err)
   141  
   142  	if stdout == "" {
   143  		return []string{}
   144  	}
   145  	return newLineRegexp.Split(stdout, -1)
   146  }
   147  
   148  func (t *dnsTestCommon) setConfigMap(ctx context.Context, cm *v1.ConfigMap) {
   149  	if t.cm != nil {
   150  		t.cm = cm
   151  	}
   152  
   153  	cm.ObjectMeta.Namespace = t.ns
   154  	cm.ObjectMeta.Name = t.name
   155  
   156  	options := metav1.ListOptions{
   157  		FieldSelector: fields.Set{
   158  			"metadata.namespace": t.ns,
   159  			"metadata.name":      t.name,
   160  		}.AsSelector().String(),
   161  	}
   162  	cmList, err := t.c.CoreV1().ConfigMaps(t.ns).List(ctx, options)
   163  	framework.ExpectNoError(err, "failed to list ConfigMaps in namespace: %s", t.ns)
   164  
   165  	if len(cmList.Items) == 0 {
   166  		ginkgo.By(fmt.Sprintf("Creating the ConfigMap (%s:%s) %+v", t.ns, t.name, *cm))
   167  		_, err := t.c.CoreV1().ConfigMaps(t.ns).Create(ctx, cm, metav1.CreateOptions{})
   168  		framework.ExpectNoError(err, "failed to create ConfigMap (%s:%s) %+v", t.ns, t.name, *cm)
   169  	} else {
   170  		ginkgo.By(fmt.Sprintf("Updating the ConfigMap (%s:%s) to %+v", t.ns, t.name, *cm))
   171  		_, err := t.c.CoreV1().ConfigMaps(t.ns).Update(ctx, cm, metav1.UpdateOptions{})
   172  		framework.ExpectNoError(err, "failed to update ConfigMap (%s:%s) to %+v", t.ns, t.name, *cm)
   173  	}
   174  }
   175  
   176  func (t *dnsTestCommon) fetchDNSConfigMapData(ctx context.Context) map[string]string {
   177  	if t.name == "coredns" {
   178  		pcm, err := t.c.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, t.name, metav1.GetOptions{})
   179  		framework.ExpectNoError(err, "failed to get DNS ConfigMap: %s", t.name)
   180  		return pcm.Data
   181  	}
   182  	return nil
   183  }
   184  
   185  func (t *dnsTestCommon) restoreDNSConfigMap(ctx context.Context, configMapData map[string]string) {
   186  	if t.name == "coredns" {
   187  		t.setConfigMap(ctx, &v1.ConfigMap{Data: configMapData})
   188  		t.deleteCoreDNSPods(ctx)
   189  	} else {
   190  		err := t.c.CoreV1().ConfigMaps(t.ns).Delete(ctx, t.name, metav1.DeleteOptions{})
   191  		if err != nil && !apierrors.IsNotFound(err) {
   192  			framework.Failf("Unexpected error deleting configmap %s/%s", t.ns, t.name)
   193  		}
   194  	}
   195  }
   196  
   197  func (t *dnsTestCommon) createUtilPodLabel(ctx context.Context, baseName string) {
   198  	// Actual port # doesn't matter, just needs to exist.
   199  	const servicePort = 10101
   200  	podName := fmt.Sprintf("%s-%s", baseName, string(uuid.NewUUID()))
   201  	ports := []v1.ContainerPort{{ContainerPort: servicePort, Protocol: v1.ProtocolTCP}}
   202  	t.utilPod = e2epod.NewAgnhostPod(t.f.Namespace.Name, podName, nil, nil, ports)
   203  
   204  	var err error
   205  	t.utilPod, err = t.c.CoreV1().Pods(t.f.Namespace.Name).Create(ctx, t.utilPod, metav1.CreateOptions{})
   206  	framework.ExpectNoError(err, "failed to create pod: %v", t.utilPod)
   207  	framework.Logf("Created pod %v", t.utilPod)
   208  	err = e2epod.WaitForPodNameRunningInNamespace(ctx, t.f.ClientSet, t.utilPod.Name, t.f.Namespace.Name)
   209  	framework.ExpectNoError(err, "pod failed to start running: %v", t.utilPod)
   210  
   211  	t.utilService = &v1.Service{
   212  		TypeMeta: metav1.TypeMeta{
   213  			Kind: "Service",
   214  		},
   215  		ObjectMeta: metav1.ObjectMeta{
   216  			Namespace: t.f.Namespace.Name,
   217  			Name:      baseName,
   218  		},
   219  		Spec: v1.ServiceSpec{
   220  			Selector: map[string]string{"app": baseName},
   221  			Ports: []v1.ServicePort{
   222  				{
   223  					Protocol:   v1.ProtocolTCP,
   224  					Port:       servicePort,
   225  					TargetPort: intstr.FromInt32(servicePort),
   226  				},
   227  			},
   228  		},
   229  	}
   230  
   231  	t.utilService, err = t.c.CoreV1().Services(t.f.Namespace.Name).Create(ctx, t.utilService, metav1.CreateOptions{})
   232  	framework.ExpectNoError(err, "failed to create service: %s/%s", t.f.Namespace.Name, t.utilService.ObjectMeta.Name)
   233  	framework.Logf("Created service %v", t.utilService)
   234  }
   235  
   236  func (t *dnsTestCommon) deleteUtilPod(ctx context.Context) {
   237  	podClient := t.c.CoreV1().Pods(t.f.Namespace.Name)
   238  	if err := podClient.Delete(ctx, t.utilPod.Name, *metav1.NewDeleteOptions(0)); err != nil {
   239  		framework.Logf("Delete of pod %v/%v failed: %v",
   240  			t.utilPod.Namespace, t.utilPod.Name, err)
   241  	}
   242  }
   243  
   244  // deleteCoreDNSPods manually deletes the CoreDNS pods to apply the changes to the ConfigMap.
   245  func (t *dnsTestCommon) deleteCoreDNSPods(ctx context.Context) {
   246  
   247  	label := labels.SelectorFromSet(labels.Set(map[string]string{"k8s-app": "kube-dns"}))
   248  	options := metav1.ListOptions{LabelSelector: label.String()}
   249  
   250  	pods, err := t.f.ClientSet.CoreV1().Pods("kube-system").List(ctx, options)
   251  	framework.ExpectNoError(err, "failed to list pods of kube-system with label %q", label.String())
   252  	podClient := t.c.CoreV1().Pods(metav1.NamespaceSystem)
   253  
   254  	for _, pod := range pods.Items {
   255  		err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0))
   256  		framework.ExpectNoError(err, "failed to delete pod: %s", pod.Name)
   257  	}
   258  }
   259  
   260  func generateCoreDNSServerPod(corednsConfig *v1.ConfigMap) *v1.Pod {
   261  	podName := fmt.Sprintf("e2e-configmap-dns-server-%s", string(uuid.NewUUID()))
   262  	volumes := []v1.Volume{
   263  		{
   264  			Name: "coredns-config",
   265  			VolumeSource: v1.VolumeSource{
   266  				ConfigMap: &v1.ConfigMapVolumeSource{
   267  					LocalObjectReference: v1.LocalObjectReference{
   268  						Name: corednsConfig.Name,
   269  					},
   270  				},
   271  			},
   272  		},
   273  	}
   274  	mounts := []v1.VolumeMount{
   275  		{
   276  			Name:      "coredns-config",
   277  			MountPath: "/etc/coredns",
   278  			ReadOnly:  true,
   279  		},
   280  	}
   281  
   282  	pod := e2epod.NewAgnhostPod("", podName, volumes, mounts, nil, "-conf", "/etc/coredns/Corefile")
   283  	pod.Spec.Containers[0].Command = []string{"/coredns"}
   284  	pod.Spec.DNSPolicy = "Default"
   285  	return pod
   286  }
   287  
   288  func generateCoreDNSConfigmap(namespaceName string, aRecords map[string]string) *v1.ConfigMap {
   289  	entries := ""
   290  	for name, ip := range aRecords {
   291  		entries += fmt.Sprintf("\n\t\t%v %v", ip, name)
   292  	}
   293  
   294  	corefileData := fmt.Sprintf(`. {
   295  	hosts {%s
   296  	}
   297  	log
   298  }`, entries)
   299  
   300  	return &v1.ConfigMap{
   301  		ObjectMeta: metav1.ObjectMeta{
   302  			Namespace:    namespaceName,
   303  			GenerateName: "e2e-coredns-configmap-",
   304  		},
   305  		Data: map[string]string{
   306  			"Corefile": corefileData,
   307  		},
   308  	}
   309  }
   310  
   311  func (t *dnsTestCommon) createDNSPodFromObj(ctx context.Context, pod *v1.Pod) {
   312  	t.dnsServerPod = pod
   313  
   314  	var err error
   315  	t.dnsServerPod, err = t.c.CoreV1().Pods(t.f.Namespace.Name).Create(ctx, t.dnsServerPod, metav1.CreateOptions{})
   316  	framework.ExpectNoError(err, "failed to create pod: %v", t.dnsServerPod)
   317  	framework.Logf("Created pod %v", t.dnsServerPod)
   318  	err = e2epod.WaitForPodNameRunningInNamespace(ctx, t.f.ClientSet, t.dnsServerPod.Name, t.f.Namespace.Name)
   319  	framework.ExpectNoError(err, "pod failed to start running: %v", t.dnsServerPod)
   320  
   321  	t.dnsServerPod, err = t.c.CoreV1().Pods(t.f.Namespace.Name).Get(ctx, t.dnsServerPod.Name, metav1.GetOptions{})
   322  	framework.ExpectNoError(err, "failed to get pod: %s", t.dnsServerPod.Name)
   323  }
   324  
   325  func (t *dnsTestCommon) createDNSServer(ctx context.Context, namespace string, aRecords map[string]string) {
   326  	corednsConfig := generateCoreDNSConfigmap(namespace, aRecords)
   327  	corednsConfig, err := t.c.CoreV1().ConfigMaps(namespace).Create(ctx, corednsConfig, metav1.CreateOptions{})
   328  	if err != nil {
   329  		framework.Failf("unable to create test configMap %s: %v", corednsConfig.Name, err)
   330  	}
   331  
   332  	t.createDNSPodFromObj(ctx, generateCoreDNSServerPod(corednsConfig))
   333  }
   334  
   335  func (t *dnsTestCommon) createDNSServerWithPtrRecord(ctx context.Context, namespace string, isIPv6 bool) {
   336  	// NOTE: PTR records are generated automatically by CoreDNS. So, if we're creating A records, we're
   337  	// going to also have PTR records. See: https://coredns.io/plugins/hosts/
   338  	var aRecords map[string]string
   339  	if isIPv6 {
   340  		aRecords = map[string]string{"my.test": "2001:db8::29"}
   341  	} else {
   342  		aRecords = map[string]string{"my.test": "192.0.2.123"}
   343  	}
   344  	t.createDNSServer(ctx, namespace, aRecords)
   345  }
   346  
   347  func (t *dnsTestCommon) deleteDNSServerPod(ctx context.Context) {
   348  	podClient := t.c.CoreV1().Pods(t.f.Namespace.Name)
   349  	if err := podClient.Delete(ctx, t.dnsServerPod.Name, *metav1.NewDeleteOptions(0)); err != nil {
   350  		framework.Logf("Delete of pod %v/%v failed: %v",
   351  			t.utilPod.Namespace, t.dnsServerPod.Name, err)
   352  	}
   353  }
   354  
   355  func createDNSPod(namespace, wheezyProbeCmd, jessieProbeCmd, podHostName, serviceName string) *v1.Pod {
   356  	podName := "dns-test-" + string(uuid.NewUUID())
   357  	volumes := []v1.Volume{
   358  		{
   359  			Name: "results",
   360  			VolumeSource: v1.VolumeSource{
   361  				EmptyDir: &v1.EmptyDirVolumeSource{},
   362  			},
   363  		},
   364  	}
   365  	mounts := []v1.VolumeMount{
   366  		{
   367  			Name:      "results",
   368  			MountPath: "/results",
   369  		},
   370  	}
   371  
   372  	// TODO: Consider scraping logs instead of running a webserver.
   373  	dnsPod := e2epod.NewAgnhostPod(namespace, podName, volumes, mounts, nil, "test-webserver")
   374  	dnsPod.Spec.Containers[0].Name = "webserver"
   375  
   376  	querier := e2epod.NewAgnhostContainer("querier", mounts, nil, wheezyProbeCmd)
   377  	querier.Command = []string{"sh", "-c"}
   378  
   379  	jessieQuerier := v1.Container{
   380  		Name:         "jessie-querier",
   381  		Image:        imageutils.GetE2EImage(imageutils.JessieDnsutils),
   382  		Command:      []string{"sh", "-c", jessieProbeCmd},
   383  		VolumeMounts: mounts,
   384  	}
   385  
   386  	dnsPod.Spec.Containers = append(dnsPod.Spec.Containers, querier, jessieQuerier)
   387  	dnsPod.Spec.Hostname = podHostName
   388  	dnsPod.Spec.Subdomain = serviceName
   389  
   390  	return dnsPod
   391  }
   392  
   393  func createProbeCommand(namesToResolve []string, hostEntries []string, ptrLookupIP string, fileNamePrefix, namespace, dnsDomain string, isIPv6 bool) (string, []string) {
   394  	fileNames := make([]string, 0, len(namesToResolve)*2)
   395  	probeCmd := "for i in `seq 1 600`; do "
   396  	dnsRecord := "A"
   397  	if isIPv6 {
   398  		dnsRecord = "AAAA"
   399  	}
   400  	for _, name := range namesToResolve {
   401  		// Resolve by TCP and UDP DNS.  Use $$(...) because $(...) is
   402  		// expanded by kubernetes (though this won't expand so should
   403  		// remain a literal, safe > sorry).
   404  		lookup := fmt.Sprintf("%s %s", name, dnsRecord)
   405  		if strings.HasPrefix(name, "_") {
   406  			lookup = fmt.Sprintf("%s SRV", name)
   407  		}
   408  		fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, name)
   409  		fileNames = append(fileNames, fileName)
   410  		probeCmd += fmt.Sprintf(`check="$$(dig +notcp +noall +answer +search %s)" && test -n "$$check" && echo OK > /results/%s;`, lookup, fileName)
   411  		fileName = fmt.Sprintf("%s_tcp@%s", fileNamePrefix, name)
   412  		fileNames = append(fileNames, fileName)
   413  		probeCmd += fmt.Sprintf(`check="$$(dig +tcp +noall +answer +search %s)" && test -n "$$check" && echo OK > /results/%s;`, lookup, fileName)
   414  	}
   415  
   416  	hostEntryCmd := `test -n "$$(getent hosts %s)" && echo OK > /results/%s;`
   417  	if framework.NodeOSDistroIs("windows") {
   418  		// We don't have getent on Windows, but we can still check the hosts file.
   419  		hostEntryCmd = `test -n "$$(grep '%s' C:/Windows/System32/drivers/etc/hosts)" && echo OK > /results/%s;`
   420  	}
   421  	for _, name := range hostEntries {
   422  		fileName := fmt.Sprintf("%s_hosts@%s", fileNamePrefix, name)
   423  		fileNames = append(fileNames, fileName)
   424  		probeCmd += fmt.Sprintf(hostEntryCmd, name, fileName)
   425  	}
   426  
   427  	if len(ptrLookupIP) > 0 {
   428  		ptrLookup, err := dnsclient.Reverseaddr(ptrLookupIP)
   429  		if err != nil {
   430  			framework.Failf("Unable to obtain reverse IP address record from IP %s: %v", ptrLookupIP, err)
   431  		}
   432  		ptrRecByUDPFileName := fmt.Sprintf("%s_udp@PTR", ptrLookupIP)
   433  		ptrRecByTCPFileName := fmt.Sprintf("%s_tcp@PTR", ptrLookupIP)
   434  		probeCmd += fmt.Sprintf(`check="$$(dig +notcp +noall +answer +search %s PTR)" && test -n "$$check" && echo OK > /results/%s;`, ptrLookup, ptrRecByUDPFileName)
   435  		probeCmd += fmt.Sprintf(`check="$$(dig +tcp +noall +answer +search %s PTR)" && test -n "$$check" && echo OK > /results/%s;`, ptrLookup, ptrRecByTCPFileName)
   436  		fileNames = append(fileNames, ptrRecByUDPFileName)
   437  		fileNames = append(fileNames, ptrRecByTCPFileName)
   438  	}
   439  
   440  	probeCmd += "sleep 1; done"
   441  	return probeCmd, fileNames
   442  }
   443  
   444  // createTargetedProbeCommand returns a command line that performs a DNS lookup for a specific record type
   445  func createTargetedProbeCommand(nameToResolve string, lookup string, fileNamePrefix string) (string, string) {
   446  	fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, nameToResolve)
   447  	nameLookup := fmt.Sprintf("%s %s", nameToResolve, lookup)
   448  	probeCmd := fmt.Sprintf("for i in `seq 1 30`; do dig +short %s > /results/%s; sleep 1; done", nameLookup, fileName)
   449  	return probeCmd, fileName
   450  }
   451  
   452  func assertFilesExist(ctx context.Context, fileNames []string, fileDir string, pod *v1.Pod, client clientset.Interface) {
   453  	assertFilesContain(ctx, fileNames, fileDir, pod, client, false, "")
   454  }
   455  
   456  func assertFilesContain(ctx context.Context, fileNames []string, fileDir string, pod *v1.Pod, client clientset.Interface, check bool, expected string) {
   457  	var failed []string
   458  
   459  	framework.ExpectNoError(wait.PollUntilContextTimeout(ctx, time.Second*5, time.Second*600, true, func(ctx context.Context) (bool, error) {
   460  		failed = []string{}
   461  
   462  		ctx, cancel := context.WithTimeout(ctx, framework.SingleCallTimeout)
   463  		defer cancel()
   464  
   465  		for _, fileName := range fileNames {
   466  			contents, err := client.CoreV1().RESTClient().Get().
   467  				Namespace(pod.Namespace).
   468  				Resource("pods").
   469  				SubResource("proxy").
   470  				Name(pod.Name).
   471  				Suffix(fileDir, fileName).
   472  				Do(ctx).Raw()
   473  
   474  			if err != nil {
   475  				if ctx.Err() != nil {
   476  					framework.Failf("Unable to read %s from pod %s/%s: %v", fileName, pod.Namespace, pod.Name, err)
   477  				} else {
   478  					framework.Logf("Unable to read %s from pod %s/%s: %v", fileName, pod.Namespace, pod.Name, err)
   479  				}
   480  				failed = append(failed, fileName)
   481  			} else if check && strings.TrimSpace(string(contents)) != expected {
   482  				framework.Logf("File %s from pod  %s/%s contains '%s' instead of '%s'", fileName, pod.Namespace, pod.Name, string(contents), expected)
   483  				failed = append(failed, fileName)
   484  			}
   485  		}
   486  		if len(failed) == 0 {
   487  			return true, nil
   488  		}
   489  		framework.Logf("Lookups using %s/%s failed for: %v\n", pod.Namespace, pod.Name, failed)
   490  
   491  		// grab logs from all the containers
   492  		for _, container := range pod.Spec.Containers {
   493  			logs, err := e2epod.GetPodLogs(ctx, client, pod.Namespace, pod.Name, container.Name)
   494  			framework.ExpectNoError(err)
   495  			framework.Logf("Pod client logs for %s: %s", container.Name, logs)
   496  		}
   497  
   498  		return false, nil
   499  	}))
   500  	gomega.Expect(failed).To(gomega.BeEmpty())
   501  }
   502  
   503  func validateDNSResults(ctx context.Context, f *framework.Framework, pod *v1.Pod, fileNames []string) {
   504  	ginkgo.By("submitting the pod to kubernetes")
   505  	podClient := f.ClientSet.CoreV1().Pods(f.Namespace.Name)
   506  	ginkgo.DeferCleanup(func(ctx context.Context) error {
   507  		ginkgo.By("deleting the pod")
   508  		return podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0))
   509  	})
   510  	if _, err := podClient.Create(ctx, pod, metav1.CreateOptions{}); err != nil {
   511  		framework.Failf("ginkgo.Failed to create pod %s/%s: %v", pod.Namespace, pod.Name, err)
   512  	}
   513  
   514  	framework.ExpectNoError(e2epod.WaitForPodRunningInNamespaceSlow(ctx, f.ClientSet, pod.Name, f.Namespace.Name))
   515  
   516  	ginkgo.By("retrieving the pod")
   517  	pod, err := podClient.Get(ctx, pod.Name, metav1.GetOptions{})
   518  	if err != nil {
   519  		framework.Failf("ginkgo.Failed to get pod %s/%s: %v", pod.Namespace, pod.Name, err)
   520  	}
   521  	// Try to find results for each expected name.
   522  	ginkgo.By("looking for the results for each expected name from probers")
   523  	assertFilesExist(ctx, fileNames, "results", pod, f.ClientSet)
   524  
   525  	// TODO: probe from the host, too.
   526  
   527  	framework.Logf("DNS probes using %s/%s succeeded\n", pod.Namespace, pod.Name)
   528  }
   529  
   530  func validateTargetedProbeOutput(ctx context.Context, f *framework.Framework, pod *v1.Pod, fileNames []string, value string) {
   531  	ginkgo.By("submitting the pod to kubernetes")
   532  	podClient := f.ClientSet.CoreV1().Pods(f.Namespace.Name)
   533  	ginkgo.DeferCleanup(func(ctx context.Context) error {
   534  		ginkgo.By("deleting the pod")
   535  		return podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0))
   536  	})
   537  	if _, err := podClient.Create(ctx, pod, metav1.CreateOptions{}); err != nil {
   538  		framework.Failf("ginkgo.Failed to create pod %s/%s: %v", pod.Namespace, pod.Name, err)
   539  	}
   540  
   541  	framework.ExpectNoError(e2epod.WaitForPodRunningInNamespaceSlow(ctx, f.ClientSet, pod.Name, f.Namespace.Name))
   542  
   543  	ginkgo.By("retrieving the pod")
   544  	pod, err := podClient.Get(ctx, pod.Name, metav1.GetOptions{})
   545  	if err != nil {
   546  		framework.Failf("ginkgo.Failed to get pod %s/%s: %v", pod.Namespace, pod.Name, err)
   547  	}
   548  	// Try to find the expected value for each expected name.
   549  	ginkgo.By("looking for the results for each expected name from probers")
   550  	assertFilesContain(ctx, fileNames, "results", pod, f.ClientSet, true, value)
   551  
   552  	framework.Logf("DNS probes using %s succeeded\n", pod.Name)
   553  }