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

Side by Side Diff: server/config/caching/config_test.go

Issue 2573403002: server/config: Generic caching backend. (Closed)
Patch Set: Created 4 years 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
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 "encoding/hex"
9 "net/url"
10 "testing"
11 "time"
12
13 "github.com/luci/luci-go/common/config/impl/memory"
14 "github.com/luci/luci-go/server/config"
15 "github.com/luci/luci-go/server/config/testconfig"
16
17 "golang.org/x/net/context"
18
19 . "github.com/smartystreets/goconvey/convey"
20 )
21
22 type testCache struct {
23 data map[string][]byte
24 callback func(hit bool)
25 }
26
27 func (tc *testCache) Store(c context.Context, key string, expire time.Duration, value []byte) {
28 if tc.data == nil {
29 tc.data = map[string][]byte{}
30 }
31 tc.data[key] = value
32 }
33
34 func (tc *testCache) Retrieve(c context.Context, key string) []byte {
35 d, ok := tc.data[key]
36 if tc.callback != nil {
37 tc.callback(ok)
38 }
39 return d
40 }
41
42 func (tc *testCache) invalidate() {
43 tc.data = nil
44 }
45
46 func (tc *testCache) nothingCached() bool {
47 return len(tc.data) == 0
48 }
49
50 type testingBackend struct {
51 config.Backend
52
53 getContentCalls int
54 getNoContentCalls int
55
56 err error
57 }
58
59 func (b *testingBackend) Get(c context.Context, configSet, path string, p config .Params) (*config.Item, error) {
60 if p.Content {
61 b.getContentCalls++
62 } else {
63 b.getNoContentCalls++
64 }
65
66 if b.err != nil {
67 return nil, b.err
68 }
69 return b.Backend.Get(c, configSet, path, p)
70 }
71
72 func (b *testingBackend) GetAll(c context.Context, t config.GetAllType, path str ing, p config.Params) ([]*config.Item, error) {
73 if p.Content {
74 b.getContentCalls++
75 } else {
76 b.getNoContentCalls++
77 }
78
79 if b.err != nil {
80 return nil, b.err
81 }
82 return b.Backend.GetAll(c, t, path, p)
83 }
84
85 func (b *testingBackend) ConfigSetURL(c context.Context, a config.Authority, con figSet string) (url.URL, error) {
86 b.getContentCalls++
87
88 if b.err != nil {
89 return url.URL{}, b.err
90 }
91 return b.Backend.ConfigSetURL(c, a, configSet)
92 }
93
94 func (b *testingBackend) reset() {
95 b.getContentCalls = 0
96 b.getNoContentCalls = 0
97 }
98
99 func TestConfig(t *testing.T) {
100 t.Parallel()
101
102 Convey(`A cache backed by a memory Config`, t, func() {
103 c := context.Background()
104
105 // Very simple cache.
106 var cache map[string][]byte
107 flushCache := func() {
108 cache = make(map[string][]byte)
109 }
110 flushCache()
111
112 mbase := map[string]memory.ConfigSet{
113 "services/foo": {
114 "file": "body",
115 },
116 "projects/proj1": {
117 "file": "project1 file",
118 },
119 "projects/goesaway": {
120 "file": "goesaway file",
121 },
122 "projects/goesaway/refs/heads/master": {
123 "file": "goesaway master ref",
124 },
125 "projects/goesaway/refs/heads/other": {
126 "file": "goesaway other ref",
127 },
128 }
129 mconfig := memory.New(mbase)
130
131 // Install our backend: memory backed by cache backed by force e rror.
132 //
133 // Cache => Testing => In-Memory
134 var backend config.Backend
135 backend = &config.ClientBackend{
136 Provider: &testconfig.LocalClientProvider{
137 Base: mconfig,
138 },
139 }
140 tb := testingBackend{Backend: backend}
141 backend = &tb
142
143 metaFor := func(configSet, path string) *config.Meta {
144 cfg, err := mconfig.GetConfig(c, configSet, path, false)
145 if err != nil {
146 panic(err)
147 }
148 return &config.Meta{
149 ConfigSet: cfg.ConfigSet,
150 Path: cfg.Path,
151 ContentHash: cfg.ContentHash,
152 Revision: cfg.Revision,
153 }
154 }
155
156 var expired bool
157 backend = &Backend{
158 Backend: backend,
159 CacheGet: func(c context.Context, k Key, l Loader) (*Val ue, error) {
160 cacheKey := hex.EncodeToString(k.ParamHash())
161
162 var v *Value
163 if d, ok := cache[cacheKey]; ok {
164 dv, err := DecodeValue(d)
165 if err != nil {
166 return nil, err
167 }
168 if !expired {
169 return dv, nil
170 }
171
172 v = dv
173 }
174
175 v, err := l(c, k, v)
176 if err != nil {
177 return nil, err
178 }
179 d, err := v.Encode()
180 if err != nil {
181 panic(err)
182 }
183 cache[cacheKey] = d
184 return v, nil
185 },
186 }
187
188 c = config.WithBackend(c, backend)
189
190 // Advance underlying config, expectation.
191 advance := func() {
192 mbase["services/foo"]["file"] = "body2"
193 mbase["services/foo"]["late"] = "late config"
194 mbase["projects/showsup"] = memory.ConfigSet{
195 "file": "shows up",
196 }
197 delete(mbase, "projects/goesaway")
198 delete(mbase, "projects/goesaway/refs/heads/master")
199 delete(mbase, "projects/goesaway/refs/heads/other")
200 }
201
202 Convey(`Get`, func() {
203 Convey(`Get works, caches, invalidates.`, func() {
204 var s string
205 So(config.Get(c, config.AsService, "services/foo ", "file", config.String(&s), nil), ShouldBeNil)
206 So(tb.getNoContentCalls, ShouldEqual, 0)
207 So(tb.getContentCalls, ShouldEqual, 1)
208 So(s, ShouldEqual, "body")
209
210 // The value should now be cached.
211 s = ""
212 So(config.Get(c, config.AsService, "services/foo ", "file", config.String(&s), nil), ShouldBeNil)
213 So(tb.getNoContentCalls, ShouldEqual, 0)
214 So(tb.getContentCalls, ShouldEqual, 1) // (Uncha nged)
215 So(s, ShouldEqual, "body")
216
217 // Expire content. Should do one Get w/out conte nt, see no change, and
218 // be done.
219 expired = true
220 So(config.Get(c, config.AsService, "services/foo ", "file", config.String(&s), nil), ShouldBeNil)
221 So(tb.getNoContentCalls, ShouldEqual, 1)
222 So(tb.getContentCalls, ShouldEqual, 1) // (Uncha nged)
223 So(s, ShouldEqual, "body")
224
225 // Backing config changes, but not expired.
226 advance()
227 expired = false
228
229 So(config.Get(c, config.AsService, "services/foo ", "file", config.String(&s), nil), ShouldBeNil)
230 So(tb.getNoContentCalls, ShouldEqual, 1) // (Unc hanged)
231 So(tb.getContentCalls, ShouldEqual, 1) // (Unc hanged)
232 So(s, ShouldEqual, "body") // Real one is "body2", but we load from cache.
233
234 // Expire local config, does full reload on hash difference.
235 expired = true
236 So(config.Get(c, config.AsService, "services/foo ", "file", config.String(&s), nil), ShouldBeNil)
237 So(tb.getNoContentCalls, ShouldEqual, 2)
238 So(tb.getContentCalls, ShouldEqual, 2)
239 So(s, ShouldEqual, "body2")
240 })
241
242 Convey(`Get w/ missing entry caches the miss.`, func() {
243 // Get missing entry.
244 var s string
245 So(config.Get(c, config.AsService, "services/foo ", "late", config.String(&s), nil), ShouldEqual, config.ErrNoConfig)
246 So(tb.getNoContentCalls, ShouldEqual, 0)
247 So(tb.getContentCalls, ShouldEqual, 1)
248
249 // Entry is still gone (cached).
250 So(config.Get(c, config.AsService, "services/foo ", "late", config.String(&s), nil), ShouldEqual, config.ErrNoConfig)
251 So(tb.getNoContentCalls, ShouldEqual, 0) // (Unc hanged)
252 So(tb.getContentCalls, ShouldEqual, 1) // (Unc hanged)
253
254 // Entry comes into existence, but still cached as gone.
255 advance()
256
257 So(config.Get(c, config.AsService, "services/foo ", "late", config.String(&s), nil), ShouldEqual, config.ErrNoConfig)
258 So(tb.getNoContentCalls, ShouldEqual, 0) // (Unc hanged)
259 So(tb.getContentCalls, ShouldEqual, 1) // (Unc hanged)
260
261 // Cache expires, entry content is loaded.
262 expired = true
263 So(config.Get(c, config.AsService, "services/foo ", "late", config.String(&s), nil), ShouldBeNil)
264 So(tb.getNoContentCalls, ShouldEqual, 0) // (Unc hanged)
265 So(tb.getContentCalls, ShouldEqual, 2)
266 So(s, ShouldEqual, "late config")
267
268 // Entry disappears, re-caches as missing throug h no content load.
269 expired = true
270 delete(mbase["services/foo"], "late")
271
272 So(config.Get(c, config.AsService, "services/foo ", "late", config.String(&s), nil), ShouldEqual, config.ErrNoConfig)
273 So(tb.getNoContentCalls, ShouldEqual, 1)
274 So(tb.getContentCalls, ShouldEqual, 2) // (Uncha nged)
275 })
276 })
277
278 Convey(`GetAll`, func() {
279 Convey(`Successfully loads, caches, refreshes projects.` , func() {
280 origMetas := []*config.Meta{
281 metaFor("projects/goesaway", "file"),
282 metaFor("projects/proj1", "file"),
283 }
284
285 // Load all successfully.
286 var s []string
287 var meta []*config.Meta
288 So(config.GetAll(c, config.AsService, config.Pro ject, "file", config.StringSlice(&s), &meta), ShouldBeNil)
289 So(s, ShouldResemble, []string{"goesaway file", "project1 file"})
290 So(meta, ShouldResemble, origMetas)
291 So(tb.getNoContentCalls, ShouldEqual, 0)
292 So(tb.getContentCalls, ShouldEqual, 1)
293
294 // Expire the cache, reloads, same entries, no c ontent only.
295 expired = true
296 So(config.GetAll(c, config.AsService, config.Pro ject, "file", config.StringSlice(&s), &meta), ShouldBeNil)
297 So(s, ShouldResemble, []string{"goesaway file", "project1 file"})
298 So(meta, ShouldResemble, origMetas)
299 So(tb.getNoContentCalls, ShouldEqual, 1)
300 So(tb.getContentCalls, ShouldEqual, 1) // (Uncha nged)
301
302 // Advance, "projects/goesaway" goes away, still loads all successfully
303 // (cache).
304 expired = false
305 advance()
306
307 So(config.GetAll(c, config.AsService, config.Pro ject, "file", config.StringSlice(&s), &meta), ShouldBeNil)
308 So(s, ShouldResemble, []string{"goesaway file", "project1 file"})
309 So(meta, ShouldResemble, origMetas)
310 So(tb.getNoContentCalls, ShouldEqual, 1) // (Unc hanged)
311 So(tb.getContentCalls, ShouldEqual, 1) // (Unc hanged)
312
313 // Expire the cache, reloads, notices missing en try (count same), reloads.
314 expired = true
315
316 So(config.GetAll(c, config.AsService, config.Pro ject, "file", config.StringSlice(&s), &meta), ShouldBeNil)
317 So(s, ShouldResemble, []string{"project1 file", "shows up"})
318 So(meta, ShouldResemble, []*config.Meta{
319 metaFor("projects/proj1", "file"),
320 metaFor("projects/showsup", "file"),
321 })
322 So(tb.getNoContentCalls, ShouldEqual, 2)
323 So(tb.getContentCalls, ShouldEqual, 2)
324
325 // Expire the cache, reloads, notices missing en try (count differs),
326 // reloads.
327 delete(mbase, "projects/showsup")
328 expired = true
329
330 So(config.GetAll(c, config.AsService, config.Pro ject, "file", config.StringSlice(&s), &meta), ShouldBeNil)
331 So(s, ShouldResemble, []string{"project1 file"})
332 So(meta, ShouldResemble, []*config.Meta{
333 metaFor("projects/proj1", "file"),
334 })
335 So(tb.getNoContentCalls, ShouldEqual, 3)
336 So(tb.getContentCalls, ShouldEqual, 3)
337 })
338
339 Convey(`Works with refs too.`, func() {
340 origMetas := []*config.Meta{
341 metaFor("projects/goesaway/refs/heads/ma ster", "file"),
342 metaFor("projects/goesaway/refs/heads/ot her", "file"),
343 }
344
345 // Load all successfully.
346 var s []string
347 var meta []*config.Meta
348
349 So(config.GetAll(c, config.AsService, config.Ref , "file", config.StringSlice(&s), &meta), ShouldBeNil)
350 So(s, ShouldResemble, []string{"goesaway master ref", "goesaway other ref"})
351 So(meta, ShouldResemble, origMetas)
352 So(tb.getNoContentCalls, ShouldEqual, 0)
353 So(tb.getContentCalls, ShouldEqual, 1) // (Uncha nged)
354
355 // Delete project, entries still cached.
356 advance()
357
358 So(config.GetAll(c, config.AsService, config.Ref , "file", config.StringSlice(&s), &meta), ShouldBeNil)
359 So(s, ShouldResemble, []string{"goesaway master ref", "goesaway other ref"})
360 So(meta, ShouldResemble, origMetas)
361 So(tb.getNoContentCalls, ShouldEqual, 0)
362 So(tb.getContentCalls, ShouldEqual, 1) // (Uncha nged)
363
364 // Expire the cache, reloads, same entries, no c ontent only.
365 expired = true
366 So(config.GetAll(c, config.AsService, config.Ref , "file", config.StringSlice(&s), &meta), ShouldBeNil)
367 So(s, ShouldResemble, []string(nil))
368 So(tb.getNoContentCalls, ShouldEqual, 1)
369 So(tb.getContentCalls, ShouldEqual, 2) // (Uncha nged)
370 })
371
372 Convey(`Handles no entries.`, func() {
373 var s []string
374 So(config.GetAll(c, config.AsService, config.Ref , "none", config.StringSlice(&s), nil), ShouldBeNil)
375 So(s, ShouldResemble, []string(nil))
376 So(tb.getNoContentCalls, ShouldEqual, 0)
377 So(tb.getContentCalls, ShouldEqual, 1)
378 })
379 })
380
381 Convey(`GetConfigSetURL`, func() {
382 u, err := config.GetConfigSetURL(c, config.AsService, "p rojects/goesaway")
383 So(err, ShouldBeNil)
384 So(u, ShouldResemble, url.URL{Scheme: "https", Host: "ex ample.com", Path: "/fake-config/projects/goesaway"})
385 So(tb.getContentCalls, ShouldEqual, 1)
386
387 // Delete project, entries still cached.
388 advance()
389
390 u, err = config.GetConfigSetURL(c, config.AsService, "pr ojects/goesaway")
391 So(err, ShouldBeNil)
392 So(u, ShouldResemble, url.URL{Scheme: "https", Host: "ex ample.com", Path: "/fake-config/projects/goesaway"})
393 So(tb.getContentCalls, ShouldEqual, 1) // (Unchanged)
394
395 // Expire the cache, ErrNoConfig.
396 expired = true
397 _, err = config.GetConfigSetURL(c, config.AsService, "pr ojects/goesaway")
398 So(err, ShouldEqual, config.ErrNoConfig)
399 So(tb.getContentCalls, ShouldEqual, 2) // Reload on expi re.
400
401 // Retains "missing" cache entry.
402 expired = false
403 _, err = config.GetConfigSetURL(c, config.AsService, "pr ojects/goesaway")
404 So(err, ShouldEqual, config.ErrNoConfig)
405 So(tb.getContentCalls, ShouldEqual, 2) // (Unchanged)
406 })
407 })
408 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698