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 }