From 60e9fe79afeb93446d30b4b7792846d7522e4939 Mon Sep 17 00:00:00 2001 From: Claudiu Belu Date: Thu, 11 Apr 2019 07:45:52 -0700 Subject: [PATCH] tests: agnhost image updates Adds the following subcommands to the agnhost binary: - dns-server-list: outputs the host's DNS server list with which it was configured with. This can be found in /etc/resolv.conf on Linux and through some powershell commands on Windows. - etc-hosts: outputs the host's hosts file. This can be found in /etc/hosts for Linux, and C:/Windows/System32/drivers/etc/hosts for Windows. - pause: pauses the binary's execution. This can be used to keep the Pod in a Running state for various reasons, including executing additional agnhost commands. Refactors bits of the code to avoid duplication. Adds a README for the agnhost image. --- test/images/agnhost/README.md | 79 ++++++++++++++++++++++++++++ test/images/agnhost/agnhost.go | 31 ++++++++++- test/images/agnhost/common.go | 61 +++++++++++++++++++++ test/images/agnhost/utils.go | 33 +++++++----- test/images/agnhost/utils_windows.go | 24 ++++----- 5 files changed, 202 insertions(+), 26 deletions(-) create mode 100644 test/images/agnhost/README.md diff --git a/test/images/agnhost/README.md b/test/images/agnhost/README.md new file mode 100644 index 0000000000..37a0ed1087 --- /dev/null +++ b/test/images/agnhost/README.md @@ -0,0 +1,79 @@ +# Agnhost + +## Overview + +There are significant differences between Linux and Windows, especially in the way +something can be obtained or tested. For example, the DNS suffix list can be found in +`/etc/resolv.conf` on Linux, but on Windows, such file does not exist, the same +information could retrieved through other means. To combat those differences, +`agnhost` was created. + +`agnhost` is an extendable CLI that behaves and outputs the same expected content, +no matter the underlying OS. The name itself reflects this idea, being a portmanteau +word of the words agnost and host. + +The image was created for testing purposes, reducing the need for having different test +cases for the same tested behaviour. + +## Usage + +The `agnhost` binary is a CLI with the following subcommands: + +- `dns-suffix`: It will output the host's configured DNS suffix list, separated by commas. +- `dns-server-list`: It will output the host's configured DNS servers, separated by commas. +- `etc-hosts`: It will output the contents of host's `hosts` file. This file's location + is `/etc/hosts` on Linux, while on Windows it is `C:/Windows/System32/drivers/etc/hosts`. +- `pause`: It will pause the execution of the binary. This can be used for containers + which have to be kept in a `Running` state for various purposes, including executing + other `agnhost` commands. +- `help`: Prints the binary's help menu. Additionally, it can be followed by another + subcommand in order to get more information about that subcommand, including its + possible arguments. + +For example, let's consider the following `pod.yaml` file: + +```yaml + apiVersion: v1 + kind: Pod + metadata: + name: test-agnhost + spec: + containers: + - args: + - dns-suffix + image: gcr.io/kubernetes-e2e-test-images/agnhost:1.0 + name: agnhost + dnsConfig: + nameservers: + - 1.1.1.1 + searches: + - resolv.conf.local + dnsPolicy: None +``` + +After we've used it to create a pod: + +```console + kubectl create -f pod.yaml +``` + +We can then check the container's output to see what is DNS suffix list the Pod was +configured with: + +```console + kubectl logs pod/test-agnhost +``` + +The output will be `resolv.conf.local`, as expected. Alternatively, the Pod could be +created with the `pause` argument instead, allowing us execute multiple commands: + +```console + kubectl exec test-agnhost -- /agnhost dns-suffix + kubectl exec test-agnhost -- /agnhost dns-server-list +``` + +## Image + +The image can be found at `gcr.io/kubernetes-e2e-test-images/agnhost:1.0` for Linux +containers, and `e2eteam/agnhost:1.0` for Windows containers. In the future, the same +repository can be used for both OSes. diff --git a/test/images/agnhost/agnhost.go b/test/images/agnhost/agnhost.go index 8b75955cbf..dd724eb478 100644 --- a/test/images/agnhost/agnhost.go +++ b/test/images/agnhost/agnhost.go @@ -23,13 +23,40 @@ import ( func main() { cmdDNSSuffix := &cobra.Command{ Use: "dns-suffix", - Short: "Prints the host's DNS suffix list.", - Long: `prints the DNS suffixes of this host.`, + Short: "Prints the host's DNS suffix list", + Long: `Prints the DNS suffixes of this host.`, Args: cobra.MaximumNArgs(0), Run: printDNSSuffixList, } + cmdDNSServerList := &cobra.Command{ + Use: "dns-server-list", + Short: "Prints the host's DNS Server list", + Long: `Prints the DNS Server list of this host.`, + Args: cobra.MaximumNArgs(0), + Run: printDNSServerList, + } + + cmdEtcHosts := &cobra.Command{ + Use: "etc-hosts", + Short: "Prints the host's /etc/hosts file", + Long: `Prints the "hosts" file of this host."`, + Args: cobra.MaximumNArgs(0), + Run: printHostsFile, + } + + cmdPause := &cobra.Command{ + Use: "pause", + Short: "Pauses", + Long: `Pauses the execution. Useful for keeping the containers running, so other commands can be executed.`, + Args: cobra.MaximumNArgs(0), + Run: pause, + } + rootCmd := &cobra.Command{Use: "app"} rootCmd.AddCommand(cmdDNSSuffix) + rootCmd.AddCommand(cmdDNSServerList) + rootCmd.AddCommand(cmdEtcHosts) + rootCmd.AddCommand(cmdPause) rootCmd.Execute() } diff --git a/test/images/agnhost/common.go b/test/images/agnhost/common.go index e0df5e7a01..9d16802543 100644 --- a/test/images/agnhost/common.go +++ b/test/images/agnhost/common.go @@ -17,8 +17,14 @@ limitations under the License. package main import ( + "bytes" "fmt" + "io/ioutil" + "os" + "os/exec" + "os/signal" "strings" + "syscall" "github.com/spf13/cobra" ) @@ -27,3 +33,58 @@ func printDNSSuffixList(cmd *cobra.Command, args []string) { dnsSuffixList := getDNSSuffixList() fmt.Println(strings.Join(dnsSuffixList, ",")) } + +func printDNSServerList(cmd *cobra.Command, args []string) { + dnsServerList := getDNSServerList() + fmt.Println(strings.Join(dnsServerList, ",")) +} + +func printHostsFile(cmd *cobra.Command, args []string) { + fmt.Println(readFile(etcHostsFile)) +} + +func pause(cmd *cobra.Command, args []string) { + sigCh := make(chan os.Signal) + done := make(chan int, 1) + signal.Notify(sigCh, syscall.SIGINT) + signal.Notify(sigCh, syscall.SIGTERM) + signal.Notify(sigCh, syscall.SIGKILL) + go func() { + sig := <-sigCh + switch sig { + case syscall.SIGINT: + done <- 1 + os.Exit(1) + case syscall.SIGTERM: + done <- 2 + os.Exit(2) + case syscall.SIGKILL: + done <- 0 + os.Exit(0) + } + }() + result := <-done + fmt.Printf("exiting %d\n", result) +} + +func readFile(fileName string) string { + fileData, err := ioutil.ReadFile(fileName) + if err != nil { + panic(err) + } + + return string(fileData) +} + +func runCommand(name string, arg ...string) string { + var out bytes.Buffer + cmd := exec.Command(name, arg...) + cmd.Stdout = &out + + err := cmd.Run() + if err != nil { + panic(err) + } + + return strings.TrimSpace(out.String()) +} diff --git a/test/images/agnhost/utils.go b/test/images/agnhost/utils.go index 950903e249..26cab3ce62 100644 --- a/test/images/agnhost/utils.go +++ b/test/images/agnhost/utils.go @@ -19,22 +19,18 @@ limitations under the License. package main import ( - "io/ioutil" "strings" ) +const etcHostsFile = "/etc/hosts" + +// A /etc/resolv.conf file managed by kubelet looks like this: +// nameserver DNS_CLUSTER_IP +// search test-dns.svc.cluster.local svc.cluster.local cluster.local q53aahaikqaehcai3ylfqdtc5b.bx.internal.cloudapp.net +// options ndots:5 func getDNSSuffixList() []string { - // A /etc/resolv.conf file managed by kubelet looks like this: - // nameserver DNS_CLUSTER_IP - // search test-dns.svc.cluster.local svc.cluster.local cluster.local q53aahaikqaehcai3ylfqdtc5b.bx.internal.cloudapp.net - // options ndots:5 - - fileData, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - panic(err) - } - - lines := strings.Split(string(fileData), "\n") + fileData := readFile("/etc/resolv.conf") + lines := strings.Split(fileData, "\n") for _, line := range lines { if strings.HasPrefix(line, "search") { // omit the starting "search". @@ -44,3 +40,16 @@ func getDNSSuffixList() []string { panic("Could not find DNS search list!") } + +func getDNSServerList() []string { + fileData := readFile("/etc/resolv.conf") + lines := strings.Split(fileData, "\n") + for _, line := range lines { + if strings.HasPrefix(line, "nameserver") { + // omit the starting "nameserver". + return strings.Split(line, " ")[1:] + } + } + + panic("Could not find DNS search list!") +} diff --git a/test/images/agnhost/utils_windows.go b/test/images/agnhost/utils_windows.go index 2199ac048b..0012fe7e6c 100644 --- a/test/images/agnhost/utils_windows.go +++ b/test/images/agnhost/utils_windows.go @@ -17,25 +17,25 @@ limitations under the License. package main import ( - "bytes" - "os/exec" "strings" ) +const etcHostsFile = "C:/Windows/System32/drivers/etc/hosts" + func getDNSSuffixList() []string { - var out bytes.Buffer - cmd := exec.Command("powershell", "-Command", "(Get-DnsClient)[0].SuffixSearchList") - cmd.Stdout = &out - - err := cmd.Run() - if err != nil { - panic(err) - } - - output := strings.TrimSpace(out.String()) + output := runCommand("powershell", "-Command", "(Get-DnsClient)[0].SuffixSearchList") if len(output) > 0 { return strings.Split(output, "\r\n") } panic("Could not find DNS search list!") } + +func getDNSServerList() []string { + output := runCommand("powershell", "-Command", "(Get-DnsClientServerAddress).ServerAddresses") + if len(output) > 0 { + return strings.Split(output, "\r\n") + } + + panic("Could not find DNS Server list!") +}