github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/imagemetadata/simplestreams_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package imagemetadata_test 5 6 import ( 7 "bytes" 8 "flag" 9 "net/http" 10 "reflect" 11 "strings" 12 stdtesting "testing" 13 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 "gopkg.in/amz.v3/aws" 17 gc "gopkg.in/check.v1" 18 19 "github.com/juju/juju/environs/imagemetadata" 20 "github.com/juju/juju/environs/simplestreams" 21 sstesting "github.com/juju/juju/environs/simplestreams/testing" 22 ) 23 24 var live = flag.Bool("live", false, "Include live simplestreams tests") 25 var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data") 26 27 type liveTestData struct { 28 baseURL string 29 requireSigned bool 30 validCloudSpec simplestreams.CloudSpec 31 } 32 33 var liveUrls = map[string]liveTestData{ 34 "ec2": { 35 baseURL: imagemetadata.DefaultUbuntuBaseURL, 36 requireSigned: true, 37 validCloudSpec: simplestreams.CloudSpec{"us-east-1", aws.Regions["us-east-1"].EC2Endpoint}, 38 }, 39 "canonistack": { 40 baseURL: "https://swift.canonistack.canonical.com/v1/AUTH_a48765cc0e864be980ee21ae26aaaed4/simplestreams/data", 41 requireSigned: false, 42 validCloudSpec: simplestreams.CloudSpec{"lcy01", "https://keystone.canonistack.canonical.com:443/v1.0/"}, 43 }, 44 } 45 46 func Test(t *stdtesting.T) { 47 if *live { 48 if *vendor == "" { 49 t.Fatal("missing vendor") 50 } 51 var ok bool 52 var testData liveTestData 53 if testData, ok = liveUrls[*vendor]; !ok { 54 keys := reflect.ValueOf(liveUrls).MapKeys() 55 t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys) 56 } 57 registerLiveSimpleStreamsTests(testData.baseURL, imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 58 CloudSpec: testData.validCloudSpec, 59 Series: []string{"quantal"}, 60 Arches: []string{"amd64"}, 61 }), testData.requireSigned) 62 } 63 registerSimpleStreamsTests() 64 gc.TestingT(t) 65 } 66 67 func registerSimpleStreamsTests() { 68 gc.Suite(&simplestreamsSuite{ 69 LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{ 70 Source: simplestreams.NewURLDataSource( 71 "test roundtripper", "test:", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, false), 72 RequireSigned: false, 73 DataType: imagemetadata.ImageIds, 74 StreamsVersion: imagemetadata.CurrentStreamsVersion, 75 ValidConstraint: imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 76 CloudSpec: simplestreams.CloudSpec{ 77 Region: "us-east-1", 78 Endpoint: "https://ec2.us-east-1.amazonaws.com", 79 }, 80 Series: []string{"precise"}, 81 Arches: []string{"amd64", "arm"}, 82 }), 83 }, 84 }) 85 gc.Suite(&signedSuite{}) 86 } 87 88 func registerLiveSimpleStreamsTests(baseURL string, validImageConstraint simplestreams.LookupConstraint, requireSigned bool) { 89 gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{ 90 Source: simplestreams.NewURLDataSource("test", baseURL, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, requireSigned), 91 RequireSigned: requireSigned, 92 DataType: imagemetadata.ImageIds, 93 ValidConstraint: validImageConstraint, 94 }) 95 } 96 97 type simplestreamsSuite struct { 98 sstesting.LocalLiveSimplestreamsSuite 99 sstesting.TestDataSuite 100 } 101 102 func (s *simplestreamsSuite) SetUpSuite(c *gc.C) { 103 s.LocalLiveSimplestreamsSuite.SetUpSuite(c) 104 s.TestDataSuite.SetUpSuite(c) 105 } 106 107 func (s *simplestreamsSuite) TearDownSuite(c *gc.C) { 108 s.TestDataSuite.TearDownSuite(c) 109 s.LocalLiveSimplestreamsSuite.TearDownSuite(c) 110 } 111 112 func (s *simplestreamsSuite) TestOfficialSources(c *gc.C) { 113 origKey := imagemetadata.SetSigningPublicKey(sstesting.SignedMetadataPublicKey) 114 defer func() { 115 imagemetadata.SetSigningPublicKey(origKey) 116 }() 117 ds, err := imagemetadata.OfficialDataSources("daily") 118 c.Assert(err, jc.ErrorIsNil) 119 c.Assert(ds, gc.HasLen, 2) 120 url, err := ds[0].URL("") 121 c.Assert(err, jc.ErrorIsNil) 122 c.Assert(url, gc.Equals, "https://streams.canonical.com/juju/images/daily/") 123 c.Assert(ds[0].PublicSigningKey(), gc.Equals, sstesting.SignedMetadataPublicKey) 124 125 url, err = ds[1].URL("") 126 c.Assert(err, jc.ErrorIsNil) 127 c.Assert(url, gc.Equals, "http://cloud-images.ubuntu.com/daily/") 128 c.Assert(ds[1].PublicSigningKey(), gc.Equals, sstesting.SignedMetadataPublicKey) 129 } 130 131 var fetchTests = []struct { 132 region string 133 version string 134 arches []string 135 images []*imagemetadata.ImageMetadata 136 }{ 137 { 138 region: "us-east-1", 139 version: "12.04", 140 arches: []string{"amd64", "arm"}, 141 images: []*imagemetadata.ImageMetadata{ 142 { 143 Id: "ami-442ea674", 144 VirtType: "hvm", 145 Arch: "amd64", 146 RegionName: "us-east-1", 147 Endpoint: "https://ec2.us-east-1.amazonaws.com", 148 Storage: "ebs", 149 }, 150 { 151 Id: "ami-442ea684", 152 VirtType: "pv", 153 Arch: "amd64", 154 RegionName: "us-east-1", 155 Endpoint: "https://ec2.us-east-1.amazonaws.com", 156 Storage: "instance", 157 }, 158 { 159 Id: "ami-442ea699", 160 VirtType: "pv", 161 Arch: "arm", 162 RegionName: "us-east-1", 163 Endpoint: "https://ec2.us-east-1.amazonaws.com", 164 Storage: "ebs", 165 }, 166 }, 167 }, 168 { 169 region: "us-east-1", 170 version: "12.04", 171 arches: []string{"amd64"}, 172 images: []*imagemetadata.ImageMetadata{ 173 { 174 Id: "ami-442ea674", 175 VirtType: "hvm", 176 Arch: "amd64", 177 RegionName: "us-east-1", 178 Endpoint: "https://ec2.us-east-1.amazonaws.com", 179 Storage: "ebs", 180 }, 181 { 182 Id: "ami-442ea684", 183 VirtType: "pv", 184 Arch: "amd64", 185 RegionName: "us-east-1", 186 Endpoint: "https://ec2.us-east-1.amazonaws.com", 187 Storage: "instance", 188 }, 189 }, 190 }, 191 { 192 region: "us-east-1", 193 version: "12.04", 194 arches: []string{"arm"}, 195 images: []*imagemetadata.ImageMetadata{ 196 { 197 Id: "ami-442ea699", 198 VirtType: "pv", 199 Arch: "arm", 200 RegionName: "us-east-1", 201 Endpoint: "https://ec2.us-east-1.amazonaws.com", 202 Storage: "ebs", 203 }, 204 }, 205 }, 206 { 207 region: "us-east-1", 208 version: "12.04", 209 arches: []string{"amd64"}, 210 images: []*imagemetadata.ImageMetadata{ 211 { 212 Id: "ami-442ea674", 213 VirtType: "hvm", 214 Arch: "amd64", 215 RegionName: "us-east-1", 216 Endpoint: "https://ec2.us-east-1.amazonaws.com", 217 Storage: "ebs", 218 }, 219 { 220 Id: "ami-442ea684", 221 VirtType: "pv", 222 Arch: "amd64", 223 RegionName: "us-east-1", 224 Endpoint: "https://ec2.us-east-1.amazonaws.com", 225 Storage: "instance", 226 }, 227 }, 228 }, 229 { 230 version: "12.04", 231 arches: []string{"amd64"}, 232 images: []*imagemetadata.ImageMetadata{ 233 { 234 Id: "ami-26745463", 235 VirtType: "pv", 236 Arch: "amd64", 237 RegionName: "au-east-2", 238 Endpoint: "https://somewhere-else", 239 Storage: "ebs", 240 }, 241 { 242 Id: "ami-26745464", 243 VirtType: "pv", 244 Arch: "amd64", 245 RegionName: "au-east-1", 246 Endpoint: "https://somewhere", 247 Storage: "ebs", 248 }, 249 { 250 Id: "ami-442ea674", 251 VirtType: "hvm", 252 Arch: "amd64", 253 RegionName: "us-east-1", 254 Endpoint: "https://ec2.us-east-1.amazonaws.com", 255 Storage: "ebs", 256 }, 257 { 258 Id: "ami-442ea675", 259 VirtType: "hvm", 260 Arch: "amd64", 261 RegionAlias: "uswest3", 262 RegionName: "us-west-3", 263 Endpoint: "https://ec2.us-west-3.amazonaws.com", 264 Storage: "ebs", 265 }, 266 { 267 Id: "ami-442ea684", 268 VirtType: "pv", 269 Arch: "amd64", 270 RegionName: "us-east-1", 271 Endpoint: "https://ec2.us-east-1.amazonaws.com", 272 Storage: "instance", 273 }, 274 }, 275 }, 276 } 277 278 func (s *simplestreamsSuite) TestFetch(c *gc.C) { 279 for i, t := range fetchTests { 280 c.Logf("test %d", i) 281 cloudSpec := simplestreams.CloudSpec{t.region, "https://ec2.us-east-1.amazonaws.com"} 282 if t.region == "" { 283 cloudSpec = simplestreams.EmptyCloudSpec 284 } 285 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 286 CloudSpec: cloudSpec, 287 Series: []string{"precise"}, 288 Arches: t.arches, 289 }) 290 // Add invalid datasource and check later that resolveInfo is correct. 291 invalidSource := simplestreams.NewURLDataSource("invalid", "file://invalid", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, s.RequireSigned) 292 images, resolveInfo, err := imagemetadata.Fetch( 293 []simplestreams.DataSource{invalidSource, s.Source}, imageConstraint) 294 if !c.Check(err, jc.ErrorIsNil) { 295 continue 296 } 297 for _, testImage := range t.images { 298 testImage.Version = t.version 299 } 300 c.Check(images, gc.DeepEquals, t.images) 301 c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 302 Source: "test roundtripper", 303 Signed: s.RequireSigned, 304 IndexURL: "test:/streams/v1/index.json", 305 MirrorURL: "", 306 }) 307 } 308 } 309 310 type productSpecSuite struct{} 311 312 var _ = gc.Suite(&productSpecSuite{}) 313 314 func (s *productSpecSuite) TestIdWithDefaultStream(c *gc.C) { 315 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 316 Series: []string{"precise"}, 317 Arches: []string{"amd64"}, 318 }) 319 for _, stream := range []string{"", "released"} { 320 imageConstraint.Stream = stream 321 ids, err := imageConstraint.ProductIds() 322 c.Assert(err, jc.ErrorIsNil) 323 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.cloud:server:12.04:amd64"}) 324 } 325 } 326 327 func (s *productSpecSuite) TestId(c *gc.C) { 328 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 329 Series: []string{"precise"}, 330 Arches: []string{"amd64"}, 331 Stream: "daily", 332 }) 333 ids, err := imageConstraint.ProductIds() 334 c.Assert(err, jc.ErrorIsNil) 335 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.cloud.daily:server:12.04:amd64"}) 336 } 337 338 func (s *productSpecSuite) TestIdMultiArch(c *gc.C) { 339 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 340 Series: []string{"precise"}, 341 Arches: []string{"amd64", "i386"}, 342 Stream: "daily", 343 }) 344 ids, err := imageConstraint.ProductIds() 345 c.Assert(err, jc.ErrorIsNil) 346 c.Assert(ids, gc.DeepEquals, []string{ 347 "com.ubuntu.cloud.daily:server:12.04:amd64", 348 "com.ubuntu.cloud.daily:server:12.04:i386"}) 349 } 350 351 type signedSuite struct { 352 origKey string 353 } 354 355 func (s *signedSuite) SetUpSuite(c *gc.C) { 356 var imageData = map[string]string{ 357 "/unsigned/streams/v1/index.json": unsignedIndex, 358 "/unsigned/streams/v1/image_metadata.json": unsignedProduct, 359 } 360 361 // Set up some signed data from the unsigned data. 362 // Overwrite the product path to use the sjson suffix. 363 rawUnsignedIndex := strings.Replace( 364 unsignedIndex, "streams/v1/image_metadata.json", "streams/v1/image_metadata.sjson", -1) 365 r := bytes.NewReader([]byte(rawUnsignedIndex)) 366 signedData, err := simplestreams.Encode( 367 r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 368 c.Assert(err, jc.ErrorIsNil) 369 imageData["/signed/streams/v1/index.sjson"] = string(signedData) 370 371 // Replace the image id in the unsigned data with a different one so we can test that the right 372 // image id is used. 373 rawUnsignedProduct := strings.Replace( 374 unsignedProduct, "ami-26745463", "ami-123456", -1) 375 r = bytes.NewReader([]byte(rawUnsignedProduct)) 376 signedData, err = simplestreams.Encode( 377 r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 378 c.Assert(err, jc.ErrorIsNil) 379 imageData["/signed/streams/v1/image_metadata.sjson"] = string(signedData) 380 sstesting.SetRoundTripperFiles(imageData, map[string]int{"test://unauth": http.StatusUnauthorized}) 381 s.origKey = imagemetadata.SetSigningPublicKey(sstesting.SignedMetadataPublicKey) 382 } 383 384 func (s *signedSuite) TearDownSuite(c *gc.C) { 385 sstesting.SetRoundTripperFiles(nil, nil) 386 imagemetadata.SetSigningPublicKey(s.origKey) 387 } 388 389 func (s *signedSuite) TestSignedImageMetadata(c *gc.C) { 390 signedSource := simplestreams.NewURLSignedDataSource("test", "test://host/signed", sstesting.SignedMetadataPublicKey, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true) 391 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 392 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 393 Series: []string{"precise"}, 394 Arches: []string{"amd64"}, 395 }) 396 images, resolveInfo, err := imagemetadata.Fetch([]simplestreams.DataSource{signedSource}, imageConstraint) 397 c.Assert(err, jc.ErrorIsNil) 398 c.Assert(len(images), gc.Equals, 1) 399 c.Assert(images[0].Id, gc.Equals, "ami-123456") 400 c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 401 Source: "test", 402 Signed: true, 403 IndexURL: "test://host/signed/streams/v1/index.sjson", 404 MirrorURL: "", 405 }) 406 } 407 408 func (s *signedSuite) TestSignedImageMetadataInvalidSignature(c *gc.C) { 409 signedSource := simplestreams.NewURLDataSource("test", "test://host/signed", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true) 410 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 411 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 412 Series: []string{"precise"}, 413 Arches: []string{"amd64"}, 414 }) 415 imagemetadata.SetSigningPublicKey(s.origKey) 416 _, _, err := imagemetadata.Fetch([]simplestreams.DataSource{signedSource}, imageConstraint) 417 c.Assert(err, gc.ErrorMatches, "cannot read index data.*") 418 } 419 420 var unsignedIndex = ` 421 { 422 "index": { 423 "com.ubuntu.cloud:released:precise": { 424 "updated": "Wed, 01 May 2013 13:31:26 +0000", 425 "clouds": [ 426 { 427 "region": "us-east-1", 428 "endpoint": "https://ec2.us-east-1.amazonaws.com" 429 } 430 ], 431 "cloudname": "aws", 432 "datatype": "image-ids", 433 "format": "products:1.0", 434 "products": [ 435 "com.ubuntu.cloud:server:12.04:amd64" 436 ], 437 "path": "streams/v1/image_metadata.json" 438 } 439 }, 440 "updated": "Wed, 01 May 2013 13:31:26 +0000", 441 "format": "index:1.0" 442 } 443 ` 444 var unsignedProduct = ` 445 { 446 "updated": "Wed, 01 May 2013 13:31:26 +0000", 447 "content_id": "com.ubuntu.cloud:released:aws", 448 "products": { 449 "com.ubuntu.cloud:server:12.04:amd64": { 450 "release": "precise", 451 "version": "12.04", 452 "arch": "amd64", 453 "region": "us-east-1", 454 "endpoint": "https://somewhere", 455 "versions": { 456 "20121218": { 457 "region": "us-east-1", 458 "endpoint": "https://somewhere-else", 459 "items": { 460 "usww1pe": { 461 "root_store": "ebs", 462 "virt": "pv", 463 "id": "ami-26745463" 464 } 465 }, 466 "pubname": "ubuntu-precise-12.04-amd64-server-20121218", 467 "label": "release" 468 } 469 } 470 } 471 }, 472 "format": "products:1.0" 473 } 474 `