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