github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/deck/tide.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  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/sirupsen/logrus"
    28  
    29  	"k8s.io/test-infra/prow/config"
    30  	"k8s.io/test-infra/prow/errorutil"
    31  	"k8s.io/test-infra/prow/tide"
    32  	"k8s.io/test-infra/prow/tide/history"
    33  )
    34  
    35  type tidePools struct {
    36  	Queries     []string
    37  	TideQueries []config.TideQuery
    38  	Pools       []tide.Pool
    39  }
    40  
    41  type tideHistory struct {
    42  	History map[string][]history.Record
    43  }
    44  
    45  type tideAgent struct {
    46  	log          *logrus.Entry
    47  	path         string
    48  	updatePeriod func() time.Duration
    49  
    50  	// Config for hiding repos
    51  	hiddenRepos []string
    52  	hiddenOnly  bool
    53  
    54  	sync.Mutex
    55  	pools   []tide.Pool
    56  	history map[string][]history.Record
    57  }
    58  
    59  func (ta *tideAgent) start() {
    60  	startTimePool := time.Now()
    61  	if err := ta.updatePools(); err != nil {
    62  		ta.log.WithError(err).Error("Updating pools the first time.")
    63  	}
    64  	startTimeHistory := time.Now()
    65  	if err := ta.updateHistory(); err != nil {
    66  		ta.log.WithError(err).Error("Updating history the first time.")
    67  	}
    68  
    69  	go func() {
    70  		for {
    71  			time.Sleep(time.Until(startTimePool.Add(ta.updatePeriod())))
    72  			startTimePool = time.Now()
    73  			if err := ta.updatePools(); err != nil {
    74  				ta.log.WithError(err).Error("Updating pools.")
    75  			}
    76  		}
    77  	}()
    78  	go func() {
    79  		for {
    80  			time.Sleep(time.Until(startTimeHistory.Add(ta.updatePeriod())))
    81  			startTimeHistory = time.Now()
    82  			if err := ta.updateHistory(); err != nil {
    83  				ta.log.WithError(err).Error("Updating history.")
    84  			}
    85  		}
    86  	}()
    87  }
    88  
    89  func fetchTideData(log *logrus.Entry, path string, data interface{}) error {
    90  	var prevErrs []error
    91  	var err error
    92  	backoff := 5 * time.Second
    93  	for i := 0; i < 4; i++ {
    94  		var resp *http.Response
    95  		if err != nil {
    96  			prevErrs = append(prevErrs, err)
    97  			time.Sleep(backoff)
    98  			backoff *= 4
    99  		}
   100  		resp, err = http.Get(path)
   101  		if err == nil {
   102  			defer resp.Body.Close()
   103  			if resp.StatusCode < 200 || resp.StatusCode > 299 {
   104  				err = fmt.Errorf("response has status code %d", resp.StatusCode)
   105  				continue
   106  			}
   107  			if err = json.NewDecoder(resp.Body).Decode(data); err != nil {
   108  				break
   109  			}
   110  			break
   111  		}
   112  	}
   113  
   114  	// Either combine previous errors with the returned error, or if we succeeded
   115  	// warn once about any errors we saw before succeeding.
   116  	prevErr := errorutil.NewAggregate(prevErrs...)
   117  	if err != nil {
   118  		return errorutil.NewAggregate(err, prevErr)
   119  	}
   120  	if prevErr != nil {
   121  		log.WithError(prevErr).Warnf(
   122  			"Failed %d retries fetching Tide data before success: %v.",
   123  			len(prevErrs),
   124  			prevErr,
   125  		)
   126  	}
   127  	return nil
   128  }
   129  
   130  func (ta *tideAgent) updatePools() error {
   131  	var pools []tide.Pool
   132  	if err := fetchTideData(ta.log, ta.path, &pools); err != nil {
   133  		return err
   134  	}
   135  	pools = ta.filterHiddenPools(pools)
   136  
   137  	ta.Lock()
   138  	defer ta.Unlock()
   139  	ta.pools = pools
   140  	return nil
   141  }
   142  
   143  func (ta *tideAgent) updateHistory() error {
   144  	path := strings.TrimSuffix(ta.path, "/") + "/history"
   145  	var history map[string][]history.Record
   146  	if err := fetchTideData(ta.log, path, &history); err != nil {
   147  		return err
   148  	}
   149  	history = ta.filterHiddenHistory(history)
   150  
   151  	ta.Lock()
   152  	defer ta.Unlock()
   153  	ta.history = history
   154  	return nil
   155  }
   156  
   157  func (ta *tideAgent) filterHiddenPools(pools []tide.Pool) []tide.Pool {
   158  	if len(ta.hiddenRepos) == 0 {
   159  		return pools
   160  	}
   161  
   162  	filtered := make([]tide.Pool, 0, len(pools))
   163  	for _, pool := range pools {
   164  		needsHide := matches(pool.Org+"/"+pool.Repo, ta.hiddenRepos)
   165  		if needsHide == ta.hiddenOnly {
   166  			filtered = append(filtered, pool)
   167  		} else {
   168  			ta.log.Debugf("Ignoring pool for %s.", pool.Org+"/"+pool.Repo)
   169  		}
   170  	}
   171  	return filtered
   172  }
   173  
   174  func (ta *tideAgent) filterHiddenHistory(hist map[string][]history.Record) map[string][]history.Record {
   175  	if len(ta.hiddenRepos) == 0 {
   176  		return hist
   177  	}
   178  
   179  	filtered := make(map[string][]history.Record, len(hist))
   180  	for pool, records := range hist {
   181  		needsHide := matches(strings.Split(pool, ":")[0], ta.hiddenRepos)
   182  		if needsHide == ta.hiddenOnly {
   183  			filtered[pool] = records
   184  		} else {
   185  			ta.log.Debugf("Ignoring history for %s.", pool)
   186  		}
   187  	}
   188  	return filtered
   189  }
   190  
   191  func (ta *tideAgent) filterHiddenQueries(queries []config.TideQuery) []config.TideQuery {
   192  	if len(ta.hiddenRepos) == 0 {
   193  		return queries
   194  	}
   195  
   196  	filtered := make([]config.TideQuery, 0, len(queries))
   197  	for _, qc := range queries {
   198  		includesHidden := false
   199  		// This will exclude the query even if a single
   200  		// repo in the query is included in hiddenRepos.
   201  		for _, repo := range qc.Repos {
   202  			if matches(repo, ta.hiddenRepos) {
   203  				includesHidden = true
   204  				break
   205  			}
   206  		}
   207  		if includesHidden == ta.hiddenOnly {
   208  			filtered = append(filtered, qc)
   209  		} else {
   210  			ta.log.Debugf("Ignoring query: %s", qc.Query())
   211  		}
   212  	}
   213  	return filtered
   214  }
   215  
   216  // matches returns whether the provided repo intersects
   217  // with repos. repo has always the "org/repo" format but
   218  // repos can include both orgs and repos.
   219  func matches(repo string, repos []string) bool {
   220  	org := strings.Split(repo, "/")[0]
   221  	for _, r := range repos {
   222  		if r == repo || r == org {
   223  			return true
   224  		}
   225  	}
   226  	return false
   227  }