github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/sidecar/run.go (about)

     1  /*
     2  Copyright 2018 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 sidecar
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/signal"
    26  	"path/filepath"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"syscall"
    31  	"time"
    32  
    33  	"github.com/fsnotify/fsnotify"
    34  	"github.com/sirupsen/logrus"
    35  	"k8s.io/test-infra/prow/pod-utils/downwardapi"
    36  
    37  	"k8s.io/test-infra/prow/pod-utils/gcs"
    38  )
    39  
    40  // Run will watch for the process being wrapped to exit
    41  // and then post the status of that process and any artifacts
    42  // to cloud storage.
    43  func (o Options) Run() error {
    44  	spec, err := downwardapi.ResolveSpecFromEnv()
    45  	if err != nil {
    46  		return fmt.Errorf("could not resolve job spec: %v", err)
    47  	}
    48  
    49  	// If we are being asked to terminate by the kubelet but we have
    50  	// NOT seen the test process exit cleanly, we need a to start
    51  	// uploading artifacts to GCS immediately. If we notice the process
    52  	// exit while doing this best-effort upload, we can race with the
    53  	// second upload but we can tolerate this as we'd rather get SOME
    54  	// data into GCS than attempt to cancel these uploads and get none.
    55  	interrupt := make(chan os.Signal)
    56  	signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
    57  	go func() {
    58  		select {
    59  		case s := <-interrupt:
    60  			logrus.Errorf("Received an interrupt: %s", s)
    61  			o.doUpload(spec, false, true)
    62  		}
    63  	}()
    64  
    65  	// Only start watching file events if the file doesn't exist
    66  	// If the file exists, it means the main process already completed.
    67  	if _, err := os.Stat(o.WrapperOptions.MarkerFile); os.IsNotExist(err) {
    68  		watcher, err := fsnotify.NewWatcher()
    69  		if err != nil {
    70  			return fmt.Errorf("could not begin fsnotify watch: %v", err)
    71  		}
    72  		defer watcher.Close()
    73  
    74  		ticker := time.NewTicker(30 * time.Second)
    75  		group := sync.WaitGroup{}
    76  		group.Add(1)
    77  		go func() {
    78  			defer group.Done()
    79  			for {
    80  				select {
    81  				case event := <-watcher.Events:
    82  					if event.Name == o.WrapperOptions.MarkerFile && event.Op&fsnotify.Create == fsnotify.Create {
    83  						return
    84  					}
    85  				case err := <-watcher.Errors:
    86  					logrus.WithError(err).Info("Encountered an error during fsnotify watch")
    87  				case <-ticker.C:
    88  					if _, err := os.Stat(o.WrapperOptions.MarkerFile); err == nil {
    89  						return
    90  					}
    91  				}
    92  			}
    93  		}()
    94  
    95  		dir := filepath.Dir(o.WrapperOptions.MarkerFile)
    96  		if err := watcher.Add(dir); err != nil {
    97  			return fmt.Errorf("could not add to fsnotify watch: %v", err)
    98  		}
    99  		group.Wait()
   100  		ticker.Stop()
   101  	}
   102  
   103  	// If we are being asked to terminate by the kubelet but we have
   104  	// seen the test process exit cleanly, we need a chance to upload
   105  	// artifacts to GCS. The only valid way for this program to exit
   106  	// after a SIGINT or SIGTERM in this situation is to finish]
   107  	// uploading, so we ignore the signals.
   108  	signal.Ignore(os.Interrupt, syscall.SIGTERM)
   109  
   110  	passed := false
   111  	aborted := false
   112  	returnCodeData, err := ioutil.ReadFile(o.WrapperOptions.MarkerFile)
   113  	if err != nil {
   114  		logrus.WithError(err).Warn("Could not read return code from marker file")
   115  	} else {
   116  		returnCode, err := strconv.Atoi(strings.TrimSpace(string(returnCodeData)))
   117  		if err != nil {
   118  			logrus.WithError(err).Warn("Failed to parse process return code")
   119  		}
   120  		passed = returnCode == 0 && err == nil
   121  		aborted = returnCode == 130
   122  	}
   123  
   124  	return o.doUpload(spec, passed, aborted)
   125  }
   126  
   127  func (o Options) doUpload(spec *downwardapi.JobSpec, passed, aborted bool) error {
   128  	uploadTargets := map[string]gcs.UploadFunc{
   129  		"build-log.txt": gcs.FileUpload(o.WrapperOptions.ProcessLog),
   130  	}
   131  	var result string
   132  	switch {
   133  	case passed:
   134  		result = "SUCCESS"
   135  	case aborted:
   136  		result = "ABORTED"
   137  	default:
   138  		result = "FAILURE"
   139  	}
   140  
   141  	finished := struct {
   142  		Timestamp int64  `json:"timestamp"`
   143  		Passed    bool   `json:"passed"`
   144  		Result    string `json:"result"`
   145  	}{
   146  		Timestamp: time.Now().Unix(),
   147  		Passed:    passed,
   148  		Result:    result,
   149  	}
   150  	finishedData, err := json.Marshal(&finished)
   151  	if err != nil {
   152  		logrus.WithError(err).Warn("Could not marshal finishing data")
   153  	} else {
   154  		uploadTargets["finished.json"] = gcs.DataUpload(bytes.NewBuffer(finishedData))
   155  	}
   156  
   157  	if err := o.GcsOptions.Run(spec, uploadTargets); err != nil {
   158  		return fmt.Errorf("failed to upload to GCS: %v", err)
   159  	}
   160  
   161  	return nil
   162  }