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 }