~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/store/store_test.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package store_test
 
2
 
 
3
import (
 
4
        "fmt"
 
5
        "io"
 
6
        "io/ioutil"
 
7
        "labix.org/v2/mgo/bson"
 
8
        . "launchpad.net/gocheck"
 
9
        "launchpad.net/juju-core/charm"
 
10
        "launchpad.net/juju-core/log"
 
11
        "launchpad.net/juju-core/store"
 
12
        "launchpad.net/juju-core/testing"
 
13
        "strconv"
 
14
        "sync"
 
15
        stdtesting "testing"
 
16
        "time"
 
17
)
 
18
 
 
19
func Test(t *stdtesting.T) {
 
20
        TestingT(t)
 
21
}
 
22
 
 
23
var _ = Suite(&StoreSuite{})
 
24
var _ = Suite(&TrivialSuite{})
 
25
 
 
26
type StoreSuite struct {
 
27
        MgoSuite
 
28
        testing.HTTPSuite
 
29
        store *store.Store
 
30
}
 
31
 
 
32
type TrivialSuite struct{}
 
33
 
 
34
func (s *StoreSuite) SetUpSuite(c *C) {
 
35
        s.MgoSuite.SetUpSuite(c)
 
36
        s.HTTPSuite.SetUpSuite(c)
 
37
}
 
38
 
 
39
func (s *StoreSuite) TearDownSuite(c *C) {
 
40
        s.HTTPSuite.TearDownSuite(c)
 
41
        s.MgoSuite.TearDownSuite(c)
 
42
}
 
43
 
 
44
func (s *StoreSuite) SetUpTest(c *C) {
 
45
        s.MgoSuite.SetUpTest(c)
 
46
        var err error
 
47
        s.store, err = store.Open(s.Addr)
 
48
        c.Assert(err, IsNil)
 
49
        log.SetTarget(c)
 
50
        log.Debug = true
 
51
}
 
52
 
 
53
func (s *StoreSuite) TearDownTest(c *C) {
 
54
        s.HTTPSuite.TearDownTest(c)
 
55
        if s.store != nil {
 
56
                s.store.Close()
 
57
        }
 
58
        s.MgoSuite.TearDownTest(c)
 
59
}
 
60
 
 
61
// FakeCharmDir is a charm that implements the interface that the
 
62
// store publisher cares about.
 
63
type FakeCharmDir struct {
 
64
        revision interface{} // so we can tell if it's not set.
 
65
        error    string
 
66
}
 
67
 
 
68
func (d *FakeCharmDir) Meta() *charm.Meta {
 
69
        return &charm.Meta{
 
70
                Name:        "fakecharm",
 
71
                Summary:     "Fake charm for testing purposes.",
 
72
                Description: "This is a fake charm for testing purposes.\n",
 
73
                Provides:    make(map[string]charm.Relation),
 
74
                Requires:    make(map[string]charm.Relation),
 
75
                Peers:       make(map[string]charm.Relation),
 
76
        }
 
77
}
 
78
 
 
79
func (d *FakeCharmDir) Config() *charm.Config {
 
80
        return &charm.Config{make(map[string]charm.Option)}
 
81
}
 
82
 
 
83
func (d *FakeCharmDir) SetRevision(revision int) {
 
84
        d.revision = revision
 
85
}
 
86
 
 
87
func (d *FakeCharmDir) BundleTo(w io.Writer) error {
 
88
        if d.error == "beforeWrite" {
 
89
                return fmt.Errorf(d.error)
 
90
        }
 
91
        _, err := w.Write([]byte(fmt.Sprintf("charm-revision-%v", d.revision)))
 
92
        if d.error == "afterWrite" {
 
93
                return fmt.Errorf(d.error)
 
94
        }
 
95
        return err
 
96
}
 
97
 
 
98
func (s *StoreSuite) TestCharmPublisherWithRevisionedURL(c *C) {
 
99
        urls := []*charm.URL{charm.MustParseURL("cs:oneiric/wordpress-0")}
 
100
        pub, err := s.store.CharmPublisher(urls, "some-digest")
 
101
        c.Assert(err, ErrorMatches, "CharmPublisher: got charm URL with revision: cs:oneiric/wordpress-0")
 
102
        c.Assert(pub, IsNil)
 
103
}
 
104
 
 
105
func (s *StoreSuite) TestCharmPublisher(c *C) {
 
106
        urlA := charm.MustParseURL("cs:oneiric/wordpress-a")
 
107
        urlB := charm.MustParseURL("cs:oneiric/wordpress-b")
 
108
        urls := []*charm.URL{urlA, urlB}
 
109
 
 
110
        pub, err := s.store.CharmPublisher(urls, "some-digest")
 
111
        c.Assert(err, IsNil)
 
112
        c.Assert(pub.Revision(), Equals, 0)
 
113
 
 
114
        err = pub.Publish(testing.Charms.ClonedDir(c.MkDir(), "dummy"))
 
115
        c.Assert(err, IsNil)
 
116
 
 
117
        for _, url := range urls {
 
118
                info, rc, err := s.store.OpenCharm(url)
 
119
                c.Assert(err, IsNil)
 
120
                c.Assert(info.Revision(), Equals, 0)
 
121
                c.Assert(info.Digest(), Equals, "some-digest")
 
122
                data, err := ioutil.ReadAll(rc)
 
123
                c.Check(err, IsNil)
 
124
                err = rc.Close()
 
125
                c.Assert(err, IsNil)
 
126
                bundle, err := charm.ReadBundleBytes(data)
 
127
                c.Assert(err, IsNil)
 
128
 
 
129
                // The same information must be available by reading the
 
130
                // full charm data...
 
131
                c.Assert(bundle.Meta().Name, Equals, "dummy")
 
132
                c.Assert(bundle.Config().Options["title"].Default, Equals, "My Title")
 
133
 
 
134
                // ... and the queriable details.
 
135
                c.Assert(info.Meta().Name, Equals, "dummy")
 
136
                c.Assert(info.Config().Options["title"].Default, Equals, "My Title")
 
137
 
 
138
                info2, err := s.store.CharmInfo(url)
 
139
                c.Assert(err, IsNil)
 
140
                c.Assert(info2, DeepEquals, info)
 
141
        }
 
142
}
 
143
 
 
144
func (s *StoreSuite) TestCharmPublishError(c *C) {
 
145
        url := charm.MustParseURL("cs:oneiric/wordpress")
 
146
        urls := []*charm.URL{url}
 
147
 
 
148
        // Publish one successfully to bump the revision so we can
 
149
        // make sure it is being correctly set below.
 
150
        pub, err := s.store.CharmPublisher(urls, "one-digest")
 
151
        c.Assert(err, IsNil)
 
152
        c.Assert(pub.Revision(), Equals, 0)
 
153
        err = pub.Publish(&FakeCharmDir{})
 
154
        c.Assert(err, IsNil)
 
155
 
 
156
        pub, err = s.store.CharmPublisher(urls, "another-digest")
 
157
        c.Assert(err, IsNil)
 
158
        c.Assert(pub.Revision(), Equals, 1)
 
159
        err = pub.Publish(&FakeCharmDir{error: "beforeWrite"})
 
160
        c.Assert(err, ErrorMatches, "beforeWrite")
 
161
 
 
162
        pub, err = s.store.CharmPublisher(urls, "another-digest")
 
163
        c.Assert(err, IsNil)
 
164
        c.Assert(pub.Revision(), Equals, 1)
 
165
        err = pub.Publish(&FakeCharmDir{error: "afterWrite"})
 
166
        c.Assert(err, ErrorMatches, "afterWrite")
 
167
 
 
168
        // Still at the original charm revision that succeeded first.
 
169
        info, err := s.store.CharmInfo(url)
 
170
        c.Assert(err, IsNil)
 
171
        c.Assert(info.Revision(), Equals, 0)
 
172
        c.Assert(info.Digest(), Equals, "one-digest")
 
173
}
 
174
 
 
175
func (s *StoreSuite) TestCharmInfoNotFound(c *C) {
 
176
        info, err := s.store.CharmInfo(charm.MustParseURL("cs:oneiric/wordpress"))
 
177
        c.Assert(err, Equals, store.ErrNotFound)
 
178
        c.Assert(info, IsNil)
 
179
}
 
180
 
 
181
func (s *StoreSuite) TestRevisioning(c *C) {
 
182
        urlA := charm.MustParseURL("cs:oneiric/wordpress-a")
 
183
        urlB := charm.MustParseURL("cs:oneiric/wordpress-b")
 
184
        urls := []*charm.URL{urlA, urlB}
 
185
 
 
186
        tests := []struct {
 
187
                urls []*charm.URL
 
188
                data string
 
189
        }{
 
190
                {urls[0:], "charm-revision-0"},
 
191
                {urls[1:], "charm-revision-1"},
 
192
                {urls[0:], "charm-revision-2"},
 
193
        }
 
194
 
 
195
        for i, t := range tests {
 
196
                pub, err := s.store.CharmPublisher(t.urls, fmt.Sprintf("digest-%d", i))
 
197
                c.Assert(err, IsNil)
 
198
                c.Assert(pub.Revision(), Equals, i)
 
199
 
 
200
                err = pub.Publish(&FakeCharmDir{})
 
201
                c.Assert(err, IsNil)
 
202
        }
 
203
 
 
204
        for i, t := range tests {
 
205
                for _, url := range t.urls {
 
206
                        url = url.WithRevision(i)
 
207
                        info, rc, err := s.store.OpenCharm(url)
 
208
                        c.Assert(err, IsNil)
 
209
                        data, err := ioutil.ReadAll(rc)
 
210
                        cerr := rc.Close()
 
211
                        c.Assert(info.Revision(), Equals, i)
 
212
                        c.Assert(url.Revision, Equals, i) // Untouched.
 
213
                        c.Assert(cerr, IsNil)
 
214
                        c.Assert(string(data), Equals, string(t.data))
 
215
                        c.Assert(err, IsNil)
 
216
                }
 
217
        }
 
218
 
 
219
        info, rc, err := s.store.OpenCharm(urlA.WithRevision(1))
 
220
        c.Assert(err, Equals, store.ErrNotFound)
 
221
        c.Assert(info, IsNil)
 
222
        c.Assert(rc, IsNil)
 
223
}
 
224
 
 
225
func (s *StoreSuite) TestLockUpdates(c *C) {
 
226
        urlA := charm.MustParseURL("cs:oneiric/wordpress-a")
 
227
        urlB := charm.MustParseURL("cs:oneiric/wordpress-b")
 
228
        urls := []*charm.URL{urlA, urlB}
 
229
 
 
230
        // Lock update of just B to force a partial conflict.
 
231
        lock1, err := s.store.LockUpdates(urls[1:])
 
232
        c.Assert(err, IsNil)
 
233
 
 
234
        // Partially conflicts with locked update above.
 
235
        lock2, err := s.store.LockUpdates(urls)
 
236
        c.Check(err, Equals, store.ErrUpdateConflict)
 
237
        c.Check(lock2, IsNil)
 
238
 
 
239
        lock1.Unlock()
 
240
 
 
241
        // Trying again should work since lock1 was released.
 
242
        lock3, err := s.store.LockUpdates(urls)
 
243
        c.Assert(err, IsNil)
 
244
        lock3.Unlock()
 
245
}
 
246
 
 
247
func (s *StoreSuite) TestLockUpdatesExpires(c *C) {
 
248
        urlA := charm.MustParseURL("cs:oneiric/wordpress-a")
 
249
        urlB := charm.MustParseURL("cs:oneiric/wordpress-b")
 
250
        urls := []*charm.URL{urlA, urlB}
 
251
 
 
252
        // Initiate an update of B only to force a partial conflict.
 
253
        lock1, err := s.store.LockUpdates(urls[1:])
 
254
        c.Assert(err, IsNil)
 
255
 
 
256
        // Hack time to force an expiration.
 
257
        locks := s.Session.DB("juju").C("locks")
 
258
        selector := bson.M{"_id": urlB.String()}
 
259
        update := bson.M{"time": bson.Now().Add(-store.UpdateTimeout - 10e9)}
 
260
        err = locks.Update(selector, update)
 
261
        c.Check(err, IsNil)
 
262
 
 
263
        // Works due to expiration of previous lock.
 
264
        lock2, err := s.store.LockUpdates(urls)
 
265
        c.Assert(err, IsNil)
 
266
        defer lock2.Unlock()
 
267
 
 
268
        // The expired lock was forcefully killed. Unlocking it must
 
269
        // not interfere with lock2 which is still alive.
 
270
        lock1.Unlock()
 
271
 
 
272
        // The above statement was a NOOP and lock2 is still in effect,
 
273
        // so attempting another lock must necessarily fail.
 
274
        lock3, err := s.store.LockUpdates(urls)
 
275
        c.Check(err == store.ErrUpdateConflict, Equals, true)
 
276
        c.Check(lock3, IsNil)
 
277
}
 
278
 
 
279
func (s *StoreSuite) TestConflictingUpdate(c *C) {
 
280
        // This test checks that if for whatever reason the locking
 
281
        // safety-net fails, adding two charms in parallel still
 
282
        // results in a sane outcome.
 
283
        url := charm.MustParseURL("cs:oneiric/wordpress")
 
284
        urls := []*charm.URL{url}
 
285
 
 
286
        pub1, err := s.store.CharmPublisher(urls, "some-digest")
 
287
        c.Assert(err, IsNil)
 
288
        c.Assert(pub1.Revision(), Equals, 0)
 
289
 
 
290
        pub2, err := s.store.CharmPublisher(urls, "some-digest")
 
291
        c.Assert(err, IsNil)
 
292
        c.Assert(pub2.Revision(), Equals, 0)
 
293
 
 
294
        // The first publishing attempt should work.
 
295
        err = pub2.Publish(&FakeCharmDir{})
 
296
        c.Assert(err, IsNil)
 
297
 
 
298
        // Attempting to finish the second attempt should break,
 
299
        // since it lost the race and the given revision is already
 
300
        // in place.
 
301
        err = pub1.Publish(&FakeCharmDir{})
 
302
        c.Assert(err, Equals, store.ErrUpdateConflict)
 
303
}
 
304
 
 
305
func (s *StoreSuite) TestRedundantUpdate(c *C) {
 
306
        urlA := charm.MustParseURL("cs:oneiric/wordpress-a")
 
307
        urlB := charm.MustParseURL("cs:oneiric/wordpress-b")
 
308
        urls := []*charm.URL{urlA, urlB}
 
309
 
 
310
        pub, err := s.store.CharmPublisher(urls, "digest-0")
 
311
        c.Assert(err, IsNil)
 
312
        c.Assert(pub.Revision(), Equals, 0)
 
313
        err = pub.Publish(&FakeCharmDir{})
 
314
        c.Assert(err, IsNil)
 
315
 
 
316
        // All charms are already on digest-0.
 
317
        pub, err = s.store.CharmPublisher(urls, "digest-0")
 
318
        c.Assert(err, ErrorMatches, "charm is up-to-date")
 
319
        c.Assert(err, Equals, store.ErrRedundantUpdate)
 
320
        c.Assert(pub, IsNil)
 
321
 
 
322
        // Now add a second revision just for wordpress-b.
 
323
        pub, err = s.store.CharmPublisher(urls[1:], "digest-1")
 
324
        c.Assert(err, IsNil)
 
325
        c.Assert(pub.Revision(), Equals, 1)
 
326
        err = pub.Publish(&FakeCharmDir{})
 
327
        c.Assert(err, IsNil)
 
328
 
 
329
        // Same digest bumps revision because one of them was old.
 
330
        pub, err = s.store.CharmPublisher(urls, "digest-1")
 
331
        c.Assert(err, IsNil)
 
332
        c.Assert(pub.Revision(), Equals, 2)
 
333
        err = pub.Publish(&FakeCharmDir{})
 
334
        c.Assert(err, IsNil)
 
335
}
 
336
 
 
337
const fakeRevZeroSha = "319095521ac8a62fa1e8423351973512ecca8928c9f62025e37de57c9ef07a53"
 
338
 
 
339
func (s *StoreSuite) TestCharmBundleData(c *C) {
 
340
        url := charm.MustParseURL("cs:oneiric/wordpress")
 
341
        urls := []*charm.URL{url}
 
342
 
 
343
        pub, err := s.store.CharmPublisher(urls, "key")
 
344
        c.Assert(err, IsNil)
 
345
        c.Assert(pub.Revision(), Equals, 0)
 
346
 
 
347
        err = pub.Publish(&FakeCharmDir{})
 
348
        c.Assert(err, IsNil)
 
349
 
 
350
        info, rc, err := s.store.OpenCharm(url)
 
351
        c.Assert(err, IsNil)
 
352
        c.Check(info.BundleSha256(), Equals, fakeRevZeroSha)
 
353
        c.Check(info.BundleSize(), Equals, int64(len("charm-revision-0")))
 
354
        err = rc.Close()
 
355
        c.Check(err, IsNil)
 
356
}
 
357
 
 
358
func (s *StoreSuite) TestLogCharmEventWithRevisionedURL(c *C) {
 
359
        url := charm.MustParseURL("cs:oneiric/wordpress-0")
 
360
        event := &store.CharmEvent{
 
361
                Kind:   store.EventPublishError,
 
362
                Digest: "some-digest",
 
363
                URLs:   []*charm.URL{url},
 
364
        }
 
365
        err := s.store.LogCharmEvent(event)
 
366
        c.Assert(err, ErrorMatches, "LogCharmEvent: got charm URL with revision: cs:oneiric/wordpress-0")
 
367
 
 
368
        // This may work in the future, but not now.
 
369
        event, err = s.store.CharmEvent(url, "some-digest")
 
370
        c.Assert(err, ErrorMatches, "CharmEvent: got charm URL with revision: cs:oneiric/wordpress-0")
 
371
        c.Assert(event, IsNil)
 
372
}
 
373
 
 
374
func (s *StoreSuite) TestLogCharmEvent(c *C) {
 
375
        url1 := charm.MustParseURL("cs:oneiric/wordpress")
 
376
        url2 := charm.MustParseURL("cs:oneiric/mysql")
 
377
        urls := []*charm.URL{url1, url2}
 
378
 
 
379
        event1 := &store.CharmEvent{
 
380
                Kind:     store.EventPublished,
 
381
                Revision: 42,
 
382
                Digest:   "revKey1",
 
383
                URLs:     urls,
 
384
                Warnings: []string{"A warning."},
 
385
                Time:     time.Unix(1, 0),
 
386
        }
 
387
        event2 := &store.CharmEvent{
 
388
                Kind:     store.EventPublished,
 
389
                Revision: 42,
 
390
                Digest:   "revKey2",
 
391
                URLs:     urls,
 
392
                Time:     time.Unix(1, 0),
 
393
        }
 
394
        event3 := &store.CharmEvent{
 
395
                Kind:   store.EventPublishError,
 
396
                Digest: "revKey2",
 
397
                Errors: []string{"An error."},
 
398
                URLs:   urls[:1],
 
399
        }
 
400
 
 
401
        for _, event := range []*store.CharmEvent{event1, event2, event3} {
 
402
                err := s.store.LogCharmEvent(event)
 
403
                c.Assert(err, IsNil)
 
404
        }
 
405
 
 
406
        events := s.Session.DB("juju").C("events")
 
407
        var s1, s2 map[string]interface{}
 
408
 
 
409
        err := events.Find(bson.M{"digest": "revKey1"}).One(&s1)
 
410
        c.Assert(err, IsNil)
 
411
        c.Assert(s1["kind"], Equals, int(store.EventPublished))
 
412
        c.Assert(s1["urls"], DeepEquals, []interface{}{"cs:oneiric/wordpress", "cs:oneiric/mysql"})
 
413
        c.Assert(s1["warnings"], DeepEquals, []interface{}{"A warning."})
 
414
        c.Assert(s1["errors"], IsNil)
 
415
        c.Assert(s1["time"], DeepEquals, time.Unix(1, 0))
 
416
 
 
417
        err = events.Find(bson.M{"digest": "revKey2", "kind": store.EventPublishError}).One(&s2)
 
418
        c.Assert(err, IsNil)
 
419
        c.Assert(s2["urls"], DeepEquals, []interface{}{"cs:oneiric/wordpress"})
 
420
        c.Assert(s2["warnings"], IsNil)
 
421
        c.Assert(s2["errors"], DeepEquals, []interface{}{"An error."})
 
422
        c.Assert(s2["time"].(time.Time).After(bson.Now().Add(-10e9)), Equals, true)
 
423
 
 
424
        // Mongo stores timestamps in milliseconds, so chop
 
425
        // off the extra bits for comparison.
 
426
        event3.Time = time.Unix(0, event3.Time.UnixNano()/1e6*1e6)
 
427
 
 
428
        event, err := s.store.CharmEvent(urls[0], "revKey2")
 
429
        c.Assert(err, IsNil)
 
430
        c.Assert(event, DeepEquals, event3)
 
431
 
 
432
        event, err = s.store.CharmEvent(urls[1], "revKey1")
 
433
        c.Assert(err, IsNil)
 
434
        c.Assert(event, DeepEquals, event1)
 
435
 
 
436
        event, err = s.store.CharmEvent(urls[1], "revKeyX")
 
437
        c.Assert(err, Equals, store.ErrNotFound)
 
438
        c.Assert(event, IsNil)
 
439
}
 
440
 
 
441
func (s *StoreSuite) TestSumCounters(c *C) {
 
442
        req := store.CounterRequest{Key: []string{"a"}}
 
443
        cs, err := s.store.Counters(&req)
 
444
        c.Assert(err, IsNil)
 
445
        c.Assert(cs, DeepEquals, []store.Counter{{Key: req.Key, Count: 0}})
 
446
 
 
447
        for i := 0; i < 10; i++ {
 
448
                err := s.store.IncCounter([]string{"a", "b", "c"})
 
449
                c.Assert(err, IsNil)
 
450
        }
 
451
        for i := 0; i < 7; i++ {
 
452
                s.store.IncCounter([]string{"a", "b"})
 
453
                c.Assert(err, IsNil)
 
454
        }
 
455
        for i := 0; i < 3; i++ {
 
456
                s.store.IncCounter([]string{"a", "z", "b"})
 
457
                c.Assert(err, IsNil)
 
458
        }
 
459
 
 
460
        tests := []struct {
 
461
                key    []string
 
462
                prefix bool
 
463
                result int64
 
464
        }{
 
465
                {[]string{"a", "b", "c"}, false, 10},
 
466
                {[]string{"a", "b"}, false, 7},
 
467
                {[]string{"a", "z", "b"}, false, 3},
 
468
                {[]string{"a", "b", "c"}, true, 0},
 
469
                {[]string{"a", "b", "c", "d"}, false, 0},
 
470
                {[]string{"a", "b"}, true, 10},
 
471
                {[]string{"a"}, true, 20},
 
472
                {[]string{"b"}, true, 0},
 
473
        }
 
474
 
 
475
        for _, t := range tests {
 
476
                c.Logf("Test: %#v\n", t)
 
477
                req = store.CounterRequest{Key: t.key, Prefix: t.prefix}
 
478
                cs, err := s.store.Counters(&req)
 
479
                c.Assert(err, IsNil)
 
480
                c.Assert(cs, DeepEquals, []store.Counter{{Key: t.key, Prefix: t.prefix, Count: t.result}})
 
481
        }
 
482
 
 
483
        // High-level interface works. Now check that the data is
 
484
        // stored correctly.
 
485
        counters := s.Session.DB("juju").C("stat.counters")
 
486
        docs1, err := counters.Count()
 
487
        c.Assert(err, IsNil)
 
488
        if docs1 != 3 && docs1 != 4 {
 
489
                fmt.Errorf("Expected 3 or 4 docs in counters collection, got %d", docs1)
 
490
        }
 
491
 
 
492
        // Hack times so that the next operation adds another document.
 
493
        err = counters.Update(nil, bson.D{{"$set", bson.D{{"t", 1}}}})
 
494
        c.Check(err, IsNil)
 
495
 
 
496
        err = s.store.IncCounter([]string{"a", "b", "c"})
 
497
        c.Assert(err, IsNil)
 
498
 
 
499
        docs2, err := counters.Count()
 
500
        c.Assert(err, IsNil)
 
501
        c.Assert(docs2, Equals, docs1+1)
 
502
 
 
503
        req = store.CounterRequest{Key: []string{"a", "b", "c"}}
 
504
        cs, err = s.store.Counters(&req)
 
505
        c.Assert(err, IsNil)
 
506
        c.Assert(cs, DeepEquals, []store.Counter{{Key: req.Key, Count: 11}})
 
507
 
 
508
        req = store.CounterRequest{Key: []string{"a"}, Prefix: true}
 
509
        cs, err = s.store.Counters(&req)
 
510
        c.Assert(err, IsNil)
 
511
        c.Assert(cs, DeepEquals, []store.Counter{{Key: req.Key, Prefix: true, Count: 21}})
 
512
}
 
513
 
 
514
func (s *StoreSuite) TestCountersReadOnlySum(c *C) {
 
515
        // Summing up an unknown key shouldn't add the key to the database.
 
516
        req := store.CounterRequest{Key: []string{"a", "b", "c"}}
 
517
        _, err := s.store.Counters(&req)
 
518
        c.Assert(err, IsNil)
 
519
 
 
520
        tokens := s.Session.DB("juju").C("stat.tokens")
 
521
        n, err := tokens.Count()
 
522
        c.Assert(err, IsNil)
 
523
        c.Assert(n, Equals, 0)
 
524
}
 
525
 
 
526
func (s *StoreSuite) TestCountersTokenCaching(c *C) {
 
527
        assertSum := func(i int, want int64) {
 
528
                req := store.CounterRequest{Key: []string{strconv.Itoa(i)}}
 
529
                cs, err := s.store.Counters(&req)
 
530
                c.Assert(err, IsNil)
 
531
                c.Assert(cs[0].Count, Equals, want)
 
532
        }
 
533
        assertSum(100000, 0)
 
534
 
 
535
        const genSize = 1024
 
536
 
 
537
        // All of these will be cached, as we have two generations
 
538
        // of genSize entries each.
 
539
        for i := 0; i < genSize*2; i++ {
 
540
                err := s.store.IncCounter([]string{strconv.Itoa(i)})
 
541
                c.Assert(err, IsNil)
 
542
        }
 
543
 
 
544
        // Now go behind the scenes and corrupt all the tokens.
 
545
        tokens := s.Session.DB("juju").C("stat.tokens")
 
546
        iter := tokens.Find(nil).Iter()
 
547
        var t struct {
 
548
                Id    int    "_id"
 
549
                Token string "t"
 
550
        }
 
551
        for iter.Next(&t) {
 
552
                err := tokens.UpdateId(t.Id, bson.M{"$set": bson.M{"t": "corrupted" + t.Token}})
 
553
                c.Assert(err, IsNil)
 
554
        }
 
555
        c.Assert(iter.Err(), IsNil)
 
556
 
 
557
        // We can consult the counters for the cached entries still.
 
558
        // First, check that the newest generation is good.
 
559
        for i := genSize; i < genSize*2; i++ {
 
560
                assertSum(i, 1)
 
561
        }
 
562
 
 
563
        // Now, we can still access a single entry of the older generation,
 
564
        // but this will cause the generations to flip and thus the rest
 
565
        // of the old generation will go away as the top half of the
 
566
        // entries is turned into the old generation.
 
567
        assertSum(0, 1)
 
568
 
 
569
        // Now we've lost access to the rest of the old generation.
 
570
        for i := 1; i < genSize; i++ {
 
571
                assertSum(i, 0)
 
572
        }
 
573
 
 
574
        // But we still have all of the top half available since it was
 
575
        // moved into the old generation.
 
576
        for i := genSize; i < genSize*2; i++ {
 
577
                assertSum(i, 1)
 
578
        }
 
579
}
 
580
 
 
581
func (s *StoreSuite) TestCounterTokenUniqueness(c *C) {
 
582
        var wg0, wg1 sync.WaitGroup
 
583
        wg0.Add(10)
 
584
        wg1.Add(10)
 
585
        for i := 0; i < 10; i++ {
 
586
                go func() {
 
587
                        wg0.Done()
 
588
                        wg0.Wait()
 
589
                        defer wg1.Done()
 
590
                        err := s.store.IncCounter([]string{"a"})
 
591
                        c.Check(err, IsNil)
 
592
                }()
 
593
        }
 
594
        wg1.Wait()
 
595
 
 
596
        req := store.CounterRequest{Key: []string{"a"}}
 
597
        cs, err := s.store.Counters(&req)
 
598
        c.Assert(err, IsNil)
 
599
        c.Assert(cs[0].Count, Equals, int64(10))
 
600
}
 
601
 
 
602
func (s *StoreSuite) TestListCounters(c *C) {
 
603
        incs := [][]string{
 
604
                {"c", "b", "a"}, // Assign internal id c < id b < id a, to make sorting slightly trickier.
 
605
                {"a"},
 
606
                {"a", "c"},
 
607
                {"a", "b"},
 
608
                {"a", "b", "c"},
 
609
                {"a", "b", "c"},
 
610
                {"a", "b", "e"},
 
611
                {"a", "b", "d"},
 
612
                {"a", "f", "g"},
 
613
                {"a", "f", "h"},
 
614
                {"a", "i"},
 
615
                {"a", "i", "j"},
 
616
                {"k", "l"},
 
617
        }
 
618
        for _, key := range incs {
 
619
                err := s.store.IncCounter(key)
 
620
                c.Assert(err, IsNil)
 
621
        }
 
622
 
 
623
        tests := []struct {
 
624
                prefix []string
 
625
                result []store.Counter
 
626
        }{
 
627
                {
 
628
                        []string{"a"},
 
629
                        []store.Counter{
 
630
                                {Key: []string{"a", "b"}, Prefix: true, Count: 4},
 
631
                                {Key: []string{"a", "f"}, Prefix: true, Count: 2},
 
632
                                {Key: []string{"a", "b"}, Prefix: false, Count: 1},
 
633
                                {Key: []string{"a", "c"}, Prefix: false, Count: 1},
 
634
                                {Key: []string{"a", "i"}, Prefix: false, Count: 1},
 
635
                                {Key: []string{"a", "i"}, Prefix: true, Count: 1},
 
636
                        },
 
637
                }, {
 
638
                        []string{"a", "b"},
 
639
                        []store.Counter{
 
640
                                {Key: []string{"a", "b", "c"}, Prefix: false, Count: 2},
 
641
                                {Key: []string{"a", "b", "d"}, Prefix: false, Count: 1},
 
642
                                {Key: []string{"a", "b", "e"}, Prefix: false, Count: 1},
 
643
                        },
 
644
                }, {
 
645
                        []string{"z"},
 
646
                        []store.Counter(nil),
 
647
                },
 
648
        }
 
649
 
 
650
        // Use a different store to exercise cache filling.
 
651
        st, err := store.Open(s.Addr)
 
652
        c.Assert(err, IsNil)
 
653
        defer st.Close()
 
654
 
 
655
        for i := range tests {
 
656
                req := &store.CounterRequest{Key: tests[i].prefix, Prefix: true, List: true}
 
657
                result, err := st.Counters(req)
 
658
                c.Assert(err, IsNil)
 
659
                c.Assert(result, DeepEquals, tests[i].result)
 
660
        }
 
661
}
 
662
 
 
663
func (s *StoreSuite) TestListCountersBy(c *C) {
 
664
        incs := []struct {
 
665
                key []string
 
666
                day int
 
667
        }{
 
668
                {[]string{"a"}, 1},
 
669
                {[]string{"a"}, 1},
 
670
                {[]string{"b"}, 1},
 
671
                {[]string{"a", "b"}, 1},
 
672
                {[]string{"a", "c"}, 1},
 
673
                {[]string{"a"}, 3},
 
674
                {[]string{"a", "b"}, 3},
 
675
                {[]string{"b"}, 9},
 
676
                {[]string{"b"}, 9},
 
677
                {[]string{"a", "c", "d"}, 9},
 
678
                {[]string{"a", "c", "e"}, 9},
 
679
                {[]string{"a", "c", "f"}, 9},
 
680
        }
 
681
 
 
682
        day := func(i int) time.Time {
 
683
                return time.Date(2012, time.May, i, 0, 0, 0, 0, time.UTC)
 
684
        }
 
685
 
 
686
        counters := s.Session.DB("juju").C("stat.counters")
 
687
        for i, inc := range incs {
 
688
                err := s.store.IncCounter(inc.key)
 
689
                c.Assert(err, IsNil)
 
690
 
 
691
                // Hack time so counters are assigned to 2012-05-<day>
 
692
                filter := bson.M{"t": bson.M{"$gt": store.TimeToStamp(time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC))}}
 
693
                stamp := store.TimeToStamp(day(inc.day))
 
694
                stamp += int32(i) * 60 // Make every entry unique.
 
695
                err = counters.Update(filter, bson.D{{"$set", bson.D{{"t", stamp}}}})
 
696
                c.Check(err, IsNil)
 
697
        }
 
698
 
 
699
        tests := []struct {
 
700
                request store.CounterRequest
 
701
                result  []store.Counter
 
702
        }{
 
703
                {
 
704
                        store.CounterRequest{
 
705
                                Key:    []string{"a"},
 
706
                                Prefix: false,
 
707
                                List:   false,
 
708
                                By:     store.ByDay,
 
709
                        },
 
710
                        []store.Counter{
 
711
                                {Key: []string{"a"}, Prefix: false, Count: 2, Time: day(1)},
 
712
                                {Key: []string{"a"}, Prefix: false, Count: 1, Time: day(3)},
 
713
                        },
 
714
                }, {
 
715
                        store.CounterRequest{
 
716
                                Key:    []string{"a"},
 
717
                                Prefix: true,
 
718
                                List:   false,
 
719
                                By:     store.ByDay,
 
720
                        },
 
721
                        []store.Counter{
 
722
                                {Key: []string{"a"}, Prefix: true, Count: 2, Time: day(1)},
 
723
                                {Key: []string{"a"}, Prefix: true, Count: 1, Time: day(3)},
 
724
                                {Key: []string{"a"}, Prefix: true, Count: 3, Time: day(9)},
 
725
                        },
 
726
                }, {
 
727
                        store.CounterRequest{
 
728
                                Key:    []string{"a"},
 
729
                                Prefix: true,
 
730
                                List:   false,
 
731
                                By:     store.ByDay,
 
732
                                Start:  day(2),
 
733
                        },
 
734
                        []store.Counter{
 
735
                                {Key: []string{"a"}, Prefix: true, Count: 1, Time: day(3)},
 
736
                                {Key: []string{"a"}, Prefix: true, Count: 3, Time: day(9)},
 
737
                        },
 
738
                }, {
 
739
                        store.CounterRequest{
 
740
                                Key:    []string{"a"},
 
741
                                Prefix: true,
 
742
                                List:   false,
 
743
                                By:     store.ByDay,
 
744
                                Stop:   day(4),
 
745
                        },
 
746
                        []store.Counter{
 
747
                                {Key: []string{"a"}, Prefix: true, Count: 2, Time: day(1)},
 
748
                                {Key: []string{"a"}, Prefix: true, Count: 1, Time: day(3)},
 
749
                        },
 
750
                }, {
 
751
                        store.CounterRequest{
 
752
                                Key:    []string{"a"},
 
753
                                Prefix: true,
 
754
                                List:   false,
 
755
                                By:     store.ByDay,
 
756
                                Start:  day(3),
 
757
                                Stop:   day(8),
 
758
                        },
 
759
                        []store.Counter{
 
760
                                {Key: []string{"a"}, Prefix: true, Count: 1, Time: day(3)},
 
761
                        },
 
762
                }, {
 
763
                        store.CounterRequest{
 
764
                                Key:    []string{"a"},
 
765
                                Prefix: true,
 
766
                                List:   true,
 
767
                                By:     store.ByDay,
 
768
                        },
 
769
                        []store.Counter{
 
770
                                {Key: []string{"a", "b"}, Prefix: false, Count: 1, Time: day(1)},
 
771
                                {Key: []string{"a", "c"}, Prefix: false, Count: 1, Time: day(1)},
 
772
                                {Key: []string{"a", "b"}, Prefix: false, Count: 1, Time: day(3)},
 
773
                                {Key: []string{"a", "c"}, Prefix: true, Count: 3, Time: day(9)},
 
774
                        },
 
775
                }, {
 
776
                        store.CounterRequest{
 
777
                                Key:    []string{"a"},
 
778
                                Prefix: true,
 
779
                                List:   false,
 
780
                                By:     store.ByWeek,
 
781
                        },
 
782
                        []store.Counter{
 
783
                                {Key: []string{"a"}, Prefix: true, Count: 3, Time: day(6)},
 
784
                                {Key: []string{"a"}, Prefix: true, Count: 3, Time: day(13)},
 
785
                        },
 
786
                }, {
 
787
                        store.CounterRequest{
 
788
                                Key:    []string{"a"},
 
789
                                Prefix: true,
 
790
                                List:   true,
 
791
                                By:     store.ByWeek,
 
792
                        },
 
793
                        []store.Counter{
 
794
                                {Key: []string{"a", "b"}, Prefix: false, Count: 2, Time: day(6)},
 
795
                                {Key: []string{"a", "c"}, Prefix: false, Count: 1, Time: day(6)},
 
796
                                {Key: []string{"a", "c"}, Prefix: true, Count: 3, Time: day(13)},
 
797
                        },
 
798
                },
 
799
        }
 
800
 
 
801
        for _, test := range tests {
 
802
                result, err := s.store.Counters(&test.request)
 
803
                c.Assert(err, IsNil)
 
804
                c.Assert(result, DeepEquals, test.result)
 
805
        }
 
806
}
 
807
 
 
808
func (s *TrivialSuite) TestEventString(c *C) {
 
809
        c.Assert(store.EventPublished, Matches, "published")
 
810
        c.Assert(store.EventPublishError, Matches, "publish-error")
 
811
        for kind := store.CharmEventKind(1); kind < store.EventKindCount; kind++ {
 
812
                // This guarantees the switch in String is properly
 
813
                // updated with new event kinds.
 
814
                c.Assert(kind.String(), Matches, "[a-z-]+")
 
815
        }
 
816
}