github.com/uber/kraken@v0.1.4/tracker/originstore/store.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package originstore 15 16 import ( 17 "fmt" 18 "time" 19 20 "github.com/uber/kraken/core" 21 "github.com/uber/kraken/lib/hostlist" 22 "github.com/uber/kraken/origin/blobclient" 23 "github.com/uber/kraken/utils/dedup" 24 "github.com/uber/kraken/utils/errutil" 25 "github.com/uber/kraken/utils/log" 26 27 "github.com/andres-erbsen/clock" 28 ) 29 30 type allUnavailableError struct { 31 error 32 } 33 34 // Store is a local cache in front of the origin cluster which is resilient to 35 // origin unavailability. 36 type Store interface { 37 // GetOrigins returns all available origins seeding d. Returns error if all origins 38 // are unavailable. 39 GetOrigins(d core.Digest) ([]*core.PeerInfo, error) 40 } 41 42 type store struct { 43 config Config 44 origins hostlist.List 45 provider blobclient.Provider 46 locations *dedup.Limiter // Caches results for origin locations per digest. 47 peerContexts *dedup.Limiter // Caches results for individual origin peer contexts. 48 } 49 50 // New creates a new Store. 51 func New(config Config, clk clock.Clock, origins hostlist.List, provider blobclient.Provider) Store { 52 config.applyDefaults() 53 s := &store{ 54 config: config, 55 origins: origins, 56 provider: provider, 57 } 58 s.locations = dedup.NewLimiter(clk, &locations{s}) 59 s.peerContexts = dedup.NewLimiter(clk, &peerContexts{s}) 60 return s 61 } 62 63 func (s *store) GetOrigins(d core.Digest) ([]*core.PeerInfo, error) { 64 lr := s.locations.Run(d).(*locationsResult) 65 if lr.err != nil { 66 return nil, lr.err 67 } 68 69 var errs []error 70 var origins []*core.PeerInfo 71 for _, addr := range lr.addrs { 72 pcr := s.peerContexts.Run(addr).(*peerContextResult) 73 if pcr.err != nil { 74 errs = append(errs, pcr.err) 75 } else { 76 origins = append(origins, core.PeerInfoFromContext(pcr.pctx, true)) 77 } 78 } 79 if len(origins) == 0 { 80 return nil, allUnavailableError{fmt.Errorf("all origins unavailable: %s", errutil.Join(errs))} 81 } 82 return origins, nil 83 } 84 85 type locations struct { 86 store *store 87 } 88 89 type locationsResult struct { 90 addrs []string 91 err error 92 } 93 94 func (l *locations) Run(input interface{}) (interface{}, time.Duration) { 95 d := input.(core.Digest) 96 addrs, err := blobclient.Locations(l.store.provider, l.store.origins, d) 97 ttl := l.store.config.LocationsTTL 98 if err != nil { 99 ttl = l.store.config.LocationsErrorTTL 100 } 101 return &locationsResult{addrs, err}, ttl 102 } 103 104 type peerContexts struct { 105 store *store 106 } 107 108 type peerContextResult struct { 109 pctx core.PeerContext 110 err error 111 } 112 113 func (p *peerContexts) Run(input interface{}) (interface{}, time.Duration) { 114 addr := input.(string) 115 pctx, err := p.store.provider.Provide(addr).GetPeerContext() 116 ttl := p.store.config.OriginContextTTL 117 if err != nil { 118 log.With("origin", addr).Errorf("Origin unavailable: %s", err) 119 ttl = p.store.config.OriginUnavailableTTL 120 } 121 return &peerContextResult{pctx, err}, ttl 122 }