github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/kubetest/util.go (about)

     1  /*
     2  Copyright 2017 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 main
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  	"net/http"
    25  	"net/url"
    26  	"os"
    27  	"os/exec"
    28  	"os/signal"
    29  	"path/filepath"
    30  	"strings"
    31  	"sync"
    32  	"syscall"
    33  	"time"
    34  )
    35  
    36  var (
    37  	termLock    = new(sync.RWMutex)
    38  	terminated  = false
    39  	intLock     = new(sync.RWMutex)
    40  	interrupted = false
    41  )
    42  
    43  func isTerminated() bool {
    44  	termLock.RLock()
    45  	t := terminated
    46  	termLock.RUnlock()
    47  	return t
    48  }
    49  
    50  func isInterrupted() bool {
    51  	intLock.RLock()
    52  	i := interrupted
    53  	intLock.RUnlock()
    54  	return i
    55  }
    56  
    57  var httpTransport *http.Transport
    58  
    59  func init() {
    60  	httpTransport = new(http.Transport)
    61  	httpTransport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
    62  }
    63  
    64  // Returns $GOPATH/src/k8s.io/...
    65  func k8s(parts ...string) string {
    66  	p := []string{os.Getenv("GOPATH"), "src", "k8s.io"}
    67  	for _, a := range parts {
    68  		p = append(p, a)
    69  	}
    70  	return filepath.Join(p...)
    71  }
    72  
    73  // append(errs, err) if err != nil
    74  func appendError(errs []error, err error) []error {
    75  	if err != nil {
    76  		return append(errs, err)
    77  	}
    78  	return errs
    79  }
    80  
    81  // Returns $HOME/part/part/part
    82  func home(parts ...string) string {
    83  	p := []string{os.Getenv("HOME")}
    84  	for _, a := range parts {
    85  		p = append(p, a)
    86  	}
    87  	return filepath.Join(p...)
    88  }
    89  
    90  // export PATH=path:$PATH
    91  func insertPath(path string) error {
    92  	return os.Setenv("PATH", fmt.Sprintf("%v:%v", path, os.Getenv("PATH")))
    93  }
    94  
    95  // Essentially curl url | writer
    96  func httpRead(url string, writer io.Writer) error {
    97  	log.Printf("curl %s", url)
    98  	c := &http.Client{Transport: httpTransport}
    99  	r, err := c.Get(url)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	defer r.Body.Close()
   104  	if r.StatusCode >= 400 {
   105  		return fmt.Errorf("%v returned %d", url, r.StatusCode)
   106  	}
   107  	_, err = io.Copy(writer, r.Body)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	return nil
   112  }
   113  
   114  // return f(), adding junit xml testcase result for name
   115  func xmlWrap(name string, f func() error) error {
   116  	alreadyInterrupted := isInterrupted()
   117  	start := time.Now()
   118  	err := f()
   119  	duration := time.Since(start)
   120  	c := testCase{
   121  		Name:      name,
   122  		ClassName: "e2e.go",
   123  		Time:      duration.Seconds(),
   124  	}
   125  	if err == nil && !alreadyInterrupted && isInterrupted() {
   126  		err = fmt.Errorf("kubetest interrupted during step %s", name)
   127  	}
   128  	if err != nil {
   129  		if !alreadyInterrupted {
   130  			c.Failure = err.Error()
   131  		} else {
   132  			c.Skipped = err.Error()
   133  		}
   134  		suite.Failures++
   135  	}
   136  
   137  	suite.Cases = append(suite.Cases, c)
   138  	suite.Tests++
   139  	return err
   140  }
   141  
   142  // return cmd.Wait() and/or timing out.
   143  func finishRunning(cmd *exec.Cmd) error {
   144  	stepName := strings.Join(cmd.Args, " ")
   145  	if isTerminated() {
   146  		return fmt.Errorf("skipped %s (kubetest is terminated)", stepName)
   147  	}
   148  	if cmd.Stdout == nil && verbose {
   149  		cmd.Stdout = os.Stdout
   150  	}
   151  	if cmd.Stderr == nil && verbose {
   152  		cmd.Stderr = os.Stderr
   153  	}
   154  	log.Printf("Running: %v", stepName)
   155  	defer func(start time.Time) {
   156  		log.Printf("Step '%s' finished in %s", stepName, time.Since(start))
   157  	}(time.Now())
   158  
   159  	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
   160  	if err := cmd.Start(); err != nil {
   161  		return fmt.Errorf("error starting %v: %v", stepName, err)
   162  	}
   163  
   164  	finished := make(chan error)
   165  
   166  	sigChannel := make(chan os.Signal, 1)
   167  	signal.Notify(sigChannel, os.Interrupt)
   168  
   169  	go func() {
   170  		finished <- cmd.Wait()
   171  	}()
   172  
   173  	for {
   174  		select {
   175  		case <-sigChannel:
   176  			log.Printf("Killing %v(%v) after receiving signal", stepName, -cmd.Process.Pid)
   177  			if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
   178  				log.Printf("Failed to kill %v: %v", stepName, err)
   179  			}
   180  		case <-terminate.C:
   181  			termLock.Lock()
   182  			terminated = true
   183  			termLock.Unlock()
   184  			terminate.Reset(time.Duration(0)) // Kill subsequent processes immediately.
   185  			if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
   186  				log.Printf("Failed to kill %v: %v", stepName, err)
   187  			}
   188  			if err := cmd.Process.Kill(); err != nil {
   189  				log.Printf("Failed to terminate %s (terminated 15m after interrupt): %v", stepName, err)
   190  			}
   191  		case <-interrupt.C:
   192  			intLock.Lock()
   193  			interrupted = true
   194  			intLock.Unlock()
   195  			log.Printf("Interrupt after %s timeout during %s. Will terminate in another 15m", timeout, stepName)
   196  			terminate.Reset(15 * time.Minute)
   197  			if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGINT); err != nil {
   198  				log.Printf("Failed to interrupt %s. Will terminate immediately: %v", stepName, err)
   199  				syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM)
   200  				cmd.Process.Kill()
   201  			}
   202  		case err := <-finished:
   203  			if err != nil {
   204  				var suffix string
   205  				if isTerminated() {
   206  					suffix = " (terminated)"
   207  				} else if isInterrupted() {
   208  					suffix = " (interrupted)"
   209  				}
   210  				return fmt.Errorf("error during %s%s: %v", stepName, suffix, err)
   211  			}
   212  			return err
   213  		}
   214  	}
   215  }
   216  
   217  type cmdExecResult struct {
   218  	stepName string
   219  	output   string
   220  	execTime time.Duration
   221  	err      error
   222  }
   223  
   224  // execute a given command and send output and error via channel
   225  func executeParallelCommand(cmd *exec.Cmd, resChan chan cmdExecResult, termChan, intChan chan struct{}) {
   226  	stepName := strings.Join(cmd.Args, " ")
   227  	stdout := bytes.Buffer{}
   228  	cmd.Stdout = &stdout
   229  	cmd.Stderr = &stdout
   230  
   231  	start := time.Now()
   232  	log.Printf("Running: %v in parallel", stepName)
   233  
   234  	if isTerminated() {
   235  		resChan <- cmdExecResult{stepName: stepName, output: stdout.String(), execTime: time.Since(start), err: fmt.Errorf("skipped %s (kubetest is terminated)", stepName)}
   236  		return
   237  	}
   238  
   239  	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
   240  	if err := cmd.Start(); err != nil {
   241  		resChan <- cmdExecResult{stepName: stepName, output: stdout.String(), execTime: time.Since(start), err: fmt.Errorf("error starting %v: %v", stepName, err)}
   242  		return
   243  	}
   244  
   245  	finished := make(chan error)
   246  	go func() {
   247  		finished <- cmd.Wait()
   248  	}()
   249  
   250  	for {
   251  		select {
   252  		case err := <-finished:
   253  			if err != nil {
   254  				var suffix string
   255  				if isTerminated() {
   256  					suffix = " (terminated)"
   257  				} else if isInterrupted() {
   258  					suffix = " (interrupted)"
   259  				}
   260  				err = fmt.Errorf("error during %s%s: %v", stepName, suffix, err)
   261  			}
   262  			resChan <- cmdExecResult{stepName: stepName, output: stdout.String(), execTime: time.Since(start), err: err}
   263  			return
   264  
   265  		case <-termChan:
   266  			syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
   267  			if err := cmd.Process.Kill(); err != nil {
   268  				log.Printf("Failed to terminate %s (terminated 15m after interrupt): %v", strings.Join(cmd.Args, " "), err)
   269  			}
   270  
   271  		case <-intChan:
   272  			log.Printf("Interrupt after %s timeout during %s. Will terminate in another 15m", timeout, strings.Join(cmd.Args, " "))
   273  			if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGINT); err != nil {
   274  				log.Printf("Failed to interrupt %s. Will terminate immediately: %v", strings.Join(cmd.Args, " "), err)
   275  				syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM)
   276  				cmd.Process.Kill()
   277  			}
   278  		}
   279  	}
   280  }
   281  
   282  // execute multiple commands in parallel
   283  func finishRunningParallel(cmds ...*exec.Cmd) error {
   284  	var wg sync.WaitGroup
   285  	resultChan := make(chan cmdExecResult, len(cmds))
   286  	termChan := make(chan struct{}, len(cmds))
   287  	intChan := make(chan struct{}, len(cmds))
   288  
   289  	for _, cmd := range cmds {
   290  		wg.Add(1)
   291  		go func(cmd *exec.Cmd) {
   292  			defer wg.Done()
   293  			executeParallelCommand(cmd, resultChan, termChan, intChan)
   294  		}(cmd)
   295  	}
   296  
   297  	go func() {
   298  		wg.Wait()
   299  		close(resultChan)
   300  	}()
   301  
   302  	cmdFailed := false
   303  	for {
   304  		select {
   305  		case <-terminate.C:
   306  			termLock.Lock()
   307  			terminated = true
   308  			termLock.Unlock()
   309  			terminate.Reset(time.Duration(0))
   310  			select {
   311  			case <-termChan:
   312  			default:
   313  				close(termChan)
   314  			}
   315  
   316  		case <-interrupt.C:
   317  			intLock.Lock()
   318  			interrupted = true
   319  			intLock.Unlock()
   320  			terminate.Reset(15 * time.Minute)
   321  			close(intChan)
   322  
   323  		case result, ok := <-resultChan:
   324  			if !ok {
   325  				if cmdFailed {
   326  					return fmt.Errorf("one or more commands failed")
   327  				}
   328  				return nil
   329  			}
   330  			log.Print(result.output)
   331  			if result.err != nil {
   332  				cmdFailed = true
   333  			}
   334  			log.Printf("Step '%s' finished in %s", result.stepName, result.execTime)
   335  		}
   336  	}
   337  }
   338  
   339  // return exec.Command(cmd, args...) while calling .StdinPipe().WriteString(input)
   340  func inputCommand(input, cmd string, args ...string) (*exec.Cmd, error) {
   341  	c := exec.Command(cmd, args...)
   342  	w, e := c.StdinPipe()
   343  	if e != nil {
   344  		return nil, e
   345  	}
   346  	go func() {
   347  		if _, e = io.WriteString(w, input); e != nil {
   348  			log.Printf("Failed to write all %d chars to %s: %v", len(input), cmd, e)
   349  		}
   350  		if e = w.Close(); e != nil {
   351  			log.Printf("Failed to close stdin for %s: %v", cmd, e)
   352  		}
   353  	}()
   354  	return c, nil
   355  }
   356  
   357  // return cmd.Output(), potentially timing out in the process.
   358  func output(cmd *exec.Cmd) ([]byte, error) {
   359  	var stdout bytes.Buffer
   360  	cmd.Stdout = &stdout
   361  	err := finishRunning(cmd)
   362  	return stdout.Bytes(), err
   363  
   364  }
   365  
   366  // gs://foo and bar becomes gs://foo/bar
   367  func joinURL(urlPath, path string) (string, error) {
   368  	u, err := url.Parse(urlPath)
   369  	if err != nil {
   370  		return "", err
   371  	}
   372  	u.Path = filepath.Join(u.Path, path)
   373  	return u.String(), nil
   374  }
   375  
   376  // Chdir() to dir and return a function to cd back to Getwd()
   377  func pushd(dir string) (func() error, error) {
   378  	old, err := os.Getwd()
   379  	if err != nil {
   380  		return nil, fmt.Errorf("failed to os.Getwd(): %v", err)
   381  	}
   382  	if err = os.Chdir(dir); err != nil {
   383  		return nil, err
   384  	}
   385  	return func() error {
   386  		return os.Chdir(old)
   387  	}, nil
   388  }
   389  
   390  // Push env=value and return a function that resets env
   391  func pushEnv(env, value string) (func() error, error) {
   392  	prev, present := os.LookupEnv(env)
   393  	if err := os.Setenv(env, value); err != nil {
   394  		return nil, fmt.Errorf("could not set %s: %v", env, err)
   395  	}
   396  	return func() error {
   397  		if present {
   398  			return os.Setenv(env, prev)
   399  		}
   400  		return os.Unsetenv(env)
   401  	}, nil
   402  }
   403  
   404  // Option that was an ENV that is now a --flag
   405  type migratedOption struct {
   406  	env      string  // env associated with --flag
   407  	option   *string // Value of --flag
   408  	name     string  // --flag name
   409  	skipPush bool    // Push option to env if false
   410  }
   411  
   412  // Read value from ENV if --flag unset, optionally pushing to ENV
   413  func migrateOptions(m []migratedOption) error {
   414  	for _, s := range m {
   415  		if *s.option == "" {
   416  			// Jobs may not be using --foo instead of FOO just yet, so ease the transition
   417  			// TODO(fejta): require --foo instead of FOO
   418  			v := os.Getenv(s.env) // expected Getenv
   419  			if v != "" {
   420  				// Tell people to use --foo=blah instead of FOO=blah
   421  				log.Printf("Please use kubetest %s=%s (instead of deprecated %s=%s)", s.name, v, s.env, v)
   422  				*s.option = v
   423  			}
   424  		}
   425  		if s.skipPush {
   426  			continue
   427  		}
   428  		// Script called by kubetest may expect these values to be set, so set them
   429  		// TODO(fejta): refactor the scripts below kubetest to use explicit config
   430  		if *s.option == "" {
   431  			continue
   432  		}
   433  		if err := os.Setenv(s.env, *s.option); err != nil {
   434  			return fmt.Errorf("could not set %s=%s: %v", s.env, *s.option, err)
   435  		}
   436  	}
   437  	return nil
   438  }
   439  
   440  func appendField(fields []string, flag, prefix string) []string {
   441  	fields, cur, _ := extractField(fields, flag)
   442  	if len(cur) == 0 {
   443  		cur = prefix
   444  	} else {
   445  		cur = cur + "-" + prefix
   446  	}
   447  	return append(fields, flag+"="+cur)
   448  }
   449  
   450  func setFieldDefault(fields []string, flag, val string) []string {
   451  	fields, cur, present := extractField(fields, flag)
   452  	if !present {
   453  		cur = val
   454  	}
   455  	return append(fields, flag+"="+cur)
   456  }
   457  
   458  // extractField("--a=this --b=that --c=other", "--b") returns [--a=this, --c=other"], "that"
   459  func extractField(fields []string, target string) ([]string, string, bool) {
   460  	f := []string{}
   461  	prefix := target + "="
   462  	consumeNext := false
   463  	done := false
   464  	r := ""
   465  	for _, field := range fields {
   466  		switch {
   467  		case done:
   468  			f = append(f, field)
   469  		case consumeNext:
   470  			r = field
   471  			done = true
   472  		case field == target:
   473  			consumeNext = true
   474  		case strings.HasPrefix(field, prefix):
   475  			r = strings.SplitN(field, "=", 2)[1]
   476  			done = true
   477  		default:
   478  			f = append(f, field)
   479  		}
   480  	}
   481  	return f, r, done
   482  }
   483  
   484  // execError returns a string format of err including stderr if the
   485  // err is an ExitError, useful for errors from e.g. exec.Cmd.Output().
   486  func execError(err error) string {
   487  	if ee, ok := err.(*exec.ExitError); ok {
   488  		return fmt.Sprintf("%v (output: %q)", err, string(ee.Stderr))
   489  	}
   490  	return err.Error()
   491  }