diff --git a/test/e2e/network/dns.go b/test/e2e/network/dns.go index 556d4720820..7f41ced62f1 100644 --- a/test/e2e/network/dns.go +++ b/test/e2e/network/dns.go @@ -17,255 +17,22 @@ limitations under the License. package network import ( - "context" "fmt" "strings" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/test/e2e/framework" - imageutils "k8s.io/kubernetes/test/utils/image" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) const dnsTestPodHostName = "dns-querier-1" const dnsTestServiceName = "dns-test-service" -func createDNSPod(namespace, wheezyProbeCmd, jessieProbeCmd string) *v1.Pod { - dnsPod := &v1.Pod{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pod", - APIVersion: testapi.Groups[v1.GroupName].GroupVersion().String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dns-test-" + string(uuid.NewUUID()), - Namespace: namespace, - }, - Spec: v1.PodSpec{ - Volumes: []v1.Volume{ - { - Name: "results", - VolumeSource: v1.VolumeSource{ - EmptyDir: &v1.EmptyDirVolumeSource{}, - }, - }, - }, - Containers: []v1.Container{ - // TODO: Consider scraping logs instead of running a webserver. - { - Name: "webserver", - Image: imageutils.GetE2EImage(imageutils.TestWebserver), - Ports: []v1.ContainerPort{ - { - Name: "http", - ContainerPort: 80, - }, - }, - VolumeMounts: []v1.VolumeMount{ - { - Name: "results", - MountPath: "/results", - }, - }, - }, - { - Name: "querier", - Image: imageutils.GetE2EImage(imageutils.Dnsutils), - Command: []string{"sh", "-c", wheezyProbeCmd}, - VolumeMounts: []v1.VolumeMount{ - { - Name: "results", - MountPath: "/results", - }, - }, - }, - { - Name: "jessie-querier", - Image: imageutils.GetE2EImage(imageutils.JessieDnsutils), - Command: []string{"sh", "-c", jessieProbeCmd}, - VolumeMounts: []v1.VolumeMount{ - { - Name: "results", - MountPath: "/results", - }, - }, - }, - }, - }, - } - - dnsPod.Spec.Hostname = dnsTestPodHostName - dnsPod.Spec.Subdomain = dnsTestServiceName - - return dnsPod -} - -func createProbeCommand(namesToResolve []string, hostEntries []string, ptrLookupIP string, fileNamePrefix, namespace string) (string, []string) { - fileNames := make([]string, 0, len(namesToResolve)*2) - probeCmd := "for i in `seq 1 600`; do " - for _, name := range namesToResolve { - // Resolve by TCP and UDP DNS. Use $$(...) because $(...) is - // expanded by kubernetes (though this won't expand so should - // remain a literal, safe > sorry). - lookup := "A" - if strings.HasPrefix(name, "_") { - lookup = "SRV" - } - fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, name) - fileNames = append(fileNames, fileName) - probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search %s %s)" && echo OK > /results/%s;`, name, lookup, fileName) - fileName = fmt.Sprintf("%s_tcp@%s", fileNamePrefix, name) - fileNames = append(fileNames, fileName) - probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search %s %s)" && echo OK > /results/%s;`, name, lookup, fileName) - } - - for _, name := range hostEntries { - fileName := fmt.Sprintf("%s_hosts@%s", fileNamePrefix, name) - fileNames = append(fileNames, fileName) - probeCmd += fmt.Sprintf(`test -n "$$(getent hosts %s)" && echo OK > /results/%s;`, name, fileName) - } - - podARecByUDPFileName := fmt.Sprintf("%s_udp@PodARecord", fileNamePrefix) - podARecByTCPFileName := fmt.Sprintf("%s_tcp@PodARecord", fileNamePrefix) - probeCmd += fmt.Sprintf(`podARec=$$(hostname -i| awk -F. '{print $$1"-"$$2"-"$$3"-"$$4".%s.pod.cluster.local"}');`, namespace) - probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search $${podARec} A)" && echo OK > /results/%s;`, podARecByUDPFileName) - probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search $${podARec} A)" && echo OK > /results/%s;`, podARecByTCPFileName) - fileNames = append(fileNames, podARecByUDPFileName) - fileNames = append(fileNames, podARecByTCPFileName) - - if len(ptrLookupIP) > 0 { - ptrLookup := fmt.Sprintf("%s.in-addr.arpa.", strings.Join(reverseArray(strings.Split(ptrLookupIP, ".")), ".")) - ptrRecByUDPFileName := fmt.Sprintf("%s_udp@PTR", ptrLookupIP) - ptrRecByTCPFileName := fmt.Sprintf("%s_tcp@PTR", ptrLookupIP) - probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search %s PTR)" && echo OK > /results/%s;`, ptrLookup, ptrRecByUDPFileName) - probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search %s PTR)" && echo OK > /results/%s;`, ptrLookup, ptrRecByTCPFileName) - fileNames = append(fileNames, ptrRecByUDPFileName) - fileNames = append(fileNames, ptrRecByTCPFileName) - } - - probeCmd += "sleep 1; done" - return probeCmd, fileNames -} - -// createTargetedProbeCommand returns a command line that performs a DNS lookup for a specific record type -func createTargetedProbeCommand(nameToResolve string, lookup string, fileNamePrefix string) (string, string) { - fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, nameToResolve) - probeCmd := fmt.Sprintf("dig +short +tries=12 +norecurse %s %s > /results/%s", nameToResolve, lookup, fileName) - return probeCmd, fileName -} - -func assertFilesExist(fileNames []string, fileDir string, pod *v1.Pod, client clientset.Interface) { - assertFilesContain(fileNames, fileDir, pod, client, false, "") -} - -func assertFilesContain(fileNames []string, fileDir string, pod *v1.Pod, client clientset.Interface, check bool, expected string) { - var failed []string - - framework.ExpectNoError(wait.Poll(time.Second*10, time.Second*600, func() (bool, error) { - failed = []string{} - - ctx, cancel := context.WithTimeout(context.Background(), framework.SingleCallTimeout) - defer cancel() - - for _, fileName := range fileNames { - contents, err := client.CoreV1().RESTClient().Get(). - Context(ctx). - Namespace(pod.Namespace). - Resource("pods"). - SubResource("proxy"). - Name(pod.Name). - Suffix(fileDir, fileName). - Do().Raw() - - if err != nil { - if ctx.Err() != nil { - framework.Failf("Unable to read %s from pod %s: %v", fileName, pod.Name, err) - } else { - framework.Logf("Unable to read %s from pod %s: %v", fileName, pod.Name, err) - } - failed = append(failed, fileName) - } else if check && strings.TrimSpace(string(contents)) != expected { - framework.Logf("File %s from pod %s contains '%s' instead of '%s'", fileName, pod.Name, string(contents), expected) - failed = append(failed, fileName) - } - } - if len(failed) == 0 { - return true, nil - } - framework.Logf("Lookups using %s failed for: %v\n", pod.Name, failed) - return false, nil - })) - Expect(len(failed)).To(Equal(0)) -} - -func validateDNSResults(f *framework.Framework, pod *v1.Pod, fileNames []string) { - By("submitting the pod to kubernetes") - podClient := f.ClientSet.CoreV1().Pods(f.Namespace.Name) - defer func() { - By("deleting the pod") - defer GinkgoRecover() - podClient.Delete(pod.Name, metav1.NewDeleteOptions(0)) - }() - if _, err := podClient.Create(pod); err != nil { - framework.Failf("Failed to create %s pod: %v", pod.Name, err) - } - - framework.ExpectNoError(f.WaitForPodRunning(pod.Name)) - - By("retrieving the pod") - pod, err := podClient.Get(pod.Name, metav1.GetOptions{}) - if err != nil { - framework.Failf("Failed to get pod %s: %v", pod.Name, err) - } - // Try to find results for each expected name. - By("looking for the results for each expected name from probers") - assertFilesExist(fileNames, "results", pod, f.ClientSet) - - // TODO: probe from the host, too. - - framework.Logf("DNS probes using %s succeeded\n", pod.Name) -} - -func validateTargetedProbeOutput(f *framework.Framework, pod *v1.Pod, fileNames []string, value string) { - By("submitting the pod to kubernetes") - podClient := f.ClientSet.CoreV1().Pods(f.Namespace.Name) - defer func() { - By("deleting the pod") - defer GinkgoRecover() - podClient.Delete(pod.Name, metav1.NewDeleteOptions(0)) - }() - if _, err := podClient.Create(pod); err != nil { - framework.Failf("Failed to create %s pod: %v", pod.Name, err) - } - - framework.ExpectNoError(f.WaitForPodRunning(pod.Name)) - - By("retrieving the pod") - pod, err := podClient.Get(pod.Name, metav1.GetOptions{}) - if err != nil { - framework.Failf("Failed to get pod %s: %v", pod.Name, err) - } - // Try to find the expected value for each expected name. - By("looking for the results for each expected name from probers") - assertFilesContain(fileNames, "results", pod, f.ClientSet, true, value) - - framework.Logf("DNS probes using %s succeeded\n", pod.Name) -} - -func reverseArray(arr []string) []string { - for i := 0; i < len(arr)/2; i++ { - j := len(arr) - i - 1 - arr[i], arr[j] = arr[j], arr[i] - } - return arr -} - var _ = SIGDescribe("DNS", func() { f := framework.NewDefaultFramework("dns") @@ -295,7 +62,7 @@ var _ = SIGDescribe("DNS", func() { // Run a pod which probes DNS and exposes the results by HTTP. By("creating a pod to probe DNS") - pod := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + pod := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd, dnsTestPodHostName, dnsTestServiceName) validateDNSResults(f, pod, append(wheezyFileNames, jessieFileNames...)) }) @@ -345,7 +112,7 @@ var _ = SIGDescribe("DNS", func() { // Run a pod which probes DNS and exposes the results by HTTP. By("creating a pod to probe DNS") - pod := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + pod := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd, dnsTestPodHostName, dnsTestServiceName) pod.ObjectMeta.Labels = testServiceSelector validateDNSResults(f, pod, append(wheezyFileNames, jessieFileNames...)) @@ -378,7 +145,7 @@ var _ = SIGDescribe("DNS", func() { // Run a pod which probes DNS and exposes the results by HTTP. By("creating a pod to probe DNS") - pod1 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + pod1 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd, dnsTestPodHostName, dnsTestServiceName) pod1.ObjectMeta.Labels = testServiceSelector pod1.Spec.Hostname = podHostname pod1.Spec.Subdomain = serviceName @@ -407,7 +174,7 @@ var _ = SIGDescribe("DNS", func() { // Run a pod which probes DNS and exposes the results by HTTP. By("creating a pod to probe DNS") - pod1 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + pod1 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd, dnsTestPodHostName, dnsTestServiceName) validateTargetedProbeOutput(f, pod1, []string{wheezyFileName, jessieFileName}, "foo.example.com.") @@ -424,7 +191,7 @@ var _ = SIGDescribe("DNS", func() { // Run a pod which probes DNS and exposes the results by HTTP. By("creating a second pod to probe DNS") - pod2 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + pod2 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd, dnsTestPodHostName, dnsTestServiceName) validateTargetedProbeOutput(f, pod2, []string{wheezyFileName, jessieFileName}, "bar.example.com.") @@ -444,11 +211,111 @@ var _ = SIGDescribe("DNS", func() { // Run a pod which probes DNS and exposes the results by HTTP. By("creating a third pod to probe DNS") - pod3 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd) + pod3 := createDNSPod(f.Namespace.Name, wheezyProbeCmd, jessieProbeCmd, dnsTestPodHostName, dnsTestServiceName) svc, err := f.ClientSet.CoreV1().Services(f.Namespace.Name).Get(externalNameService.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) validateTargetedProbeOutput(f, pod3, []string{wheezyFileName, jessieFileName}, svc.Spec.ClusterIP) }) + + It("should support configurable pod resolv.conf", func() { + By("Preparing a test DNS service with injected DNS names...") + testInjectedIP := "1.1.1.1" + testDNSNameShort := "notexistname" + testSearchPath := "resolv.conf.local" + testDNSNameFull := fmt.Sprintf("%s.%s", testDNSNameShort, testSearchPath) + + testServerPod := generateDNSServerPod(map[string]string{ + testDNSNameFull: testInjectedIP, + }) + testServerPod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(testServerPod) + Expect(err).NotTo(HaveOccurred(), "failed to create pod %s", testServerPod.Name) + framework.Logf("Created pod %v", testServerPod) + defer func() { + framework.Logf("Deleting pod %s...", testServerPod.Name) + if err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete(testServerPod.Name, metav1.NewDeleteOptions(0)); err != nil { + framework.Failf("Failed to delete pod %s: %v", testServerPod.Name, err) + } + }() + Expect(f.WaitForPodRunning(testServerPod.Name)).NotTo(HaveOccurred(), "failed to wait for pod %s to be running", testServerPod.Name) + + // Retrieve server pod IP. + testServerPod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(testServerPod.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to get pod %v", testServerPod.Name) + testServerIP := testServerPod.Status.PodIP + framework.Logf("testServerIP is %s", testServerIP) + + By("Creating a pod with dnsPolicy=None and customized dnsConfig...") + testUtilsPod := generateDNSUtilsPod() + testUtilsPod.Spec.DNSPolicy = v1.DNSNone + testNdotsValue := "2" + testUtilsPod.Spec.DNSConfig = &v1.PodDNSConfig{ + Nameservers: []string{testServerIP}, + Searches: []string{testSearchPath}, + Options: []v1.PodDNSConfigOption{ + { + Name: "ndots", + Value: &testNdotsValue, + }, + }, + } + testUtilsPod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(testUtilsPod) + Expect(err).NotTo(HaveOccurred(), "failed to create pod %s", testUtilsPod.Name) + framework.Logf("Created pod %v", testUtilsPod) + defer func() { + framework.Logf("Deleting pod %s...", testUtilsPod.Name) + if err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete(testUtilsPod.Name, metav1.NewDeleteOptions(0)); err != nil { + framework.Failf("Failed to delete pod %s: %v", testUtilsPod.Name, err) + } + }() + Expect(f.WaitForPodRunning(testUtilsPod.Name)).NotTo(HaveOccurred(), "failed to wait for pod %s to be running", testUtilsPod.Name) + + By("Verifying customized DNS option is configured on pod...") + // TODO: Figure out a better way other than checking the actual resolv,conf file. + cmd := []string{"cat", "/etc/resolv.conf"} + stdout, stderr, err := f.ExecWithOptions(framework.ExecOptions{ + Command: cmd, + Namespace: f.Namespace.Name, + PodName: testUtilsPod.Name, + ContainerName: "util", + CaptureStdout: true, + CaptureStderr: true, + }) + Expect(err).NotTo(HaveOccurred(), "failed to examine resolv,conf file on pod, stdout: %v, stderr: %v, err: %v", stdout, stderr, err) + if !strings.Contains(stdout, "ndots:2") { + framework.Failf("customized DNS options not found in resolv.conf, got: %s", stdout) + } + + By("Verifying customized name server and search path are working...") + // Do dig on not-exist-dns-name and see if the injected DNS record is returned. + // This verifies both: + // - Custom search path is appended. + // - DNS query is sent to the specified server. + cmd = []string{"/usr/bin/dig", "+short", "+search", testDNSNameShort} + digFunc := func() (bool, error) { + stdout, stderr, err := f.ExecWithOptions(framework.ExecOptions{ + Command: cmd, + Namespace: f.Namespace.Name, + PodName: testUtilsPod.Name, + ContainerName: "util", + CaptureStdout: true, + CaptureStderr: true, + }) + if err != nil { + framework.Logf("Failed to execute dig command, stdout:%v, stderr: %v, err: %v", stdout, stderr, err) + return false, nil + } + res := strings.Split(stdout, "\n") + if len(res) != 1 || res[0] != testInjectedIP { + framework.Logf("Expect command `%v` to return %s, got: %v", cmd, testInjectedIP, res) + return false, nil + } + return true, nil + } + err = wait.PollImmediate(5*time.Second, 3*time.Minute, digFunc) + Expect(err).NotTo(HaveOccurred(), "failed to verify customized name server and search path") + + // TODO: Add more test cases for other DNSPolicies. + }) }) diff --git a/test/e2e/network/dns_common.go b/test/e2e/network/dns_common.go index 66ecefa9da2..1400f42e0ed 100644 --- a/test/e2e/network/dns_common.go +++ b/test/e2e/network/dns_common.go @@ -17,6 +17,7 @@ limitations under the License. package network import ( + "context" "fmt" "strings" "time" @@ -27,8 +28,10 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/test/e2e/framework" imageutils "k8s.io/kubernetes/test/utils/image" @@ -229,13 +232,12 @@ func (t *dnsTestCommon) deleteUtilPod() { } } -func (t *dnsTestCommon) createDNSServer(aRecords map[string]string) { - t.dnsServerPod = &v1.Pod{ +func generateDNSServerPod(aRecords map[string]string) *v1.Pod { + pod := &v1.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: t.f.Namespace.Name, GenerateName: "e2e-dns-configmap-dns-server-", }, Spec: v1.PodSpec{ @@ -257,10 +259,15 @@ func (t *dnsTestCommon) createDNSServer(aRecords map[string]string) { } for name, ip := range aRecords { - t.dnsServerPod.Spec.Containers[0].Command = append( - t.dnsServerPod.Spec.Containers[0].Command, + pod.Spec.Containers[0].Command = append( + pod.Spec.Containers[0].Command, fmt.Sprintf("-A/%v/%v", name, ip)) } + return pod +} + +func (t *dnsTestCommon) createDNSServer(aRecords map[string]string) { + t.dnsServerPod = generateDNSServerPod(aRecords) var err error t.dnsServerPod, err = t.c.CoreV1().Pods(t.f.Namespace.Name).Create(t.dnsServerPod) @@ -280,3 +287,252 @@ func (t *dnsTestCommon) deleteDNSServerPod() { t.utilPod.Namespace, t.dnsServerPod.Name, err) } } + +func createDNSPod(namespace, wheezyProbeCmd, jessieProbeCmd, podHostName, serviceName string) *v1.Pod { + dnsPod := &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + APIVersion: testapi.Groups[v1.GroupName].GroupVersion().String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "dns-test-" + string(uuid.NewUUID()), + Namespace: namespace, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{ + { + Name: "results", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }, + }, + Containers: []v1.Container{ + // TODO: Consider scraping logs instead of running a webserver. + { + Name: "webserver", + Image: imageutils.GetE2EImage(imageutils.TestWebserver), + Ports: []v1.ContainerPort{ + { + Name: "http", + ContainerPort: 80, + }, + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "results", + MountPath: "/results", + }, + }, + }, + { + Name: "querier", + Image: imageutils.GetE2EImage(imageutils.Dnsutils), + Command: []string{"sh", "-c", wheezyProbeCmd}, + VolumeMounts: []v1.VolumeMount{ + { + Name: "results", + MountPath: "/results", + }, + }, + }, + { + Name: "jessie-querier", + Image: imageutils.GetE2EImage(imageutils.JessieDnsutils), + Command: []string{"sh", "-c", jessieProbeCmd}, + VolumeMounts: []v1.VolumeMount{ + { + Name: "results", + MountPath: "/results", + }, + }, + }, + }, + }, + } + + dnsPod.Spec.Hostname = podHostName + dnsPod.Spec.Subdomain = serviceName + + return dnsPod +} + +func createProbeCommand(namesToResolve []string, hostEntries []string, ptrLookupIP string, fileNamePrefix, namespace string) (string, []string) { + fileNames := make([]string, 0, len(namesToResolve)*2) + probeCmd := "for i in `seq 1 600`; do " + for _, name := range namesToResolve { + // Resolve by TCP and UDP DNS. Use $$(...) because $(...) is + // expanded by kubernetes (though this won't expand so should + // remain a literal, safe > sorry). + lookup := "A" + if strings.HasPrefix(name, "_") { + lookup = "SRV" + } + fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, name) + fileNames = append(fileNames, fileName) + probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search %s %s)" && echo OK > /results/%s;`, name, lookup, fileName) + fileName = fmt.Sprintf("%s_tcp@%s", fileNamePrefix, name) + fileNames = append(fileNames, fileName) + probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search %s %s)" && echo OK > /results/%s;`, name, lookup, fileName) + } + + for _, name := range hostEntries { + fileName := fmt.Sprintf("%s_hosts@%s", fileNamePrefix, name) + fileNames = append(fileNames, fileName) + probeCmd += fmt.Sprintf(`test -n "$$(getent hosts %s)" && echo OK > /results/%s;`, name, fileName) + } + + podARecByUDPFileName := fmt.Sprintf("%s_udp@PodARecord", fileNamePrefix) + podARecByTCPFileName := fmt.Sprintf("%s_tcp@PodARecord", fileNamePrefix) + probeCmd += fmt.Sprintf(`podARec=$$(hostname -i| awk -F. '{print $$1"-"$$2"-"$$3"-"$$4".%s.pod.cluster.local"}');`, namespace) + probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search $${podARec} A)" && echo OK > /results/%s;`, podARecByUDPFileName) + probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search $${podARec} A)" && echo OK > /results/%s;`, podARecByTCPFileName) + fileNames = append(fileNames, podARecByUDPFileName) + fileNames = append(fileNames, podARecByTCPFileName) + + if len(ptrLookupIP) > 0 { + ptrLookup := fmt.Sprintf("%s.in-addr.arpa.", strings.Join(reverseArray(strings.Split(ptrLookupIP, ".")), ".")) + ptrRecByUDPFileName := fmt.Sprintf("%s_udp@PTR", ptrLookupIP) + ptrRecByTCPFileName := fmt.Sprintf("%s_tcp@PTR", ptrLookupIP) + probeCmd += fmt.Sprintf(`test -n "$$(dig +notcp +noall +answer +search %s PTR)" && echo OK > /results/%s;`, ptrLookup, ptrRecByUDPFileName) + probeCmd += fmt.Sprintf(`test -n "$$(dig +tcp +noall +answer +search %s PTR)" && echo OK > /results/%s;`, ptrLookup, ptrRecByTCPFileName) + fileNames = append(fileNames, ptrRecByUDPFileName) + fileNames = append(fileNames, ptrRecByTCPFileName) + } + + probeCmd += "sleep 1; done" + return probeCmd, fileNames +} + +// createTargetedProbeCommand returns a command line that performs a DNS lookup for a specific record type +func createTargetedProbeCommand(nameToResolve string, lookup string, fileNamePrefix string) (string, string) { + fileName := fmt.Sprintf("%s_udp@%s", fileNamePrefix, nameToResolve) + probeCmd := fmt.Sprintf("dig +short +tries=12 +norecurse %s %s > /results/%s", nameToResolve, lookup, fileName) + return probeCmd, fileName +} + +func assertFilesExist(fileNames []string, fileDir string, pod *v1.Pod, client clientset.Interface) { + assertFilesContain(fileNames, fileDir, pod, client, false, "") +} + +func assertFilesContain(fileNames []string, fileDir string, pod *v1.Pod, client clientset.Interface, check bool, expected string) { + var failed []string + + framework.ExpectNoError(wait.Poll(time.Second*10, time.Second*600, func() (bool, error) { + failed = []string{} + + ctx, cancel := context.WithTimeout(context.Background(), framework.SingleCallTimeout) + defer cancel() + + for _, fileName := range fileNames { + contents, err := client.CoreV1().RESTClient().Get(). + Context(ctx). + Namespace(pod.Namespace). + Resource("pods"). + SubResource("proxy"). + Name(pod.Name). + Suffix(fileDir, fileName). + Do().Raw() + + if err != nil { + if ctx.Err() != nil { + framework.Failf("Unable to read %s from pod %s: %v", fileName, pod.Name, err) + } else { + framework.Logf("Unable to read %s from pod %s: %v", fileName, pod.Name, err) + } + failed = append(failed, fileName) + } else if check && strings.TrimSpace(string(contents)) != expected { + framework.Logf("File %s from pod %s contains '%s' instead of '%s'", fileName, pod.Name, string(contents), expected) + failed = append(failed, fileName) + } + } + if len(failed) == 0 { + return true, nil + } + framework.Logf("Lookups using %s failed for: %v\n", pod.Name, failed) + return false, nil + })) + Expect(len(failed)).To(Equal(0)) +} + +func validateDNSResults(f *framework.Framework, pod *v1.Pod, fileNames []string) { + By("submitting the pod to kubernetes") + podClient := f.ClientSet.CoreV1().Pods(f.Namespace.Name) + defer func() { + By("deleting the pod") + defer GinkgoRecover() + podClient.Delete(pod.Name, metav1.NewDeleteOptions(0)) + }() + if _, err := podClient.Create(pod); err != nil { + framework.Failf("Failed to create %s pod: %v", pod.Name, err) + } + + framework.ExpectNoError(f.WaitForPodRunning(pod.Name)) + + By("retrieving the pod") + pod, err := podClient.Get(pod.Name, metav1.GetOptions{}) + if err != nil { + framework.Failf("Failed to get pod %s: %v", pod.Name, err) + } + // Try to find results for each expected name. + By("looking for the results for each expected name from probers") + assertFilesExist(fileNames, "results", pod, f.ClientSet) + + // TODO: probe from the host, too. + + framework.Logf("DNS probes using %s succeeded\n", pod.Name) +} + +func validateTargetedProbeOutput(f *framework.Framework, pod *v1.Pod, fileNames []string, value string) { + By("submitting the pod to kubernetes") + podClient := f.ClientSet.CoreV1().Pods(f.Namespace.Name) + defer func() { + By("deleting the pod") + defer GinkgoRecover() + podClient.Delete(pod.Name, metav1.NewDeleteOptions(0)) + }() + if _, err := podClient.Create(pod); err != nil { + framework.Failf("Failed to create %s pod: %v", pod.Name, err) + } + + framework.ExpectNoError(f.WaitForPodRunning(pod.Name)) + + By("retrieving the pod") + pod, err := podClient.Get(pod.Name, metav1.GetOptions{}) + if err != nil { + framework.Failf("Failed to get pod %s: %v", pod.Name, err) + } + // Try to find the expected value for each expected name. + By("looking for the results for each expected name from probers") + assertFilesContain(fileNames, "results", pod, f.ClientSet, true, value) + + framework.Logf("DNS probes using %s succeeded\n", pod.Name) +} + +func reverseArray(arr []string) []string { + for i := 0; i < len(arr)/2; i++ { + j := len(arr) - i - 1 + arr[i], arr[j] = arr[j], arr[i] + } + return arr +} + +func generateDNSUtilsPod() *v1.Pod { + return &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "e2e-dns-utils-", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "util", + Image: imageutils.GetE2EImage(imageutils.Dnsutils), + Command: []string{"sleep", "10000"}, + }, + }, + }, + } +}