github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/collection_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	jc "github.com/juju/testing/checkers"
     8  	gc "gopkg.in/check.v1"
     9  	"gopkg.in/mgo.v2"
    10  	"gopkg.in/mgo.v2/bson"
    11  
    12  	"github.com/juju/juju/state"
    13  	"github.com/juju/juju/testing/factory"
    14  )
    15  
    16  type collectionSuite struct {
    17  	ConnSuite
    18  }
    19  
    20  var _ = gc.Suite(&collectionSuite{})
    21  
    22  type collectionTestCase struct {
    23  	label         string
    24  	test          func() (int, error)
    25  	expectedCount int
    26  	expectedPanic string
    27  	expectedError string
    28  }
    29  
    30  func (s *collectionSuite) TestGenericStateCollection(c *gc.C) {
    31  	// The users collection does not require filtering by model UUID.
    32  	coll, closer := state.GetCollection(s.State, state.UsersC)
    33  	defer closer()
    34  
    35  	c.Check(coll.Name(), gc.Equals, state.UsersC)
    36  
    37  	s.Factory.MakeUser(c, &factory.UserParams{Name: "foo", DisplayName: "Ms Foo"})
    38  	s.Factory.MakeUser(c, &factory.UserParams{Name: "bar"})
    39  
    40  	collSnapshot := newCollectionSnapshot(c, coll.Writeable().Underlying())
    41  
    42  	for i, t := range []collectionTestCase{
    43  		{
    44  			label: "Count",
    45  			test: func() (int, error) {
    46  				return coll.Count()
    47  			},
    48  			expectedCount: 3,
    49  		},
    50  		{
    51  			label: "FindId",
    52  			test: func() (int, error) {
    53  				return coll.FindId("foo").Count()
    54  			},
    55  			expectedCount: 1,
    56  		},
    57  		{
    58  			label: "Find with one result",
    59  			test: func() (int, error) {
    60  				return coll.Find(bson.D{{"displayname", "Ms Foo"}}).Count()
    61  			},
    62  			expectedCount: 1,
    63  		},
    64  		{
    65  			label: "Find with nil",
    66  			test: func() (int, error) {
    67  				return coll.Find(nil).Count()
    68  			},
    69  			expectedCount: 3,
    70  		},
    71  		{
    72  			label: "Insert",
    73  			test: func() (int, error) {
    74  				err := coll.Writeable().Insert(bson.D{{"_id", "more"}})
    75  				c.Assert(err, jc.ErrorIsNil)
    76  				return coll.Count()
    77  			},
    78  			expectedCount: 4,
    79  		},
    80  		{
    81  			label: "RemoveId",
    82  			test: func() (int, error) {
    83  				err := coll.Writeable().RemoveId("bar")
    84  				c.Assert(err, jc.ErrorIsNil)
    85  				return coll.Count()
    86  			},
    87  			expectedCount: 2,
    88  		},
    89  		{
    90  			label: "Remove",
    91  			test: func() (int, error) {
    92  				err := coll.Writeable().Remove(bson.D{{"displayname", "Ms Foo"}})
    93  				c.Assert(err, jc.ErrorIsNil)
    94  				return coll.Count()
    95  			},
    96  			expectedCount: 2,
    97  		},
    98  		{
    99  			label: "RemoveAll",
   100  			test: func() (int, error) {
   101  				_, err := coll.Writeable().RemoveAll(bson.D{{"createdby", s.Owner.Name()}})
   102  				c.Assert(err, jc.ErrorIsNil)
   103  				return coll.Count()
   104  			},
   105  			expectedCount: 0,
   106  		},
   107  		{
   108  			label: "Update",
   109  			test: func() (int, error) {
   110  				err := coll.Writeable().Update(bson.D{{"_id", "bar"}},
   111  					bson.D{{"$set", bson.D{{"displayname", "Updated Bar"}}}})
   112  				c.Assert(err, jc.ErrorIsNil)
   113  
   114  				return coll.Find(bson.D{{"displayname", "Updated Bar"}}).Count()
   115  			},
   116  			expectedCount: 1,
   117  		},
   118  		{
   119  			label: "UpdateId",
   120  			test: func() (int, error) {
   121  				err := coll.Writeable().UpdateId("bar",
   122  					bson.D{{"$set", bson.D{{"displayname", "Updated Bar"}}}})
   123  				c.Assert(err, jc.ErrorIsNil)
   124  
   125  				return coll.Find(bson.D{{"displayname", "Updated Bar"}}).Count()
   126  			},
   127  			expectedCount: 1,
   128  		},
   129  	} {
   130  		c.Logf("test %d: %s", i, t.label)
   131  		collSnapshot.restore(c)
   132  
   133  		count, err := t.test()
   134  		c.Assert(err, jc.ErrorIsNil)
   135  		c.Check(count, gc.Equals, t.expectedCount)
   136  	}
   137  }
   138  
   139  func (s *collectionSuite) TestModelStateCollection(c *gc.C) {
   140  	// The machines collection requires filtering by model UUID. Set up
   141  	// 2 models with machines in each.
   142  	m0 := s.Factory.MakeMachine(c, nil)
   143  	s.Factory.MakeMachine(c, nil)
   144  	st1 := s.Factory.MakeModel(c, nil)
   145  	defer st1.Close()
   146  	f1 := factory.NewFactory(st1)
   147  	otherM0 := f1.MakeMachine(c, &factory.MachineParams{Series: "trusty"})
   148  
   149  	// Ensure that the first machine in each model have overlapping ids
   150  	// (otherwise tests may not fail when they should)
   151  	c.Assert(m0.Id(), gc.Equals, otherM0.Id())
   152  
   153  	machines0, closer := state.GetCollection(s.State, state.MachinesC)
   154  	defer closer()
   155  	machines1, closer := state.GetCollection(st1, state.MachinesC)
   156  	defer closer()
   157  
   158  	machinesSnapshot := newCollectionSnapshot(c, machines0.Writeable().Underlying())
   159  
   160  	c.Assert(machines0.Name(), gc.Equals, state.MachinesC)
   161  
   162  	for i, t := range []collectionTestCase{
   163  		{
   164  			label: "Count filters by model",
   165  			test: func() (int, error) {
   166  				return machines0.Count()
   167  			},
   168  			expectedCount: 2,
   169  		},
   170  		{
   171  			label: "Find filters by model",
   172  			test: func() (int, error) {
   173  				return machines0.Find(bson.D{{"series", m0.Series()}}).Count()
   174  			},
   175  			expectedCount: 2,
   176  		},
   177  		{
   178  			label: "Find adds model UUID when _id is provided",
   179  			test: func() (int, error) {
   180  				return machines0.Find(bson.D{{"_id", m0.Id()}}).Count()
   181  			},
   182  			expectedCount: 1,
   183  		},
   184  		{
   185  			label: "Find tolerates model UUID prefix already being present",
   186  			test: func() (int, error) {
   187  				return machines0.Find(bson.D{
   188  					{"_id", state.DocID(s.State, m0.Id())},
   189  				}).Count()
   190  			},
   191  			expectedCount: 1,
   192  		},
   193  		{
   194  			label: "Find with no selector still filters by model",
   195  			test: func() (int, error) {
   196  				return machines0.Find(nil).Count()
   197  			},
   198  			expectedCount: 2,
   199  		},
   200  		{
   201  			label: "Find leaves _id alone if used with operator",
   202  			test: func() (int, error) {
   203  				return machines0.Find(bson.D{
   204  					{"_id", bson.D{{"$regex", ":" + m0.Id() + "$"}}},
   205  				}).Count()
   206  			},
   207  			expectedCount: 1, // not 2 because model-uuid filter is still added
   208  		},
   209  		{
   210  			label: "Find works with maps",
   211  			test: func() (int, error) {
   212  				return machines0.Find(map[string]string{"_id": m0.Id()}).Count()
   213  			},
   214  			expectedCount: 1,
   215  		},
   216  		{
   217  			label: "Find panics if model-uuid is included",
   218  			test: func() (int, error) {
   219  				machines0.Find(bson.D{{"model-uuid", "whatever"}})
   220  				return 0, nil
   221  			},
   222  			expectedPanic: "model-uuid is added automatically and should not be provided",
   223  		},
   224  		{
   225  			label: "FindId adds model UUID prefix",
   226  			test: func() (int, error) {
   227  				return machines0.FindId(m0.Id()).Count()
   228  			},
   229  			expectedCount: 1,
   230  		},
   231  		{
   232  			label: "FindId tolerates model UUID prefix already being there",
   233  			test: func() (int, error) {
   234  				return machines0.FindId(state.DocID(s.State, m0.Id())).Count()
   235  			},
   236  			expectedCount: 1,
   237  		},
   238  		{
   239  			label: "Insert adds model-uuid",
   240  			test: func() (int, error) {
   241  				err := machines0.Writeable().Insert(bson.D{
   242  					{"_id", state.DocID(s.State, "99")},
   243  					{"machineid", 99},
   244  				})
   245  				c.Assert(err, jc.ErrorIsNil)
   246  				return machines0.Count()
   247  			},
   248  			expectedCount: 3,
   249  		},
   250  		{
   251  			label: "Insert populates model-uuid if blank",
   252  			test: func() (int, error) {
   253  				err := machines0.Writeable().Insert(bson.D{
   254  					{"_id", state.DocID(s.State, "99")},
   255  					{"machineid", 99},
   256  					{"model-uuid", ""},
   257  				})
   258  				c.Assert(err, jc.ErrorIsNil)
   259  				return machines0.Count()
   260  			},
   261  			expectedCount: 3,
   262  		},
   263  		{
   264  			label: "Insert prefixes _id",
   265  			test: func() (int, error) {
   266  				err := machines0.Writeable().Insert(bson.D{
   267  					{"_id", "99"},
   268  					{"machineid", 99},
   269  				})
   270  				c.Assert(err, jc.ErrorIsNil)
   271  				return machines0.FindId("99").Count()
   272  			},
   273  			expectedCount: 1,
   274  		},
   275  		{
   276  			label: "Insert tolerates prefixed _id and correct model-uuid if provided",
   277  			test: func() (int, error) {
   278  				err := machines0.Writeable().Insert(bson.D{
   279  					{"_id", state.DocID(s.State, "99")},
   280  					{"machineid", 99},
   281  					{"model-uuid", s.State.ModelUUID()},
   282  				})
   283  				c.Assert(err, jc.ErrorIsNil)
   284  				return machines0.Count()
   285  			},
   286  			expectedCount: 3,
   287  		},
   288  		{
   289  			label: "Insert fails if model-uuid doesn't match",
   290  			test: func() (int, error) {
   291  				err := machines0.Writeable().Insert(bson.D{
   292  					{"_id", "99"},
   293  					{"machineid", 99},
   294  					{"model-uuid", "something-else"},
   295  				})
   296  				return 0, err
   297  			},
   298  			expectedError: "bad \"model-uuid\" value: .+",
   299  		},
   300  		{
   301  			label: "Remove adds model UUID prefix to _id",
   302  			test: func() (int, error) {
   303  				err := machines0.Writeable().Remove(bson.D{{"_id", "0"}})
   304  				c.Assert(err, jc.ErrorIsNil)
   305  				return s.machines.Count()
   306  			},
   307  			expectedCount: 2, // Expect machine-1 in first model and machine-0 in second model
   308  		},
   309  		{
   310  			label: "Remove filters by model",
   311  			test: func() (int, error) {
   312  				// Attempt to remove the trusty machine in the second
   313  				// model with the collection that's filtering for the
   314  				// first model - nothing should get removed.
   315  				err := machines0.Writeable().Remove(bson.D{{"series", "trusty"}})
   316  				c.Assert(err, gc.ErrorMatches, "not found")
   317  				return s.machines.Count()
   318  			},
   319  			expectedCount: 3, // Expect all machines to still be there.
   320  		},
   321  		{
   322  			label: "Remove filters by model 2",
   323  			test: func() (int, error) {
   324  				err := machines0.Writeable().Remove(bson.D{{"machineid", "0"}})
   325  				c.Assert(err, jc.ErrorIsNil)
   326  				return s.machines.Count()
   327  			},
   328  			expectedCount: 2, // Expect machine 1 in first model and machine-0 in second model
   329  		},
   330  		{
   331  			label: "RemoveId adds model UUID prefix",
   332  			test: func() (int, error) {
   333  				err := machines0.Writeable().RemoveId(m0.Id())
   334  				c.Assert(err, jc.ErrorIsNil)
   335  				return s.machines.Count()
   336  			},
   337  			expectedCount: 2, // Expect machine-1 in first model and machine-0 in second model
   338  		},
   339  		{
   340  			label: "RemoveId tolerates model UUID prefix already being there",
   341  			test: func() (int, error) {
   342  				err := machines0.Writeable().RemoveId(state.DocID(s.State, m0.Id()))
   343  				c.Assert(err, jc.ErrorIsNil)
   344  				return s.machines.Count()
   345  			},
   346  			expectedCount: 2, // Expect machine-1 in first model and machine-0 in second model
   347  		},
   348  		{
   349  			label: "RemoveAll filters by model",
   350  			test: func() (int, error) {
   351  				_, err := machines0.Writeable().RemoveAll(bson.D{{"series", m0.Series()}})
   352  				c.Assert(err, jc.ErrorIsNil)
   353  				return s.machines.Count()
   354  			},
   355  			expectedCount: 1, // Expect machine-1 in second model
   356  		},
   357  		{
   358  			label: "RemoveAll adds model UUID when _id is provided",
   359  			test: func() (int, error) {
   360  				_, err := machines0.Writeable().RemoveAll(bson.D{{"_id", m0.Id()}})
   361  				c.Assert(err, jc.ErrorIsNil)
   362  				return s.machines.Count()
   363  			},
   364  			expectedCount: 2, // Expect machine-1 in first model and machine-0 in second model
   365  		},
   366  		{
   367  			label: "RemoveAll tolerates model UUID prefix already being present",
   368  			test: func() (int, error) {
   369  				_, err := machines0.Writeable().RemoveAll(bson.D{
   370  					{"_id", state.DocID(s.State, m0.Id())},
   371  				})
   372  				c.Assert(err, jc.ErrorIsNil)
   373  				return s.machines.Count()
   374  			},
   375  			expectedCount: 2, // Expect machine-1 in first model and machine-0 in second model
   376  		},
   377  		{
   378  			label: "RemoveAll with no selector still filters by model",
   379  			test: func() (int, error) {
   380  				_, err := machines0.Writeable().RemoveAll(nil)
   381  				c.Assert(err, jc.ErrorIsNil)
   382  				return s.machines.Count()
   383  			},
   384  			expectedCount: 1, // Expect machine-0 in second model
   385  		},
   386  		{
   387  			label: "RemoveAll panics if model-uuid is included",
   388  			test: func() (int, error) {
   389  				machines0.Writeable().RemoveAll(bson.D{{"model-uuid", "whatever"}})
   390  				return 0, nil
   391  			},
   392  			expectedPanic: "model-uuid is added automatically and should not be provided",
   393  		},
   394  		{
   395  			label: "Update",
   396  			test: func() (int, error) {
   397  				err := machines0.Writeable().Update(bson.D{{"_id", m0.Id()}},
   398  					bson.D{{"$set", bson.D{{"update-field", "field value"}}}})
   399  				c.Assert(err, jc.ErrorIsNil)
   400  				return machines0.Find(bson.D{{"update-field", "field value"}}).Count()
   401  			},
   402  			expectedCount: 1,
   403  		},
   404  		{
   405  			label: "UpdateId",
   406  			test: func() (int, error) {
   407  				err := machines0.Writeable().UpdateId(m0.Id(),
   408  					bson.D{{"$set", bson.D{{"update-field", "field value"}}}})
   409  				c.Assert(err, jc.ErrorIsNil)
   410  				return machines0.Find(bson.D{{"update-field", "field value"}}).Count()
   411  			},
   412  			expectedCount: 1,
   413  		},
   414  	} {
   415  		c.Logf("test %d: %s", i, t.label)
   416  		machinesSnapshot.restore(c)
   417  
   418  		if t.expectedPanic == "" {
   419  			count, err := t.test()
   420  			if t.expectedError != "" {
   421  				c.Assert(err, gc.ErrorMatches, t.expectedError)
   422  			} else {
   423  				c.Assert(err, jc.ErrorIsNil)
   424  			}
   425  			c.Check(count, gc.Equals, t.expectedCount)
   426  		} else {
   427  			c.Check(func() { t.test() }, gc.PanicMatches, t.expectedPanic)
   428  		}
   429  
   430  		// Check that other model is untouched after each test
   431  		count, err := machines1.Count()
   432  		c.Assert(err, jc.ErrorIsNil)
   433  		c.Check(count, gc.Equals, 1)
   434  	}
   435  }
   436  
   437  type collectionSnapshot struct {
   438  	coll     *mgo.Collection
   439  	origDocs []interface{}
   440  }
   441  
   442  func newCollectionSnapshot(c *gc.C, coll *mgo.Collection) *collectionSnapshot {
   443  	ss := &collectionSnapshot{coll: coll}
   444  	err := coll.Find(nil).All(&ss.origDocs)
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	return ss
   447  }
   448  
   449  func (ss *collectionSnapshot) restore(c *gc.C) {
   450  	_, err := ss.coll.RemoveAll(nil)
   451  	c.Assert(err, jc.ErrorIsNil)
   452  	err = ss.coll.Insert(ss.origDocs...)
   453  	c.Assert(err, jc.ErrorIsNil)
   454  }