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  }