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