github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/state/cloudimagemetadata/image_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloudimagemetadata_test
     5  
     6  import (
     7  	"regexp"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/txn"
    13  	txntesting "github.com/juju/txn/testing"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/mgo.v2"
    16  
    17  	"github.com/juju/juju/mongo"
    18  	"github.com/juju/juju/state/cloudimagemetadata"
    19  )
    20  
    21  type cloudImageMetadataSuite struct {
    22  	testing.IsolatedMgoSuite
    23  
    24  	access  *TestMongo
    25  	storage cloudimagemetadata.Storage
    26  }
    27  
    28  var _ = gc.Suite(&cloudImageMetadataSuite{})
    29  
    30  const (
    31  	envName        = "test-model"
    32  	collectionName = "test-collection"
    33  )
    34  
    35  func (s *cloudImageMetadataSuite) SetUpTest(c *gc.C) {
    36  	s.IsolatedMgoSuite.SetUpTest(c)
    37  
    38  	db := s.MgoSuite.Session.DB("juju")
    39  
    40  	s.access = NewTestMongo(db)
    41  	s.storage = cloudimagemetadata.NewStorage(envName, collectionName, s.access)
    42  }
    43  
    44  func (s *cloudImageMetadataSuite) TestSaveMetadata(c *gc.C) {
    45  	attrs1 := cloudimagemetadata.MetadataAttributes{
    46  		Stream:          "stream",
    47  		Region:          "region-test",
    48  		Version:         "14.04",
    49  		Series:          "trusty",
    50  		Arch:            "arch",
    51  		VirtType:        "virtType-test",
    52  		RootStorageType: "rootStorageType-test",
    53  	}
    54  	attrs2 := cloudimagemetadata.MetadataAttributes{
    55  		Stream:  "chalk",
    56  		Region:  "nether",
    57  		Version: "12.04",
    58  		Series:  "precise",
    59  		Arch:    "amd64",
    60  	}
    61  	added := []cloudimagemetadata.Metadata{
    62  		{attrs1, 0, "1"},
    63  		{attrs2, 0, "2"},
    64  	}
    65  	s.assertRecordMetadata(c, added[0])
    66  	s.assertRecordMetadata(c, added[1])
    67  	s.assertMetadataRecorded(c, cloudimagemetadata.MetadataAttributes{}, added...)
    68  }
    69  
    70  func (s *cloudImageMetadataSuite) TestFindMetadataNotFound(c *gc.C) {
    71  	s.assertNoMetadata(c)
    72  
    73  	// insert something...
    74  	attrs := cloudimagemetadata.MetadataAttributes{
    75  		Stream:          "stream",
    76  		Region:          "region",
    77  		Version:         "14.04",
    78  		Series:          "trusty",
    79  		Arch:            "arch",
    80  		VirtType:        "virtType",
    81  		RootStorageType: "rootStorageType"}
    82  	m := cloudimagemetadata.Metadata{attrs, 0, "1"}
    83  	s.assertRecordMetadata(c, m)
    84  
    85  	// ...but look for something else.
    86  	none, err := s.storage.FindMetadata(cloudimagemetadata.MetadataFilter{
    87  		Stream: "something else",
    88  	})
    89  	// Make sure that we are explicit that we could not find what we wanted.
    90  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
    91  	c.Assert(err, gc.ErrorMatches, "matching cloud image metadata not found")
    92  	c.Assert(none, gc.HasLen, 0)
    93  }
    94  
    95  func buildAttributesFilter(attrs cloudimagemetadata.MetadataAttributes) cloudimagemetadata.MetadataFilter {
    96  	filter := cloudimagemetadata.MetadataFilter{
    97  		Stream:          attrs.Stream,
    98  		Region:          attrs.Region,
    99  		VirtType:        attrs.VirtType,
   100  		RootStorageType: attrs.RootStorageType}
   101  	if attrs.Series != "" {
   102  		filter.Series = []string{attrs.Series}
   103  	}
   104  	if attrs.Arch != "" {
   105  		filter.Arches = []string{attrs.Arch}
   106  	}
   107  	return filter
   108  }
   109  
   110  func (s *cloudImageMetadataSuite) TestFindMetadata(c *gc.C) {
   111  	attrs := cloudimagemetadata.MetadataAttributes{
   112  		Stream:          "stream",
   113  		Region:          "region",
   114  		Version:         "14.04",
   115  		Series:          "trusty",
   116  		Arch:            "arch",
   117  		VirtType:        "virtType",
   118  		RootStorageType: "rootStorageType"}
   119  
   120  	m := cloudimagemetadata.Metadata{attrs, 0, "1"}
   121  
   122  	_, err := s.storage.FindMetadata(buildAttributesFilter(attrs))
   123  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   124  
   125  	s.assertRecordMetadata(c, m)
   126  	expected := []cloudimagemetadata.Metadata{m}
   127  	s.assertMetadataRecorded(c, attrs, expected...)
   128  
   129  	attrs.Stream = "another_stream"
   130  	m = cloudimagemetadata.Metadata{attrs, 0, "2"}
   131  	s.assertRecordMetadata(c, m)
   132  
   133  	expected = append(expected, m)
   134  	// Should find both
   135  	s.assertMetadataRecorded(c, cloudimagemetadata.MetadataAttributes{Region: "region"}, expected...)
   136  }
   137  
   138  func (s *cloudImageMetadataSuite) TestSaveMetadataUpdateSameAttrsAndImages(c *gc.C) {
   139  	attrs := cloudimagemetadata.MetadataAttributes{
   140  		Stream:  "stream",
   141  		Version: "14.04",
   142  		Series:  "trusty",
   143  		Arch:    "arch",
   144  	}
   145  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   146  	metadata1 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   147  
   148  	s.assertRecordMetadata(c, metadata0)
   149  	s.assertRecordMetadata(c, metadata1)
   150  	s.assertMetadataRecorded(c, attrs, metadata1)
   151  }
   152  
   153  func (s *cloudImageMetadataSuite) TestSaveMetadataUpdateSameAttrsDiffImages(c *gc.C) {
   154  	attrs := cloudimagemetadata.MetadataAttributes{
   155  		Stream:  "stream",
   156  		Version: "14.04",
   157  		Series:  "trusty",
   158  		Arch:    "arch",
   159  	}
   160  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   161  	metadata1 := cloudimagemetadata.Metadata{attrs, 0, "12"}
   162  
   163  	s.assertRecordMetadata(c, metadata0)
   164  	s.assertMetadataRecorded(c, attrs, metadata0)
   165  	s.assertRecordMetadata(c, metadata1)
   166  	s.assertMetadataRecorded(c, attrs, metadata1)
   167  	s.assertMetadataRecorded(c, cloudimagemetadata.MetadataAttributes{}, metadata1)
   168  }
   169  
   170  func (s *cloudImageMetadataSuite) TestSaveDiffMetadataConcurrentlyAndOrderByDateCreated(c *gc.C) {
   171  	attrs := cloudimagemetadata.MetadataAttributes{
   172  		Stream:  "stream",
   173  		Version: "14.04",
   174  		Series:  "trusty",
   175  		Arch:    "arch",
   176  	}
   177  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "0"}
   178  	metadata1 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   179  	metadata1.Stream = "scream"
   180  
   181  	s.assertConcurrentSave(c,
   182  		metadata0, // add this one
   183  		metadata1, // add this one
   184  		// last added should be first as order is by date created
   185  		metadata1, // verify it's in the list
   186  		metadata0, // verify it's in the list
   187  	)
   188  }
   189  
   190  func (s *cloudImageMetadataSuite) TestSaveSameMetadataDiffImageConcurrently(c *gc.C) {
   191  	attrs := cloudimagemetadata.MetadataAttributes{
   192  		Stream:  "stream",
   193  		Version: "14.04",
   194  		Series:  "trusty",
   195  		Arch:    "arch",
   196  	}
   197  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "0"}
   198  	metadata1 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   199  
   200  	s.assertConcurrentSave(c,
   201  		metadata0, // add this one
   202  		metadata1, // overwrite it with this one
   203  		metadata1, // verify only the last one is in the list
   204  	)
   205  }
   206  
   207  func (s *cloudImageMetadataSuite) TestSaveSameMetadataSameImageConcurrently(c *gc.C) {
   208  	attrs := cloudimagemetadata.MetadataAttributes{
   209  		Stream:  "stream",
   210  		Version: "14.04",
   211  		Series:  "trusty",
   212  		Arch:    "arch",
   213  	}
   214  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "0"}
   215  
   216  	s.assertConcurrentSave(c,
   217  		metadata0, // add this one
   218  		metadata0, // add it again
   219  		metadata0, // varify only one is in the list
   220  	)
   221  }
   222  
   223  func (s *cloudImageMetadataSuite) TestSaveSameMetadataSameImageDiffSourceConcurrently(c *gc.C) {
   224  	attrs := cloudimagemetadata.MetadataAttributes{
   225  		Stream:  "stream",
   226  		Version: "14.04",
   227  		Series:  "trusty",
   228  		Arch:    "arch",
   229  		Source:  "public",
   230  	}
   231  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "0"}
   232  
   233  	attrs.Source = "custom"
   234  	metadata1 := cloudimagemetadata.Metadata{attrs, 0, "0"}
   235  
   236  	s.assertConcurrentSave(c,
   237  		metadata0,
   238  		metadata1,
   239  		metadata0,
   240  		metadata1,
   241  	)
   242  }
   243  
   244  func (s *cloudImageMetadataSuite) TestSaveMetadataNoVersionPassed(c *gc.C) {
   245  	attrs := cloudimagemetadata.MetadataAttributes{
   246  		Stream: "stream",
   247  		Series: "trusty",
   248  		Arch:   "arch",
   249  	}
   250  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   251  	s.assertRecordMetadata(c, metadata0)
   252  }
   253  
   254  func (s *cloudImageMetadataSuite) TestSaveMetadataNoSeriesPassed(c *gc.C) {
   255  	attrs := cloudimagemetadata.MetadataAttributes{
   256  		Stream: "stream",
   257  		Arch:   "arch",
   258  	}
   259  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   260  	err := s.storage.SaveMetadata([]cloudimagemetadata.Metadata{metadata0})
   261  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`missing series: metadata for image 1 not valid`))
   262  }
   263  
   264  func (s *cloudImageMetadataSuite) TestSaveMetadataUnsupportedSeriesPassed(c *gc.C) {
   265  	attrs := cloudimagemetadata.MetadataAttributes{
   266  		Stream: "stream",
   267  		Series: "blah",
   268  		Arch:   "arch",
   269  	}
   270  	metadata0 := cloudimagemetadata.Metadata{attrs, 0, "1"}
   271  	err := s.storage.SaveMetadata([]cloudimagemetadata.Metadata{metadata0})
   272  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`unknown version for series: "blah"`))
   273  }
   274  
   275  func (s *cloudImageMetadataSuite) assertConcurrentSave(c *gc.C, metadata0, metadata1 cloudimagemetadata.Metadata, expected ...cloudimagemetadata.Metadata) {
   276  	addMetadata := func() {
   277  		s.assertRecordMetadata(c, metadata0)
   278  	}
   279  	defer txntesting.SetBeforeHooks(c, s.access.runner, addMetadata).Check()
   280  	s.assertRecordMetadata(c, metadata1)
   281  	s.assertMetadataRecorded(c, cloudimagemetadata.MetadataAttributes{}, expected...)
   282  }
   283  
   284  func (s *cloudImageMetadataSuite) assertRecordMetadata(c *gc.C, m cloudimagemetadata.Metadata) {
   285  	err := s.storage.SaveMetadata([]cloudimagemetadata.Metadata{m})
   286  	c.Assert(err, jc.ErrorIsNil)
   287  }
   288  
   289  func (s *cloudImageMetadataSuite) assertMetadataRecorded(
   290  	c *gc.C,
   291  	criteria cloudimagemetadata.MetadataAttributes,
   292  	expected ...cloudimagemetadata.Metadata,
   293  ) {
   294  	metadata, err := s.storage.FindMetadata(buildAttributesFilter(criteria))
   295  	c.Assert(err, jc.ErrorIsNil)
   296  
   297  	// Collate expected into a map
   298  	groups := make(map[string][]cloudimagemetadata.Metadata)
   299  	for _, expectedMetadata := range expected {
   300  		groups[expectedMetadata.Source] = append(groups[expectedMetadata.Source], expectedMetadata)
   301  	}
   302  
   303  	// Compare maps by key; order of slices does not matter
   304  	c.Assert(groups, gc.HasLen, len(metadata))
   305  	for source, expectedMetadata := range groups {
   306  		c.Assert(metadata[source], jc.SameContents, expectedMetadata)
   307  	}
   308  }
   309  
   310  func (s *cloudImageMetadataSuite) TestSupportedArchitectures(c *gc.C) {
   311  	stream := "stream"
   312  	region := "region-test"
   313  
   314  	arch1 := "arch"
   315  	attrs := cloudimagemetadata.MetadataAttributes{
   316  		Stream:          stream,
   317  		Region:          region,
   318  		Version:         "14.04",
   319  		Series:          "trusty",
   320  		Arch:            arch1,
   321  		VirtType:        "virtType-test",
   322  		RootStorageType: "rootStorageType-test"}
   323  
   324  	added := cloudimagemetadata.Metadata{attrs, 0, "1"}
   325  	s.assertRecordMetadata(c, added)
   326  	s.assertMetadataRecorded(c, attrs, added)
   327  
   328  	addedNonUnique := cloudimagemetadata.Metadata{attrs, 0, "21"}
   329  	s.assertRecordMetadata(c, addedNonUnique)
   330  	s.assertMetadataRecorded(c, attrs, addedNonUnique)
   331  
   332  	arch2 := "anotherArch"
   333  	attrs.Arch = arch2
   334  	added2 := cloudimagemetadata.Metadata{attrs, 0, "21"}
   335  	s.assertRecordMetadata(c, added2)
   336  	s.assertMetadataRecorded(c, attrs, added2)
   337  
   338  	expected := []string{arch1, arch2}
   339  	uniqueArches, err := s.storage.SupportedArchitectures(
   340  		cloudimagemetadata.MetadataFilter{Stream: stream, Region: region})
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	c.Assert(uniqueArches, gc.DeepEquals, expected)
   343  }
   344  
   345  func (s *cloudImageMetadataSuite) TestSupportedArchitecturesUnmatchedStreams(c *gc.C) {
   346  	stream := "stream"
   347  	region := "region-test"
   348  
   349  	attrs := cloudimagemetadata.MetadataAttributes{
   350  		Stream:          "new-stream",
   351  		Region:          region,
   352  		Version:         "14.04",
   353  		Series:          "trusty",
   354  		Arch:            "arch",
   355  		VirtType:        "virtType-test",
   356  		RootStorageType: "rootStorageType-test"}
   357  
   358  	added := cloudimagemetadata.Metadata{attrs, 0, "1"}
   359  	s.assertRecordMetadata(c, added)
   360  	s.assertMetadataRecorded(c, attrs, added)
   361  
   362  	uniqueArches, err := s.storage.SupportedArchitectures(
   363  		cloudimagemetadata.MetadataFilter{Stream: stream, Region: region})
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	c.Assert(uniqueArches, gc.DeepEquals, []string{})
   366  }
   367  
   368  func (s *cloudImageMetadataSuite) TestSupportedArchitecturesUnmatchedRegions(c *gc.C) {
   369  	stream := "stream"
   370  	region := "region-test"
   371  
   372  	attrs := cloudimagemetadata.MetadataAttributes{
   373  		Stream:          stream,
   374  		Region:          "new-region",
   375  		Version:         "14.04",
   376  		Series:          "trusty",
   377  		Arch:            "arch",
   378  		VirtType:        "virtType-test",
   379  		RootStorageType: "rootStorageType-test"}
   380  
   381  	added := cloudimagemetadata.Metadata{attrs, 0, "1"}
   382  	s.assertRecordMetadata(c, added)
   383  	s.assertMetadataRecorded(c, attrs, added)
   384  
   385  	uniqueArches, err := s.storage.SupportedArchitectures(
   386  		cloudimagemetadata.MetadataFilter{Stream: stream, Region: region})
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	c.Assert(uniqueArches, gc.DeepEquals, []string{})
   389  }
   390  
   391  func (s *cloudImageMetadataSuite) TestSupportedArchitecturesUnmatchedStreamsAndRegions(c *gc.C) {
   392  	stream := "stream"
   393  	region := "region-test"
   394  
   395  	attrs := cloudimagemetadata.MetadataAttributes{
   396  		Stream:          "new-stream",
   397  		Region:          "new-region",
   398  		Version:         "14.04",
   399  		Series:          "trusty",
   400  		Arch:            "arch",
   401  		VirtType:        "virtType-test",
   402  		RootStorageType: "rootStorageType-test"}
   403  
   404  	added := cloudimagemetadata.Metadata{attrs, 0, "1"}
   405  	s.assertRecordMetadata(c, added)
   406  	s.assertMetadataRecorded(c, attrs, added)
   407  
   408  	uniqueArches, err := s.storage.SupportedArchitectures(
   409  		cloudimagemetadata.MetadataFilter{Stream: stream, Region: region})
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	c.Assert(uniqueArches, gc.DeepEquals, []string{})
   412  }
   413  
   414  func (s *cloudImageMetadataSuite) TestDeleteMetadata(c *gc.C) {
   415  	imageId := "ok-to-delete"
   416  	s.addTestImageMetadata(c, imageId)
   417  	s.assertDeleteMetadata(c, imageId)
   418  	s.assertNoMetadata(c)
   419  
   420  	// calling delete on it again should be a no-op
   421  	s.assertDeleteMetadata(c, imageId)
   422  	// make sure log has "nothing to delete" message
   423  	c.Assert(c.GetTestLog(), jc.Contains, "no metadata for image ID ok-to-delete to delete")
   424  }
   425  
   426  func (s *cloudImageMetadataSuite) TestDeleteDiffMetadataConcurrently(c *gc.C) {
   427  	imageId := "ok-to-delete"
   428  	s.addTestImageMetadata(c, imageId)
   429  
   430  	diffImageId := "ok-to-delete-too"
   431  	s.addTestImageMetadata(c, diffImageId)
   432  
   433  	s.assertConcurrentDelete(c, imageId, diffImageId)
   434  }
   435  
   436  func (s *cloudImageMetadataSuite) TestDeleteSameMetadataConcurrently(c *gc.C) {
   437  	imageId := "ok-to-delete"
   438  	s.addTestImageMetadata(c, imageId)
   439  
   440  	s.assertConcurrentDelete(c, imageId, imageId)
   441  }
   442  
   443  func (s *cloudImageMetadataSuite) assertConcurrentDelete(c *gc.C, imageId0, imageId1 string) {
   444  	deleteMetadata := func() {
   445  		s.assertDeleteMetadata(c, imageId0)
   446  	}
   447  	defer txntesting.SetBeforeHooks(c, s.access.runner, deleteMetadata).Check()
   448  	s.assertDeleteMetadata(c, imageId1)
   449  	s.assertNoMetadata(c)
   450  }
   451  
   452  func (s *cloudImageMetadataSuite) addTestImageMetadata(c *gc.C, imageId string) {
   453  	attrs := cloudimagemetadata.MetadataAttributes{
   454  		Stream:          "stream",
   455  		Region:          "region-test",
   456  		Version:         "14.04",
   457  		Series:          "trusty",
   458  		Arch:            "arch",
   459  		VirtType:        "virtType-test",
   460  		RootStorageType: "rootStorageType-test"}
   461  
   462  	added := cloudimagemetadata.Metadata{attrs, 0, imageId}
   463  	s.assertRecordMetadata(c, added)
   464  	s.assertMetadataRecorded(c, attrs, added)
   465  }
   466  
   467  func (s *cloudImageMetadataSuite) assertDeleteMetadata(c *gc.C, imageId string) {
   468  	err := s.storage.DeleteMetadata(imageId)
   469  	c.Assert(err, jc.ErrorIsNil)
   470  }
   471  
   472  func (s *cloudImageMetadataSuite) assertNoMetadata(c *gc.C) {
   473  	// No metadata should be in store.
   474  	// So when looking for all and none is found, err.
   475  	found, err := s.storage.FindMetadata(cloudimagemetadata.MetadataFilter{})
   476  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   477  	c.Assert(err, gc.ErrorMatches, "matching cloud image metadata not found")
   478  	c.Assert(found, gc.HasLen, 0)
   479  }
   480  
   481  type TestMongo struct {
   482  	database *mgo.Database
   483  	runner   txn.Runner
   484  }
   485  
   486  func NewTestMongo(database *mgo.Database) *TestMongo {
   487  	return &TestMongo{
   488  		database: database,
   489  		runner: txn.NewRunner(txn.RunnerParams{
   490  			Database: database,
   491  		}),
   492  	}
   493  }
   494  
   495  func (m *TestMongo) GetCollection(name string) (mongo.Collection, func()) {
   496  	return mongo.CollectionFromName(m.database, name)
   497  }
   498  
   499  func (m *TestMongo) RunTransaction(getTxn txn.TransactionSource) error {
   500  	return m.runner.Run(getTxn)
   501  }