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