github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/imagemetadata/updatefrompublished_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package imagemetadata_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/config"
    17  	"github.com/juju/juju/environs/imagemetadata"
    18  	imagetesting "github.com/juju/juju/environs/imagemetadata/testing"
    19  	"github.com/juju/juju/environs/simplestreams"
    20  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    21  	"github.com/juju/juju/juju/keys"
    22  	"github.com/juju/juju/provider/dummy"
    23  	"github.com/juju/juju/state/cloudimagemetadata"
    24  	"github.com/juju/juju/testing"
    25  )
    26  
    27  // useTestImageData causes the given content to be served when published metadata is requested.
    28  func useTestImageData(c *gc.C, files map[string]string) {
    29  	if files != nil {
    30  		sstesting.SetRoundTripperFiles(sstesting.AddSignedFiles(c, files), nil)
    31  	} else {
    32  		sstesting.SetRoundTripperFiles(nil, nil)
    33  	}
    34  }
    35  
    36  // TODO (anastasiamac 2015-09-04) This metadata is so verbose.
    37  // Need to generate the text by creating a struct and marshalling it.
    38  var testImagesData = map[string]string{
    39  	"/streams/v1/index.json": `
    40  		{
    41  		 "index": {
    42  		  "com.ubuntu.cloud:released:aws": {
    43  		   "updated": "Wed, 01 May 2013 13:31:26 +0000",
    44  		   "clouds": [
    45  			{
    46  			 "region": "dummy_region",
    47  			 "endpoint": "https://anywhere"
    48  			},
    49  			{
    50  			 "region": "another_dummy_region",
    51  			 "endpoint": ""
    52  			}
    53  		   ],
    54  		   "cloudname": "aws",
    55  		   "datatype": "image-ids",
    56  		   "format": "products:1.0",
    57  		   "products": [
    58  			"com.ubuntu.cloud:server:12.04:amd64",
    59  			"com.ubuntu.cloud:server:14.04:amd64"
    60  		   ],
    61  		   "path": "streams/v1/image_metadata.json"
    62  		   }
    63  		  },
    64  		 "updated": "Wed, 27 May 2015 13:31:26 +0000",
    65  		 "format": "index:1.0"
    66  		}
    67  `,
    68  	"/streams/v1/image_metadata.json": `
    69  {
    70   "updated": "Wed, 27 May 2015 13:31:26 +0000",
    71   "content_id": "com.ubuntu.cloud:released:aws",
    72   "products": {
    73    "com.ubuntu.cloud:server:14.04:amd64": {
    74     "release": "trusty",
    75     "version": "14.04",
    76     "arch": "amd64",
    77     "versions": {
    78      "20140118": {
    79       "items": {
    80        "nzww1pe": {
    81         "root_store": "ebs",
    82         "virt": "pv",
    83         "crsn": "da1",
    84         "id": "ami-36745463"
    85        },
    86        "nzww1pe2": {
    87         "root_store": "ebs",
    88         "virt": "pv",
    89         "crsn": "da2",
    90         "id": "ami-1136745463"
    91        }
    92       },
    93       "pubname": "ubuntu-trusty-14.04-amd64-server-20140118",
    94       "label": "release"
    95      }
    96     }
    97    },
    98    "com.ubuntu.cloud:server:12.04:amd64": {
    99     "release": "precise",
   100     "version": "12.04",
   101     "arch": "amd64",
   102     "versions": {
   103      "20121218": {
   104       "items": {
   105        "usww1pe": {
   106         "root_store": "ebs",
   107         "virt": "pv",
   108         "crsn": "da1",
   109         "id": "ami-26745463"
   110        },
   111        "usww1pe2": {
   112         "root_store": "ebs",
   113         "virt": "pv",
   114         "crsn": "da2",
   115         "id": "ami-1126745463"
   116        }
   117       },
   118       "pubname": "ubuntu-precise-12.04-amd64-server-20121218",
   119       "label": "release"
   120      }
   121     }
   122    }
   123   },
   124   "_aliases": {
   125    "crsn": {
   126     "da1": {
   127      "region": "dummy_region",
   128      "endpoint": "https://anywhere"
   129     },
   130     "da2": {
   131      "region": "another_dummy_region",
   132      "endpoint": ""
   133     }
   134    }
   135   },
   136   "format": "products:1.0"
   137  }
   138  `,
   139  }
   140  
   141  var _ = gc.Suite(&imageMetadataUpdateSuite{})
   142  
   143  type imageMetadataUpdateSuite struct {
   144  	baseImageMetadataSuite
   145  }
   146  
   147  func (s *imageMetadataUpdateSuite) SetUpSuite(c *gc.C) {
   148  	s.BaseSuite.SetUpSuite(c)
   149  	imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "test:")
   150  	useTestImageData(c, testImagesData)
   151  }
   152  
   153  func (s *imageMetadataUpdateSuite) TearDownSuite(c *gc.C) {
   154  	useTestImageData(c, nil)
   155  	s.BaseSuite.TearDownSuite(c)
   156  }
   157  
   158  func (s *imageMetadataUpdateSuite) TestUpdateFromPublishedImagesForProviderWithNoRegions(c *gc.C) {
   159  	// This will save all available image metadata.
   160  	saved := []cloudimagemetadata.Metadata{}
   161  
   162  	// testingEnvConfig prepares an environment configuration using
   163  	// the dummy provider since it doesn't implement simplestreams.HasRegion.
   164  	s.state.environConfig = func() (*config.Config, error) {
   165  		return config.New(config.UseDefaults, dummy.SampleConfig())
   166  	}
   167  
   168  	s.state.saveMetadata = func(m []cloudimagemetadata.Metadata) error {
   169  		saved = append(saved, m...)
   170  		return nil
   171  	}
   172  
   173  	err := s.api.UpdateFromPublishedImages()
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	c.Assert(saved, jc.SameContents, []cloudimagemetadata.Metadata{})
   176  }
   177  
   178  // mockConfig returns a configuration for the usage of the
   179  // mock provider below.
   180  func mockConfig() testing.Attrs {
   181  	return dummy.SampleConfig().Merge(testing.Attrs{
   182  		"type": "mock",
   183  	})
   184  }
   185  
   186  // mockEnviron is an environment without networking support.
   187  type mockEnviron struct {
   188  	environs.Environ
   189  }
   190  
   191  func (e mockEnviron) Config() *config.Config {
   192  	cfg, err := config.New(config.NoDefaults, mockConfig())
   193  	if err != nil {
   194  		panic("invalid configuration for testing")
   195  	}
   196  	return cfg
   197  }
   198  
   199  // Region is specified in the HasRegion interface.
   200  func (e *mockEnviron) Region() (simplestreams.CloudSpec, error) {
   201  	return simplestreams.CloudSpec{
   202  		Region:   "dummy_region",
   203  		Endpoint: "https://anywhere",
   204  	}, nil
   205  }
   206  
   207  var _ = gc.Suite(&regionMetadataSuite{})
   208  
   209  type regionMetadataSuite struct {
   210  	baseImageMetadataSuite
   211  
   212  	env *mockEnviron
   213  
   214  	saved    []cloudimagemetadata.Metadata
   215  	expected []cloudimagemetadata.Metadata
   216  }
   217  
   218  func (s *regionMetadataSuite) SetUpSuite(c *gc.C) {
   219  	s.baseImageMetadataSuite.SetUpSuite(c)
   220  
   221  	s.env = &mockEnviron{}
   222  
   223  	s.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey)
   224  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
   225  	// Prepare mock http transport for overriding metadata and images output in tests.
   226  	useTestImageData(c, testImagesData)
   227  }
   228  
   229  func (s *regionMetadataSuite) TearDownSuite(c *gc.C) {
   230  	useTestImageData(c, nil)
   231  	s.baseImageMetadataSuite.TearDownSuite(c)
   232  }
   233  
   234  func (s *regionMetadataSuite) SetUpTest(c *gc.C) {
   235  	s.baseImageMetadataSuite.SetUpTest(c)
   236  
   237  	s.saved = nil
   238  	s.expected = nil
   239  }
   240  
   241  func (s *regionMetadataSuite) setExpectations(c *gc.C) {
   242  	// This will only save image metadata specific to provider cloud spec.
   243  	s.expected = []cloudimagemetadata.Metadata{
   244  		cloudimagemetadata.Metadata{
   245  			MetadataAttributes: cloudimagemetadata.MetadataAttributes{
   246  				RootStorageType: "ebs",
   247  				VirtType:        "pv",
   248  				Arch:            "amd64",
   249  				Series:          "trusty",
   250  				Region:          "dummy_region",
   251  				Source:          "default cloud images",
   252  				Stream:          "released"},
   253  			Priority: 10,
   254  			ImageId:  "ami-36745463",
   255  		},
   256  		cloudimagemetadata.Metadata{
   257  			MetadataAttributes: cloudimagemetadata.MetadataAttributes{
   258  				RootStorageType: "ebs",
   259  				VirtType:        "pv",
   260  				Arch:            "amd64",
   261  				Series:          "precise",
   262  				Region:          "dummy_region",
   263  				Source:          "default cloud images",
   264  				Stream:          "released"},
   265  			Priority: 10,
   266  			ImageId:  "ami-26745463",
   267  		},
   268  	}
   269  
   270  	// testingEnvConfig prepares an environment configuration using
   271  	// mock provider which impelements simplestreams.HasRegion interface.
   272  	s.state.environConfig = func() (*config.Config, error) {
   273  		return s.env.Config(), nil
   274  	}
   275  
   276  	s.state.saveMetadata = func(m []cloudimagemetadata.Metadata) error {
   277  		s.saved = append(s.saved, m...)
   278  		return nil
   279  	}
   280  }
   281  
   282  func (s *regionMetadataSuite) checkStoredPublished(c *gc.C) {
   283  	err := s.api.UpdateFromPublishedImages()
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	s.assertCalls(c, "ControllerTag", "ControllerTag", environConfig, saveMetadata)
   286  	c.Assert(s.saved, jc.SameContents, s.expected)
   287  }
   288  
   289  func (s *regionMetadataSuite) TestUpdateFromPublishedImagesForProviderWithRegions(c *gc.C) {
   290  	// This will only save image metadata specific to provider cloud spec.
   291  	s.setExpectations(c)
   292  	s.checkStoredPublished(c)
   293  }
   294  
   295  const (
   296  	indexContent = `{
   297      "index": {
   298          "com.ubuntu.cloud:%v": {
   299              "updated": "Fri, 17 Jul 2015 13:42:48 +1000",
   300              "format": "products:1.0",
   301              "datatype": "image-ids",
   302              "cloudname": "custom",
   303              "clouds": [
   304                  {
   305                      "region": "%v",
   306                      "endpoint": "%v"
   307                  }
   308              ],
   309              "path": "streams/v1/products.json",
   310              "products": [
   311                  "com.ubuntu.cloud:server:14.04:%v"
   312              ]
   313          }
   314      },
   315      "updated": "Fri, 17 Jul 2015 13:42:48 +1000",
   316      "format": "index:1.0"
   317  }`
   318  
   319  	productContent = `{
   320      "products": {
   321          "com.ubuntu.cloud:server:14.04:%v": {
   322              "version": "14.04",
   323              "arch": "%v",
   324              "versions": {
   325                  "20151707": {
   326                      "items": {
   327                          "%v": {
   328                              "id": "%v",
   329                              "root_store": "%v",
   330                              "virt": "%v",
   331                              "region": "%v",
   332                              "endpoint": "%v"
   333                          }
   334                      }
   335                  }
   336              }
   337          }
   338       },
   339      "updated": "Fri, 17 Jul 2015 13:42:48 +1000",
   340      "format": "products:1.0",
   341      "content_id": "com.ubuntu.cloud:custom"
   342  }`
   343  )
   344  
   345  func writeTempFiles(c *gc.C, metadataDir string, expected []struct{ path, content string }) {
   346  	for _, pair := range expected {
   347  		path := filepath.Join(metadataDir, pair.path)
   348  		err := os.MkdirAll(filepath.Dir(path), 0755)
   349  		c.Assert(err, jc.ErrorIsNil)
   350  		err = ioutil.WriteFile(path, []byte(pair.content), 0644)
   351  		c.Assert(err, jc.ErrorIsNil)
   352  	}
   353  }
   354  
   355  func (s *regionMetadataSuite) createTestDataSource(c *gc.C, dsID string, files []struct{ path, content string }) int {
   356  	metadataDir := c.MkDir()
   357  	writeTempFiles(c, metadataDir, files)
   358  
   359  	ds := simplestreams.NewURLDataSource(dsID, "file://"+metadataDir, false, 20, false)
   360  	environs.RegisterImageDataSourceFunc(dsID, func(environs.Environ) (simplestreams.DataSource, error) {
   361  		return ds, nil
   362  	})
   363  	s.AddCleanup(func(*gc.C) {
   364  		environs.UnregisterImageDataSourceFunc(dsID)
   365  	})
   366  	return ds.Priority()
   367  }
   368  
   369  func (s *regionMetadataSuite) setupMetadata(c *gc.C, dsID string, cloudSpec simplestreams.CloudSpec, metadata cloudimagemetadata.Metadata) int {
   370  	files := []struct{ path, content string }{{
   371  		path:    "streams/v1/index.json",
   372  		content: fmt.Sprintf(indexContent, metadata.Source, metadata.Region, cloudSpec.Endpoint, metadata.Arch),
   373  	}, {
   374  		path:    "streams/v1/products.json",
   375  		content: fmt.Sprintf(productContent, metadata.Arch, metadata.Arch, metadata.ImageId, metadata.ImageId, metadata.RootStorageType, metadata.VirtType, metadata.Region, cloudSpec.Endpoint),
   376  	}, {
   377  		path:    "wayward/file.txt",
   378  		content: "ghi",
   379  	}}
   380  	return s.createTestDataSource(c, dsID, files)
   381  }
   382  
   383  func (s *regionMetadataSuite) TestUpdateFromPublishedImagesMultipleDS(c *gc.C) {
   384  	s.setExpectations(c)
   385  
   386  	// register another data source
   387  	cloudSpec, err := s.env.Region()
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	anotherDS := "second ds"
   390  
   391  	m1 := s.expected[0]
   392  	priority := s.setupMetadata(c, anotherDS, cloudSpec, m1)
   393  	m1.Source = anotherDS
   394  	m1.Priority = priority
   395  	m1.Stream = "released"
   396  
   397  	s.expected = append(s.expected, m1)
   398  
   399  	err = s.api.UpdateFromPublishedImages()
   400  	c.Assert(err, jc.ErrorIsNil)
   401  	s.assertCalls(c, "ControllerTag", "ControllerTag", environConfig, saveMetadata, "ControllerTag", environConfig, saveMetadata)
   402  	c.Assert(s.saved, jc.SameContents, s.expected)
   403  }
   404  
   405  func (s *regionMetadataSuite) TestUpdateFromPublishedImagesMultipleDSError(c *gc.C) {
   406  	s.setExpectations(c)
   407  
   408  	// register another data source that would error
   409  	files := []struct{ path, content string }{{
   410  		path:    "wayward/file.txt",
   411  		content: "ghi",
   412  	}}
   413  	s.createTestDataSource(c, "error in ds", files)
   414  
   415  	s.checkStoredPublished(c)
   416  }