github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/cmd/tot/main.go (about) 1 /* 2 Copyright 2016 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 // Tot vends (rations) incrementing numbers for use in builds. 18 // https://en.wikipedia.org/wiki/Rum_ration 19 package main 20 21 import ( 22 "encoding/json" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "net/http" 27 "os" 28 "strconv" 29 "strings" 30 "sync" 31 "time" 32 33 log "github.com/sirupsen/logrus" 34 ) 35 36 var ( 37 port = flag.Int("port", 8888, "port to listen on") 38 storagePath = flag.String("storage", "tot.json", "where to store the results") 39 40 // TODO(rmmh): remove this once we have no jobs running on Jenkins 41 useFallback = flag.Bool("fallback", false, "fallback to GCS bucket for missing builds") 42 fallbackURI = "https://storage.googleapis.com/kubernetes-jenkins/logs/%s/latest-build.txt" 43 ) 44 45 type store struct { 46 Number map[string]int // job name -> last vended build number 47 mutex sync.Mutex 48 storagePath string 49 fallbackFunc func(string) int 50 } 51 52 func newStore(storagePath string) (*store, error) { 53 s := &store{ 54 Number: make(map[string]int), 55 storagePath: storagePath, 56 } 57 buf, err := ioutil.ReadFile(storagePath) 58 if err == nil { 59 err = json.Unmarshal(buf, s) 60 if err != nil { 61 return nil, err 62 } 63 } else if !os.IsNotExist(err) { 64 return nil, err 65 } 66 return s, nil 67 } 68 69 func (s *store) save() error { 70 buf, err := json.Marshal(s) 71 if err != nil { 72 return err 73 } 74 err = ioutil.WriteFile(s.storagePath+".tmp", buf, 0644) 75 if err != nil { 76 return err 77 } 78 return os.Rename(s.storagePath+".tmp", s.storagePath) 79 } 80 81 func (s *store) vend(b string) int { 82 s.mutex.Lock() 83 defer s.mutex.Unlock() 84 n, ok := s.Number[b] 85 if !ok && s.fallbackFunc != nil { 86 n = s.fallbackFunc(b) 87 } 88 n++ 89 90 s.Number[b] = n 91 92 err := s.save() 93 if err != nil { 94 log.Error(err) 95 } 96 97 return n 98 } 99 100 func (s *store) peek(b string) int { 101 s.mutex.Lock() 102 defer s.mutex.Unlock() 103 return s.Number[b] 104 } 105 106 func (s *store) set(b string, n int) { 107 s.mutex.Lock() 108 defer s.mutex.Unlock() 109 s.Number[b] = n 110 111 err := s.save() 112 if err != nil { 113 log.Error(err) 114 } 115 } 116 117 func (s *store) handle(w http.ResponseWriter, r *http.Request) { 118 b := r.URL.Path[len("/vend/"):] 119 switch r.Method { 120 case "GET": 121 n := s.vend(b) 122 log.Infof("Vending %s number %d to %s.", b, n, r.RemoteAddr) 123 fmt.Fprintf(w, "%d", n) 124 case "HEAD": 125 n := s.peek(b) 126 log.Infof("Peeking %s number %d to %s.", b, n, r.RemoteAddr) 127 fmt.Fprintf(w, "%d", n) 128 case "POST": 129 body, err := ioutil.ReadAll(r.Body) 130 if err != nil { 131 log.WithError(err).Error("Unable to read body.") 132 return 133 } 134 n, err := strconv.Atoi(string(body)) 135 if err != nil { 136 log.WithError(err).Error("Unable to parse number.") 137 return 138 } 139 log.Infof("Setting %s to %d from %s.", b, n, r.RemoteAddr) 140 s.set(b, n) 141 } 142 } 143 144 type fallbackHandler struct { 145 template string 146 } 147 148 func (f fallbackHandler) get(b string) int { 149 url := fmt.Sprintf(f.template, b) 150 151 var body []byte 152 153 for i := 0; i < 10; i++ { 154 resp, err := http.Get(url) 155 if err == nil { 156 defer resp.Body.Close() 157 if resp.StatusCode == http.StatusOK { 158 body, err = ioutil.ReadAll(resp.Body) 159 if err == nil { 160 break 161 } else { 162 log.WithError(err).Error("Failed to read response body.") 163 } 164 } 165 } else { 166 log.WithError(err).Errorf("Failed to GET %s.", url) 167 } 168 time.Sleep(2 * time.Second) 169 } 170 171 n, err := strconv.Atoi(strings.TrimSpace(string(body))) 172 if err != nil { 173 return 0 174 } 175 176 return n 177 } 178 179 func main() { 180 flag.Parse() 181 182 log.SetFormatter(&log.JSONFormatter{}) 183 184 s, err := newStore(*storagePath) 185 if err != nil { 186 log.WithError(err).Fatal("newStore failed") 187 } 188 189 if *useFallback { 190 s.fallbackFunc = fallbackHandler{fallbackURI}.get 191 } 192 193 http.HandleFunc("/vend/", s.handle) 194 195 log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*port), nil)) 196 }