github.com/interconnectedcloud/qdr-operator@v0.0.0-20210826174505-576d2b33dac7/test/e2e/framework/kubectl.go (about) 1 /* 2 Copyright 2019 The Interconnectedcloud 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 framework 18 19 import ( 20 "bytes" 21 "fmt" 22 "net" 23 "net/url" 24 "os/exec" 25 "path/filepath" 26 "regexp" 27 "strings" 28 "syscall" 29 "time" 30 31 "k8s.io/client-go/tools/clientcmd" 32 uexec "k8s.io/utils/exec" 33 34 e2elog "github.com/interconnectedcloud/qdr-operator/test/e2e/framework/log" 35 //"github.com/onsi/gomega" 36 ) 37 38 const ( 39 // Poll is how often to Poll pods 40 Poll = 2 * time.Second 41 ) 42 43 // KubectlCmd runs the kubectl executable through the wrapper script. 44 func KubectlCmd(args ...string) *exec.Cmd { 45 defaultArgs := []string{} 46 47 // Reference a --server option so tests can run anywhere. 48 if TestContext.Host != "" { 49 defaultArgs = append(defaultArgs, "--"+clientcmd.FlagAPIServer+"="+TestContext.Host) 50 } 51 if TestContext.KubeConfig != "" { 52 defaultArgs = append(defaultArgs, "--"+clientcmd.RecommendedConfigPathFlag+"="+TestContext.KubeConfig) 53 54 // Reference the KubeContext 55 if TestContext.KubeContext != "" { 56 defaultArgs = append(defaultArgs, "--"+clientcmd.FlagContext+"="+TestContext.KubeContext) 57 } 58 59 } else { 60 if TestContext.CertDir != "" { 61 defaultArgs = append(defaultArgs, 62 fmt.Sprintf("--certificate-authority=%s", filepath.Join(TestContext.CertDir, "ca.crt")), 63 fmt.Sprintf("--client-certificate=%s", filepath.Join(TestContext.CertDir, "kubecfg.crt")), 64 fmt.Sprintf("--client-key=%s", filepath.Join(TestContext.CertDir, "kubecfg.key"))) 65 } 66 } 67 kubectlArgs := append(defaultArgs, args...) 68 69 //We allow users to specify path to kubectl, so you can test either "kubectl" or "cluster/kubectl.sh" 70 //and so on. 71 cmd := exec.Command(TestContext.KubectlPath, kubectlArgs...) 72 73 //caller will invoke this and wait on it. 74 return cmd 75 } 76 77 // LookForString looks for the given string in the output of fn, repeatedly calling fn until 78 // the timeout is reached or the string is found. Returns last log and possibly 79 // error if the string was not found. 80 // TODO(alejandrox1): move to pod/ subpkg once kubectl methods are refactored. 81 func LookForString(expectedString string, timeout time.Duration, fn func() string) (result string, err error) { 82 for t := time.Now(); time.Since(t) < timeout; time.Sleep(Poll) { 83 result = fn() 84 if strings.Contains(result, expectedString) { 85 return 86 } 87 } 88 err = fmt.Errorf("Failed to find \"%s\", last result: \"%s\"", expectedString, result) 89 return 90 } 91 92 // LookForStringInLog looks for the given string in the log of a specific pod container 93 func LookForStringInLog(ns, podName, container, expectedString string, timeout time.Duration) (result string, err error) { 94 return LookForString(expectedString, timeout, func() string { 95 return RunKubectlOrDie("logs", podName, container, fmt.Sprintf("--namespace=%v", ns)) 96 }) 97 } 98 99 // LookForRegexp looks for the given regexp in results from given "func() string" 100 func LookForRegexp(expectedRegexp string, timeout time.Duration, fn func() string) (result string, err error) { 101 var expRegexp = regexp.MustCompile(expectedRegexp) 102 for t := time.Now(); time.Since(t) < timeout; time.Sleep(Poll) { 103 result = fn() 104 if expRegexp.MatchString(result) { 105 return 106 } 107 } 108 err = fmt.Errorf("Failed to find \"%s\", last result: \"%s\"", expectedRegexp, result) 109 return 110 } 111 112 // LookForRegexpInLog looks for the given regexp in the log of a specific pod container 113 func LookForRegexpInLog(ns, podName, container, expectedRegexp string, timeout time.Duration) (result string, err error) { 114 return LookForRegexp(expectedRegexp, timeout, func() string { 115 return RunKubectlOrDie("logs", podName, container, fmt.Sprintf("--namespace=%v", ns)) 116 }) 117 } 118 119 // KubectlBuilder is used to build, customize and execute a kubectl Command. 120 // Add more functions to customize the builder as needed. 121 type KubectlBuilder struct { 122 cmd *exec.Cmd 123 timeout <-chan time.Time 124 } 125 126 // NewKubectlCommand returns a KubectlBuilder for running kubectl. 127 func NewKubectlCommand(args ...string) *KubectlBuilder { 128 return NewKubectlCommandTimeout(Timeout, args...) 129 } 130 131 // NewKubectlCommandTimeout returns a KubectlBuilder with a timeout defined, for running kubectl. 132 func NewKubectlCommandTimeout(timeout time.Duration, args ...string) *KubectlBuilder { 133 b := new(KubectlBuilder) 134 b.cmd = KubectlCmd(args...) 135 b.timeout = time.After(timeout) 136 return b 137 } 138 139 // NewKubectlExecCommand returns a KubectlBuilder prepared to execute a given command in a running pod. 140 func NewKubectlExecCommand(f *Framework, pod string, timeout time.Duration, commandArgs ...string) *KubectlBuilder { 141 defaultArgs := []string{} 142 defaultArgs = append(defaultArgs, "--namespace", f.Namespace, "exec", pod, "--") 143 defaultArgs = append(defaultArgs, commandArgs...) 144 return NewKubectlCommandTimeout(timeout, defaultArgs...) 145 } 146 147 // ExecOrDie runs the kubectl executable or dies if error occurs. 148 func (b KubectlBuilder) ExecOrDie() string { 149 str, err := b.Exec() 150 // In case of i/o timeout error, try talking to the apiserver again after 2s before dying. 151 // Note that we're still dying after retrying so that we can get visibility to triage it further. 152 if isTimeout(err) { 153 e2elog.Logf("Hit i/o timeout error, talking to the server 2s later to see if it's temporary.") 154 time.Sleep(2 * time.Second) 155 retryStr, retryErr := RunKubectl("version") 156 e2elog.Logf("stdout: %q", retryStr) 157 e2elog.Logf("err: %v", retryErr) 158 } 159 ExpectNoError(err) 160 return str 161 } 162 163 func isTimeout(err error) bool { 164 switch err := err.(type) { 165 case net.Error: 166 if err.Timeout() { 167 return true 168 } 169 case *url.Error: 170 if err, ok := err.Err.(net.Error); ok && err.Timeout() { 171 return true 172 } 173 } 174 return false 175 } 176 177 // Exec runs the kubectl executable. 178 func (b KubectlBuilder) Exec() (string, error) { 179 var stdout, stderr bytes.Buffer 180 cmd := b.cmd 181 cmd.Stdout, cmd.Stderr = &stdout, &stderr 182 183 //e2elog.Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately 184 if err := cmd.Start(); err != nil { 185 return "", fmt.Errorf("error starting %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err) 186 } 187 errCh := make(chan error, 1) 188 go func() { 189 errCh <- cmd.Wait() 190 }() 191 select { 192 case err := <-errCh: 193 if err != nil { 194 var rc = 127 195 if ee, ok := err.(*exec.ExitError); ok { 196 rc = int(ee.Sys().(syscall.WaitStatus).ExitStatus()) 197 e2elog.Logf("rc: %d", rc) 198 } 199 return "", uexec.CodeExitError{ 200 Err: fmt.Errorf("error running %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err), 201 Code: rc, 202 } 203 } 204 case <-b.timeout: 205 _ = b.cmd.Process.Kill() 206 return "", fmt.Errorf("timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v", cmd, cmd.Stdout, cmd.Stderr) 207 } 208 // Note: these help to debug 209 //e2elog.Logf("stderr: %q", stderr.String()) 210 //e2elog.Logf("stdout: %q", stdout.String()) 211 return stdout.String(), nil 212 } 213 214 // RunKubectlOrDie is a convenience wrapper over kubectlBuilder 215 func RunKubectlOrDie(args ...string) string { 216 return NewKubectlCommand(args...).ExecOrDie() 217 } 218 219 // RunKubectl is a convenience wrapper over kubectlBuilder 220 func RunKubectl(args ...string) (string, error) { 221 return NewKubectlCommand(args...).Exec() 222 }