github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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(®ionMetadataSuite{}) 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 }