kubectl_utils.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package kubectl
  14. import (
  15. "bytes"
  16. "context"
  17. "fmt"
  18. "os/exec"
  19. "path/filepath"
  20. "strings"
  21. "time"
  22. v1 "k8s.io/api/core/v1"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. clientset "k8s.io/client-go/kubernetes"
  25. "k8s.io/client-go/tools/clientcmd"
  26. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  27. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  28. testutils "k8s.io/kubernetes/test/utils"
  29. "github.com/onsi/ginkgo"
  30. )
  31. const (
  32. maxKubectlExecRetries = 5
  33. )
  34. // TestKubeconfig is a struct containing the needed attributes from TestContext and Framework(Namespace).
  35. type TestKubeconfig struct {
  36. CertDir string
  37. Host string
  38. KubeConfig string
  39. KubeContext string
  40. KubectlPath string
  41. Namespace string // Every test has at least one namespace unless creation is skipped
  42. }
  43. // NewTestKubeconfig returns a new Kubeconfig struct instance.
  44. func NewTestKubeconfig(certdir, host, kubeconfig, kubecontext, kubectlpath, namespace string) *TestKubeconfig {
  45. return &TestKubeconfig{
  46. CertDir: certdir,
  47. Host: host,
  48. KubeConfig: kubeconfig,
  49. KubeContext: kubecontext,
  50. KubectlPath: kubectlpath,
  51. Namespace: namespace,
  52. }
  53. }
  54. // KubectlCmd runs the kubectl executable through the wrapper script.
  55. func (tk *TestKubeconfig) KubectlCmd(args ...string) *exec.Cmd {
  56. defaultArgs := []string{}
  57. // Reference a --server option so tests can run anywhere.
  58. if tk.Host != "" {
  59. defaultArgs = append(defaultArgs, "--"+clientcmd.FlagAPIServer+"="+tk.Host)
  60. }
  61. if tk.KubeConfig != "" {
  62. defaultArgs = append(defaultArgs, "--"+clientcmd.RecommendedConfigPathFlag+"="+tk.KubeConfig)
  63. // Reference the KubeContext
  64. if tk.KubeContext != "" {
  65. defaultArgs = append(defaultArgs, "--"+clientcmd.FlagContext+"="+tk.KubeContext)
  66. }
  67. } else {
  68. if tk.CertDir != "" {
  69. defaultArgs = append(defaultArgs,
  70. fmt.Sprintf("--certificate-authority=%s", filepath.Join(tk.CertDir, "ca.crt")),
  71. fmt.Sprintf("--client-certificate=%s", filepath.Join(tk.CertDir, "kubecfg.crt")),
  72. fmt.Sprintf("--client-key=%s", filepath.Join(tk.CertDir, "kubecfg.key")))
  73. }
  74. }
  75. kubectlArgs := append(defaultArgs, args...)
  76. //We allow users to specify path to kubectl, so you can test either "kubectl" or "cluster/kubectl.sh"
  77. //and so on.
  78. cmd := exec.Command(tk.KubectlPath, kubectlArgs...)
  79. //caller will invoke this and wait on it.
  80. return cmd
  81. }
  82. // LogFailedContainers runs `kubectl logs` on a failed containers.
  83. func LogFailedContainers(c clientset.Interface, ns string, logFunc func(ftm string, args ...interface{})) {
  84. podList, err := c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{})
  85. if err != nil {
  86. logFunc("Error getting pods in namespace '%s': %v", ns, err)
  87. return
  88. }
  89. logFunc("Running kubectl logs on non-ready containers in %v", ns)
  90. for _, pod := range podList.Items {
  91. if res, err := testutils.PodRunningReady(&pod); !res || err != nil {
  92. kubectlLogPod(c, pod, "", e2elog.Logf)
  93. }
  94. }
  95. }
  96. func kubectlLogPod(c clientset.Interface, pod v1.Pod, containerNameSubstr string, logFunc func(ftm string, args ...interface{})) {
  97. for _, container := range pod.Spec.Containers {
  98. if strings.Contains(container.Name, containerNameSubstr) {
  99. // Contains() matches all strings if substr is empty
  100. logs, err := e2epod.GetPodLogs(c, pod.Namespace, pod.Name, container.Name)
  101. if err != nil {
  102. logs, err = e2epod.GetPreviousPodLogs(c, pod.Namespace, pod.Name, container.Name)
  103. if err != nil {
  104. logFunc("Failed to get logs of pod %v, container %v, err: %v", pod.Name, container.Name, err)
  105. }
  106. }
  107. logFunc("Logs of %v/%v:%v on node %v", pod.Namespace, pod.Name, container.Name, pod.Spec.NodeName)
  108. logFunc("%s : STARTLOG\n%s\nENDLOG for container %v:%v:%v", containerNameSubstr, logs, pod.Namespace, pod.Name, container.Name)
  109. }
  110. }
  111. }
  112. // WriteFileViaContainer writes a file using kubectl exec echo <contents> > <path> via specified container
  113. // because of the primitive technique we're using here, we only allow ASCII alphanumeric characters
  114. func (tk *TestKubeconfig) WriteFileViaContainer(podName, containerName string, path string, contents string) error {
  115. ginkgo.By("writing a file in the container")
  116. allowedCharacters := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  117. for _, c := range contents {
  118. if !strings.ContainsRune(allowedCharacters, c) {
  119. return fmt.Errorf("Unsupported character in string to write: %v", c)
  120. }
  121. }
  122. command := fmt.Sprintf("echo '%s' > '%s'; sync", contents, path)
  123. stdout, stderr, err := tk.kubectlExecWithRetry(tk.Namespace, podName, containerName, "--", "/bin/sh", "-c", command)
  124. if err != nil {
  125. e2elog.Logf("error running kubectl exec to write file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
  126. }
  127. return err
  128. }
  129. // ReadFileViaContainer reads a file using kubectl exec cat <path>.
  130. func (tk *TestKubeconfig) ReadFileViaContainer(podName, containerName string, path string) (string, error) {
  131. ginkgo.By("reading a file in the container")
  132. stdout, stderr, err := tk.kubectlExecWithRetry(tk.Namespace, podName, containerName, "--", "cat", path)
  133. if err != nil {
  134. e2elog.Logf("error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))
  135. }
  136. return string(stdout), err
  137. }
  138. func (tk *TestKubeconfig) kubectlExecWithRetry(namespace string, podName, containerName string, args ...string) ([]byte, []byte, error) {
  139. for numRetries := 0; numRetries < maxKubectlExecRetries; numRetries++ {
  140. if numRetries > 0 {
  141. e2elog.Logf("Retrying kubectl exec (retry count=%v/%v)", numRetries+1, maxKubectlExecRetries)
  142. }
  143. stdOutBytes, stdErrBytes, err := tk.kubectlExec(namespace, podName, containerName, args...)
  144. if err != nil {
  145. if strings.Contains(strings.ToLower(string(stdErrBytes)), "i/o timeout") {
  146. // Retry on "i/o timeout" errors
  147. e2elog.Logf("Warning: kubectl exec encountered i/o timeout.\nerr=%v\nstdout=%v\nstderr=%v)", err, string(stdOutBytes), string(stdErrBytes))
  148. continue
  149. }
  150. if strings.Contains(strings.ToLower(string(stdErrBytes)), "container not found") {
  151. // Retry on "container not found" errors
  152. e2elog.Logf("Warning: kubectl exec encountered container not found.\nerr=%v\nstdout=%v\nstderr=%v)", err, string(stdOutBytes), string(stdErrBytes))
  153. time.Sleep(2 * time.Second)
  154. continue
  155. }
  156. }
  157. return stdOutBytes, stdErrBytes, err
  158. }
  159. err := fmt.Errorf("Failed: kubectl exec failed %d times with \"i/o timeout\". Giving up", maxKubectlExecRetries)
  160. return nil, nil, err
  161. }
  162. func (tk *TestKubeconfig) kubectlExec(namespace string, podName, containerName string, args ...string) ([]byte, []byte, error) {
  163. var stdout, stderr bytes.Buffer
  164. cmdArgs := []string{
  165. "exec",
  166. fmt.Sprintf("--namespace=%v", namespace),
  167. podName,
  168. fmt.Sprintf("-c=%v", containerName),
  169. }
  170. cmdArgs = append(cmdArgs, args...)
  171. cmd := tk.KubectlCmd(cmdArgs...)
  172. cmd.Stdout, cmd.Stderr = &stdout, &stderr
  173. e2elog.Logf("Running '%s %s'", cmd.Path, strings.Join(cmdArgs, " "))
  174. err := cmd.Run()
  175. return stdout.Bytes(), stderr.Bytes(), err
  176. }