Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: common/config/filters/caching/config.go

Issue 2575383002: Add server/cache support to gaeconfig. (Closed)
Patch Set: Un-collapse. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « common/config/context.go ('k') | common/config/filters/caching/config_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package caching
6
7 import (
8 "bytes"
9 "compress/zlib"
10 "encoding/json"
11 "errors"
12 "io/ioutil"
13 "net/url"
14 "strings"
15 "time"
16
17 "github.com/luci/luci-go/common/config"
18 "golang.org/x/net/context"
19 )
20
21 const (
22 version = "v2"
23 )
24
25 var errCacheMiss = errors.New("cache miss")
26
27 type contentKey byte
28
29 const (
30 contentHit = contentKey(iota)
31 contentErrNoConfig
32 )
33
34 // Cache implements a generic caching layer.
35 //
36 // The layer has no consistency expectations.
37 type Cache interface {
38 // Store adds data to the cache. If value is nil, the cache should inval idate
39 // the supplied key.
40 Store(c context.Context, key string, expire time.Duration, value []byte)
41
42 // Retrieve pulls data from the cache. If no data is available, nil will be
43 // returned with no error.
44 Retrieve(c context.Context, key string) []byte
45 }
46
47 // Options is the set of configuration options for the caching layer.
48 type Options struct {
49 // Cache is the caching layer to use.
50 Cache Cache
51
52 // Expiration is the maximum amount of time that the configuration shoul d be
53 // retained before it must be refreshed.
54 //
55 // Due to implementation details of the cache layer, the configuration m ay be
56 // retained for less time if necessary.
57 Expiration time.Duration
58 }
59
60 // Wrap returns Interface object that adds caching layer on top of given one.
61 func Wrap(cc config.Interface, o Options) config.Interface {
62 return &cacheConfig{
63 opts: o,
64 inner: cc,
65 }
66 }
67
68 // cacheConfig implements a config.Interface that caches results in MemCache.
69 type cacheConfig struct {
70 opts Options
71 inner config.Interface
72 }
73
74 func (cc *cacheConfig) ServiceURL(ctx context.Context) url.URL {
75 return cc.inner.ServiceURL(ctx)
76 }
77
78 func (cc *cacheConfig) GetConfig(ctx context.Context, configSet, path string, ha shOnly bool) (*config.Config, error) {
79 // If we're doing hash-only lookup, we're okay with either full or hash- only
80 // result. However, if we have to do the lookup, we will store the resul t in
81 // a hash-only cache bucket.
82 c := config.Config{}
83 key := cc.cacheKey("configs", "full", configSet, path)
84 switch err := cc.retrieve(ctx, key, &c); err {
85 case nil:
86 // Cache hit.
87 return &c, nil
88 case errCacheMiss:
89 // Cache miss, load from service.
90 break
91 default:
92 // Return cached error.
93 return nil, err
94 }
95
96 if hashOnly {
97 key = cc.cacheKey("configs", "hashOnly", configSet, path)
98 switch err := cc.retrieve(ctx, key, &c); err {
99 case nil:
100 // Cache hit.
101 return &c, nil
102 case errCacheMiss:
103 // Cache miss, load from service.
104 break
105 default:
106 // Return cached error.
107 return nil, err
108 }
109 }
110
111 ic, err := cc.inner.GetConfig(ctx, configSet, path, hashOnly)
112 if err != nil {
113 cc.storeErr(ctx, key, err)
114 return nil, err
115 }
116
117 cc.store(ctx, key, ic)
118 if !hashOnly {
119 cc.store(ctx, cc.configByHashCacheKey(ic.ContentHash), ic.Conten t)
120 }
121 return ic, nil
122 }
123
124 func (cc *cacheConfig) GetConfigByHash(ctx context.Context, contentHash string) (string, error) {
125 c := ""
126 key := cc.configByHashCacheKey(contentHash)
127 switch err := cc.retrieve(ctx, key, &c); err {
128 case nil:
129 // Cache hit.
130 return c, nil
131 case errCacheMiss:
132 // Cache miss, load from service.
133 break
134 default:
135 // Return cached error.
136 return "", err
137 }
138
139 c, err := cc.inner.GetConfigByHash(ctx, contentHash)
140 if err != nil {
141 cc.storeErr(ctx, key, err)
142 return "", err
143 }
144
145 cc.store(ctx, key, c)
146 return c, nil
147 }
148
149 func (cc *cacheConfig) configByHashCacheKey(contentHash string) string {
150 return cc.cacheKey("configsByHash", contentHash)
151 }
152
153 func (cc *cacheConfig) GetConfigSetLocation(ctx context.Context, configSet strin g) (*url.URL, error) {
154 v := ""
155 key := cc.cacheKey("configSet", "location", configSet)
156 switch err := cc.retrieve(ctx, key, &v); err {
157 case nil:
158 // Cache hit.
159 u, err := url.Parse(v)
160 if err != nil {
161 return nil, err
162 }
163 return u, nil
164
165 case errCacheMiss:
166 // Cache miss, load from service.
167 break
168 default:
169 // Return cached error.
170 return nil, err
171 }
172
173 u, err := cc.inner.GetConfigSetLocation(ctx, configSet)
174 if err != nil {
175 cc.storeErr(ctx, key, err)
176 return nil, err
177 }
178
179 cc.store(ctx, key, u.String())
180 return u, nil
181 }
182
183 func (cc *cacheConfig) GetProjectConfigs(ctx context.Context, path string, hashe sOnly bool) ([]config.Config, error) {
184 var c []config.Config
185 key := cc.cacheKey("projectConfigs", "full", path)
186 switch err := cc.retrieve(ctx, key, &c); err {
187 case nil:
188 // Cache hit.
189 return c, nil
190 case errCacheMiss:
191 // Cache miss, load from service.
192 break
193 default:
194 // Return cached error.
195 return nil, err
196 }
197
198 if hashesOnly {
199 key = cc.cacheKey("projectConfigs", "hashesOnly", path)
200 }
201
202 c, err := cc.inner.GetProjectConfigs(ctx, path, hashesOnly)
203 if err != nil {
204 cc.storeErr(ctx, key, err)
205 return nil, err
206 }
207
208 cc.store(ctx, key, c)
209 return c, nil
210 }
211
212 func (cc *cacheConfig) GetProjects(ctx context.Context) ([]config.Project, error ) {
213 p := []config.Project(nil)
214 key := cc.cacheKey("projects")
215 switch err := cc.retrieve(ctx, key, &p); err {
216 case nil:
217 // Cache hit.
218 return p, nil
219 case errCacheMiss:
220 // Cache miss, load from service.
221 break
222 default:
223 // Return cached error.
224 return nil, err
225 }
226
227 p, err := cc.inner.GetProjects(ctx)
228 if err != nil {
229 cc.storeErr(ctx, key, err)
230 return nil, err
231 }
232
233 cc.store(ctx, key, p)
234 return p, nil
235 }
236
237 func (cc *cacheConfig) GetRefConfigs(ctx context.Context, path string, hashesOnl y bool) ([]config.Config, error) {
238 c := []config.Config(nil)
239 key := cc.cacheKey("refConfigs", "full", path)
240 switch err := cc.retrieve(ctx, key, &c); err {
241 case nil:
242 // Cache hit.
243 return c, nil
244 case errCacheMiss:
245 // Cache miss, load from service.
246 break
247 default:
248 // Return cached error.
249 return nil, err
250 }
251
252 if hashesOnly {
253 key = cc.cacheKey("refConfigs", "hashesOnly", path)
254 }
255
256 c, err := cc.inner.GetRefConfigs(ctx, path, hashesOnly)
257 if err != nil {
258 cc.storeErr(ctx, key, err)
259 return nil, err
260 }
261
262 cc.store(ctx, key, c)
263 return c, nil
264 }
265
266 func (cc *cacheConfig) GetRefs(ctx context.Context, projectID string) ([]string, error) {
267 var refs []string
268 key := cc.cacheKey("refs", projectID)
269 switch err := cc.retrieve(ctx, key, &refs); err {
270 case nil:
271 // Cache hit.
272 return refs, nil
273 case errCacheMiss:
274 // Cache miss, load from service.
275 break
276 default:
277 // Return cached error.
278 return nil, err
279 }
280
281 refs, err := cc.inner.GetRefs(ctx, projectID)
282 if err != nil {
283 cc.storeErr(ctx, key, err)
284 return nil, err
285 }
286
287 cc.store(ctx, key, refs)
288 return refs, nil
289 }
290
291 func (cc *cacheConfig) retrieve(ctx context.Context, key string, v interface{}) error {
292 if cc.opts.Cache == nil {
293 return errCacheMiss
294 }
295
296 // Load the cache value.
297 d := cc.opts.Cache.Retrieve(ctx, key)
298 if len(d) == 0 {
299 return errCacheMiss
300 }
301
302 // Handle the content key.
303 switch contentKey(d[0]) {
304 case contentHit:
305 d = d[1:]
306 break
307
308 case contentErrNoConfig:
309 return config.ErrNoConfig
310
311 default:
312 // Unknown content key, treat as cache miss.
313 return errCacheMiss
314 }
315
316 // Unzip.
317 zr, err := zlib.NewReader(bytes.NewBuffer(d))
318 if err != nil {
319 return errCacheMiss
320 }
321 defer zr.Close()
322
323 rd, err := ioutil.ReadAll(zr)
324 if err != nil {
325 return errCacheMiss
326 }
327
328 // Unpack.
329 if err := json.Unmarshal(rd, v); err != nil {
330 return errCacheMiss
331 }
332
333 return nil
334 }
335
336 func (cc *cacheConfig) store(ctx context.Context, key string, v interface{}) {
337 if cc.opts.Cache == nil {
338 return
339 }
340
341 // Convert "v" to zlib-compressed JSON.
342 d, err := json.Marshal(v)
343 if err != nil {
344 return
345 }
346
347 // Write a "content hit" record.
348 buf := bytes.Buffer{}
349 buf.WriteByte(byte(contentHit))
350 w := zlib.NewWriter(&buf)
351 _, err = w.Write(d)
352 if err != nil {
353 w.Close()
354 return
355 }
356 if err := w.Close(); err != nil {
357 return
358 }
359
360 cc.opts.Cache.Store(ctx, key, cc.opts.Expiration, buf.Bytes())
361 }
362
363 func (cc *cacheConfig) storeErr(ctx context.Context, key string, err error) {
364 if cc.opts.Cache == nil {
365 return
366 }
367
368 switch err {
369 case config.ErrNoConfig:
370 cc.opts.Cache.Store(ctx, key, cc.opts.Expiration, []byte{byte(co ntentErrNoConfig)})
371
372 default:
373 // Don't know how to store this error type.
374 return
375 }
376 }
377
378 // cacheKey constructs a cache key from a set of value segments.
379 //
380 // In order to ensure that segments remain distinct in the resulting key, each
381 // segment is URL query escaped, and segments are joined by the non-escaped
382 // character, "|".
383 //
384 // For example, ["a|b", "c"] => "a%7Cb|c"
385 func (cc *cacheConfig) cacheKey(values ...string) string {
386 enc := url.QueryEscape
387 parts := make([]string, 0, len(values)+1)
388 parts = append(parts, enc(version))
389 for _, v := range values {
390 parts = append(parts, enc(v))
391 }
392
393 return strings.Join(parts, "|")
394 }
OLDNEW
« no previous file with comments | « common/config/context.go ('k') | common/config/filters/caching/config_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698