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  }