github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "fmt" 10 "io" 11 "net/http" 12 "net/http/httptest" 13 "reflect" 14 "strings" 15 stdtesting "testing" 16 17 "github.com/aws/aws-sdk-go-v2/service/ec2" 18 "github.com/juju/errors" 19 jc "github.com/juju/testing/checkers" 20 gc "gopkg.in/check.v1" 21 22 "github.com/juju/juju/environs/imagemetadata" 23 "github.com/juju/juju/environs/simplestreams" 24 sstesting "github.com/juju/juju/environs/simplestreams/testing" 25 "github.com/juju/juju/juju/keys" 26 ) 27 28 var live = flag.Bool("live", false, "Include live simplestreams tests") 29 var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data") 30 31 type liveTestData struct { 32 baseURL string 33 requireSigned bool 34 validCloudSpec simplestreams.CloudSpec 35 } 36 37 func getLiveURLs() (map[string]liveTestData, error) { 38 resolver := ec2.NewDefaultEndpointResolver() 39 ep, err := resolver.ResolveEndpoint("us-east-1", ec2.EndpointResolverOptions{}) 40 if err != nil { 41 return nil, errors.Trace(err) 42 } 43 44 return map[string]liveTestData{ 45 "ec2": { 46 baseURL: imagemetadata.DefaultUbuntuBaseURL, 47 requireSigned: true, 48 validCloudSpec: simplestreams.CloudSpec{ 49 Region: "us-east-1", 50 Endpoint: ep.URL, 51 }, 52 }, 53 "canonistack": { 54 baseURL: "https://swift.canonistack.canonical.com/v1/AUTH_a48765cc0e864be980ee21ae26aaaed4/simplestreams/data", 55 requireSigned: false, 56 validCloudSpec: simplestreams.CloudSpec{ 57 Region: "lcy01", 58 Endpoint: "https://keystone.canonistack.canonical.com:443/v1.0/", 59 }, 60 }, 61 }, nil 62 } 63 64 func Test(t *stdtesting.T) { 65 if *live { 66 if *vendor == "" { 67 t.Fatal("missing vendor") 68 } 69 var ok bool 70 var testData liveTestData 71 liveURLs, err := getLiveURLs() 72 if err != nil { 73 t.Fatalf(err.Error()) 74 } 75 if testData, ok = liveURLs[*vendor]; !ok { 76 keys := reflect.ValueOf(liveURLs).MapKeys() 77 t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys) 78 } 79 cons, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 80 CloudSpec: testData.validCloudSpec, 81 Releases: []string{"12.10"}, 82 Arches: []string{"amd64"}, 83 }) 84 if err != nil { 85 t.Fatalf(err.Error()) 86 } 87 registerLiveSimpleStreamsTests(testData.baseURL, cons, testData.requireSigned) 88 } 89 registerSimpleStreamsTests(t) 90 gc.TestingT(t) 91 } 92 93 func registerSimpleStreamsTests(t *stdtesting.T) { 94 cons, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 95 CloudSpec: simplestreams.CloudSpec{ 96 Region: "us-east-1", 97 Endpoint: "https://ec2.us-east-1.amazonaws.com", 98 }, 99 Releases: []string{"12.04"}, 100 Arches: []string{"amd64", "arm"}, 101 }) 102 if err != nil { 103 t.Fatalf(err.Error()) 104 } 105 gc.Suite(&simplestreamsSuite{ 106 LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{ 107 Source: sstesting.VerifyDefaultCloudDataSource("test roundtripper", "test:"), 108 RequireSigned: false, 109 DataType: imagemetadata.ImageIds, 110 StreamsVersion: imagemetadata.CurrentStreamsVersion, 111 ValidConstraint: cons, 112 }, 113 }) 114 gc.Suite(&signedSuite{}) 115 } 116 117 func registerLiveSimpleStreamsTests(baseURL string, validImageConstraint simplestreams.LookupConstraint, requireSigned bool) { 118 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 119 gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{ 120 Source: ss.NewDataSource(simplestreams.Config{ 121 Description: "test", 122 BaseURL: baseURL, 123 HostnameVerification: true, 124 Priority: simplestreams.DEFAULT_CLOUD_DATA, 125 RequireSigned: requireSigned, 126 }), 127 RequireSigned: requireSigned, 128 DataType: imagemetadata.ImageIds, 129 ValidConstraint: validImageConstraint, 130 }) 131 } 132 133 type simplestreamsSuite struct { 134 sstesting.LocalLiveSimplestreamsSuite 135 sstesting.TestDataSuite 136 } 137 138 func (s *simplestreamsSuite) SetUpSuite(c *gc.C) { 139 s.LocalLiveSimplestreamsSuite.SetUpSuite(c) 140 s.TestDataSuite.SetUpSuite(c) 141 } 142 143 func (s *simplestreamsSuite) TearDownSuite(c *gc.C) { 144 s.TestDataSuite.TearDownSuite(c) 145 s.LocalLiveSimplestreamsSuite.TearDownSuite(c) 146 } 147 148 func (s *simplestreamsSuite) TestOfficialSources(c *gc.C) { 149 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 150 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 151 origKey := imagemetadata.SetSigningPublicKey(sstesting.SignedMetadataPublicKey) 152 defer func() { 153 imagemetadata.SetSigningPublicKey(origKey) 154 }() 155 ds, err := imagemetadata.OfficialDataSources(ss, "daily") 156 c.Assert(err, jc.ErrorIsNil) 157 c.Assert(ds, gc.HasLen, 1) 158 url, err := ds[0].URL("") 159 c.Assert(err, jc.ErrorIsNil) 160 c.Assert(url, gc.Equals, "http://cloud-images.ubuntu.com/daily/") 161 c.Assert(ds[0].PublicSigningKey(), gc.Equals, sstesting.SignedMetadataPublicKey) 162 } 163 164 var fetchTests = []struct { 165 region string 166 version string 167 arches []string 168 images []*imagemetadata.ImageMetadata 169 }{ 170 { 171 region: "us-east-1", 172 version: "12.04", 173 arches: []string{"amd64", "arm"}, 174 images: []*imagemetadata.ImageMetadata{ 175 { 176 Id: "ami-442ea674", 177 VirtType: "hvm", 178 Arch: "amd64", 179 RegionName: "us-east-1", 180 Endpoint: "https://ec2.us-east-1.amazonaws.com", 181 Storage: "ebs", 182 }, 183 { 184 Id: "ami-442ea684", 185 VirtType: "pv", 186 Arch: "amd64", 187 RegionName: "us-east-1", 188 Endpoint: "https://ec2.us-east-1.amazonaws.com", 189 Storage: "instance", 190 }, 191 { 192 Id: "ami-442ea699", 193 VirtType: "pv", 194 Arch: "arm", 195 RegionName: "us-east-1", 196 Endpoint: "https://ec2.us-east-1.amazonaws.com", 197 Storage: "ebs", 198 }, 199 }, 200 }, 201 { 202 region: "us-east-1", 203 version: "12.04", 204 arches: []string{"amd64"}, 205 images: []*imagemetadata.ImageMetadata{ 206 { 207 Id: "ami-442ea674", 208 VirtType: "hvm", 209 Arch: "amd64", 210 RegionName: "us-east-1", 211 Endpoint: "https://ec2.us-east-1.amazonaws.com", 212 Storage: "ebs", 213 }, 214 { 215 Id: "ami-442ea684", 216 VirtType: "pv", 217 Arch: "amd64", 218 RegionName: "us-east-1", 219 Endpoint: "https://ec2.us-east-1.amazonaws.com", 220 Storage: "instance", 221 }, 222 }, 223 }, 224 { 225 region: "us-east-1", 226 version: "12.04", 227 arches: []string{"arm"}, 228 images: []*imagemetadata.ImageMetadata{ 229 { 230 Id: "ami-442ea699", 231 VirtType: "pv", 232 Arch: "arm", 233 RegionName: "us-east-1", 234 Endpoint: "https://ec2.us-east-1.amazonaws.com", 235 Storage: "ebs", 236 }, 237 }, 238 }, 239 { 240 region: "us-east-1", 241 version: "12.04", 242 arches: []string{"amd64"}, 243 images: []*imagemetadata.ImageMetadata{ 244 { 245 Id: "ami-442ea674", 246 VirtType: "hvm", 247 Arch: "amd64", 248 RegionName: "us-east-1", 249 Endpoint: "https://ec2.us-east-1.amazonaws.com", 250 Storage: "ebs", 251 }, 252 { 253 Id: "ami-442ea684", 254 VirtType: "pv", 255 Arch: "amd64", 256 RegionName: "us-east-1", 257 Endpoint: "https://ec2.us-east-1.amazonaws.com", 258 Storage: "instance", 259 }, 260 }, 261 }, 262 { 263 version: "12.04", 264 arches: []string{"amd64"}, 265 images: []*imagemetadata.ImageMetadata{ 266 { 267 Id: "ami-26745463", 268 VirtType: "pv", 269 Arch: "amd64", 270 RegionName: "au-east-2", 271 Endpoint: "https://somewhere-else", 272 Storage: "ebs", 273 }, 274 { 275 Id: "ami-26745464", 276 VirtType: "pv", 277 Arch: "amd64", 278 RegionName: "au-east-1", 279 Endpoint: "https://somewhere", 280 Storage: "ebs", 281 }, 282 { 283 Id: "ami-442ea674", 284 VirtType: "hvm", 285 Arch: "amd64", 286 RegionName: "us-east-1", 287 Endpoint: "https://ec2.us-east-1.amazonaws.com", 288 Storage: "ebs", 289 }, 290 { 291 Id: "ami-442ea675", 292 VirtType: "hvm", 293 Arch: "amd64", 294 RegionAlias: "uswest3", 295 RegionName: "us-west-3", 296 Endpoint: "https://ec2.us-west-3.amazonaws.com", 297 Storage: "ebs", 298 }, 299 { 300 Id: "ami-442ea684", 301 VirtType: "pv", 302 Arch: "amd64", 303 RegionName: "us-east-1", 304 Endpoint: "https://ec2.us-east-1.amazonaws.com", 305 Storage: "instance", 306 }, 307 }, 308 }, 309 } 310 311 func (s *simplestreamsSuite) TestFetch(c *gc.C) { 312 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 313 for i, t := range fetchTests { 314 c.Logf("test %d", i) 315 cloudSpec := simplestreams.CloudSpec{ 316 Region: t.region, 317 Endpoint: "https://ec2.us-east-1.amazonaws.com", 318 } 319 if t.region == "" { 320 cloudSpec = simplestreams.EmptyCloudSpec 321 } 322 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 323 CloudSpec: cloudSpec, 324 Releases: []string{"12.04"}, 325 Arches: t.arches, 326 }) 327 c.Assert(err, jc.ErrorIsNil) 328 // Add invalid datasource and check later that resolveInfo is correct. 329 invalidSource := sstesting.InvalidDataSource(s.RequireSigned) 330 images, resolveInfo, err := imagemetadata.Fetch(ss, 331 []simplestreams.DataSource{invalidSource, s.Source}, imageConstraint) 332 if !c.Check(err, jc.ErrorIsNil) { 333 continue 334 } 335 for _, testImage := range t.images { 336 testImage.Version = t.version 337 } 338 c.Check(images, gc.DeepEquals, t.images) 339 c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 340 Source: "test roundtripper", 341 Signed: s.RequireSigned, 342 IndexURL: "test:/streams/v1/index.json", 343 MirrorURL: "", 344 }) 345 } 346 } 347 348 type productSpecSuite struct{} 349 350 var _ = gc.Suite(&productSpecSuite{}) 351 352 func (s *productSpecSuite) TestIdWithDefaultStream(c *gc.C) { 353 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 354 Releases: []string{"12.04"}, 355 Arches: []string{"amd64"}, 356 }) 357 c.Assert(err, jc.ErrorIsNil) 358 for _, stream := range []string{"", "released"} { 359 imageConstraint.Stream = stream 360 ids, err := imageConstraint.ProductIds() 361 c.Assert(err, jc.ErrorIsNil) 362 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.cloud:server:12.04:amd64"}) 363 } 364 } 365 366 func (s *productSpecSuite) TestId(c *gc.C) { 367 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 368 Releases: []string{"12.04"}, 369 Arches: []string{"amd64"}, 370 Stream: "daily", 371 }) 372 c.Assert(err, jc.ErrorIsNil) 373 ids, err := imageConstraint.ProductIds() 374 c.Assert(err, jc.ErrorIsNil) 375 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.cloud.daily:server:12.04:amd64"}) 376 } 377 378 func (s *productSpecSuite) TestIdMultiArch(c *gc.C) { 379 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 380 Releases: []string{"12.04"}, 381 Arches: []string{"amd64", "arm64"}, 382 Stream: "daily", 383 }) 384 c.Assert(err, jc.ErrorIsNil) 385 ids, err := imageConstraint.ProductIds() 386 c.Assert(err, jc.ErrorIsNil) 387 c.Assert(ids, gc.DeepEquals, []string{ 388 "com.ubuntu.cloud.daily:server:12.04:amd64", 389 "com.ubuntu.cloud.daily:server:12.04:arm64"}) 390 } 391 392 type signedSuite struct { 393 origKey string 394 server *httptest.Server 395 } 396 397 func (s *signedSuite) SetUpSuite(_ *gc.C) { 398 s.origKey = imagemetadata.SetSigningPublicKey(sstesting.SignedMetadataPublicKey) 399 s.server = httptest.NewServer(&sstreamsHandler{}) 400 } 401 402 func (s *signedSuite) TearDownSuite(_ *gc.C) { 403 s.server.Close() 404 imagemetadata.SetSigningPublicKey(s.origKey) 405 } 406 407 func (s *signedSuite) TestSignedImageMetadata(c *gc.C) { 408 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 409 signedSource := simplestreams.NewDataSource( 410 simplestreams.Config{ 411 Description: "test", 412 BaseURL: fmt.Sprintf("%s/signed", s.server.URL), 413 PublicSigningKey: sstesting.SignedMetadataPublicKey, 414 HostnameVerification: true, 415 Priority: simplestreams.DEFAULT_CLOUD_DATA, 416 RequireSigned: true, 417 }, 418 ) 419 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 420 CloudSpec: simplestreams.CloudSpec{ 421 Region: "us-east-1", 422 Endpoint: "https://ec2.us-east-1.amazonaws.com", 423 }, 424 Releases: []string{"12.04"}, 425 Arches: []string{"amd64"}, 426 }) 427 c.Assert(err, jc.ErrorIsNil) 428 images, resolveInfo, err := imagemetadata.Fetch(ss, []simplestreams.DataSource{signedSource}, imageConstraint) 429 c.Assert(err, jc.ErrorIsNil) 430 c.Assert(len(images), gc.Equals, 1) 431 c.Assert(images[0].Id, gc.Equals, "ami-123456") 432 c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 433 Source: "test", 434 Signed: true, 435 IndexURL: fmt.Sprintf("%s/signed/streams/v1/index.sjson", s.server.URL), 436 MirrorURL: "", 437 }) 438 } 439 440 func (s *signedSuite) TestSignedImageMetadataInvalidSignature(c *gc.C) { 441 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 442 signedSource := simplestreams.NewDataSource(simplestreams.Config{ 443 Description: "test", 444 BaseURL: fmt.Sprintf("%s/signed", s.server.URL), 445 HostnameVerification: true, 446 Priority: simplestreams.DEFAULT_CLOUD_DATA, 447 RequireSigned: true, 448 }) 449 imageConstraint, err := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 450 CloudSpec: simplestreams.CloudSpec{ 451 Region: "us-east-1", 452 Endpoint: "https://ec2.us-east-1.amazonaws.com", 453 }, 454 Releases: []string{"12.04"}, 455 Arches: []string{"amd64"}, 456 }) 457 c.Assert(err, jc.ErrorIsNil) 458 imagemetadata.SetSigningPublicKey(s.origKey) 459 _, _, err = imagemetadata.Fetch(ss, []simplestreams.DataSource{signedSource}, imageConstraint) 460 c.Assert(err, gc.ErrorMatches, "cannot read index data.*") 461 } 462 463 type sstreamsHandler struct{} 464 465 func (h *sstreamsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 466 switch r.URL.Path { 467 case "/unsigned/streams/v1/index.json": 468 w.Header().Set("Content-Type", "application/json") 469 w.WriteHeader(http.StatusOK) 470 _, _ = io.WriteString(w, unsignedIndex) 471 case "/unsigned/streams/v1/image_metadata.json": 472 w.Header().Set("Content-Type", "application/json") 473 w.WriteHeader(http.StatusOK) 474 _, _ = io.WriteString(w, unsignedProduct) 475 case "/signed/streams/v1/image_metadata.sjson": 476 w.Header().Set("Content-Type", "application/json") 477 w.WriteHeader(http.StatusOK) 478 rawUnsignedProduct := strings.Replace( 479 unsignedProduct, "ami-26745463", "ami-123456", -1) 480 _, _ = io.WriteString(w, encode(rawUnsignedProduct)) 481 return 482 case "/signed/streams/v1/index.sjson": 483 w.Header().Set("Content-Type", "application/json") 484 w.WriteHeader(http.StatusOK) 485 rawUnsignedIndex := strings.Replace( 486 unsignedIndex, "streams/v1/image_metadata.json", "streams/v1/image_metadata.sjson", -1) 487 _, _ = io.WriteString(w, encode(rawUnsignedIndex)) 488 return 489 default: 490 http.Error(w, r.URL.Path, 404) 491 return 492 } 493 } 494 495 func encode(data string) string { 496 reader := bytes.NewReader([]byte(data)) 497 signedData, _ := simplestreams.Encode( 498 reader, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 499 return string(signedData) 500 } 501 502 var unsignedIndex = ` 503 { 504 "index": { 505 "com.ubuntu.cloud:released:precise": { 506 "updated": "Wed, 01 May 2013 13:31:26 +0000", 507 "clouds": [ 508 { 509 "region": "us-east-1", 510 "endpoint": "https://ec2.us-east-1.amazonaws.com" 511 } 512 ], 513 "cloudname": "aws", 514 "datatype": "image-ids", 515 "format": "products:1.0", 516 "products": [ 517 "com.ubuntu.cloud:server:12.04:amd64" 518 ], 519 "path": "streams/v1/image_metadata.json" 520 } 521 }, 522 "updated": "Wed, 01 May 2013 13:31:26 +0000", 523 "format": "index:1.0" 524 } 525 ` 526 var unsignedProduct = ` 527 { 528 "updated": "Wed, 01 May 2013 13:31:26 +0000", 529 "content_id": "com.ubuntu.cloud:released:aws", 530 "products": { 531 "com.ubuntu.cloud:server:12.04:amd64": { 532 "release": "12.04", 533 "version": "12.04", 534 "arch": "amd64", 535 "region": "us-east-1", 536 "endpoint": "https://somewhere", 537 "versions": { 538 "20121218": { 539 "region": "us-east-1", 540 "endpoint": "https://somewhere-else", 541 "items": { 542 "usww1pe": { 543 "root_store": "ebs", 544 "virt": "pv", 545 "id": "ami-26745463" 546 } 547 }, 548 "pubname": "ubuntu-precise-12.04-amd64-server-20121218", 549 "label": "release" 550 } 551 } 552 } 553 }, 554 "format": "products:1.0" 555 } 556 `