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(®ionMetadataSuite{}) 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 }