github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/environs/tools/simplestreams_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package tools_test 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "flag" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/http" 14 "os" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "strings" 19 "testing" 20 21 "github.com/juju/os/series" 22 jc "github.com/juju/testing/checkers" 23 "github.com/juju/utils" 24 "github.com/juju/version" 25 "gopkg.in/amz.v3/aws" 26 gc "gopkg.in/check.v1" 27 28 "github.com/juju/juju/environs/filestorage" 29 "github.com/juju/juju/environs/simplestreams" 30 sstesting "github.com/juju/juju/environs/simplestreams/testing" 31 "github.com/juju/juju/environs/storage" 32 "github.com/juju/juju/environs/tools" 33 toolstesting "github.com/juju/juju/environs/tools/testing" 34 "github.com/juju/juju/juju/keys" 35 coretesting "github.com/juju/juju/testing" 36 coretools "github.com/juju/juju/tools" 37 ) 38 39 var live = flag.Bool("live", false, "Include live simplestreams tests") 40 var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data") 41 42 type liveTestData struct { 43 baseURL string 44 requireSigned bool 45 validCloudSpec simplestreams.CloudSpec 46 } 47 48 var liveURLs = map[string]liveTestData{ 49 "ec2": { 50 baseURL: tools.DefaultBaseURL, 51 requireSigned: true, 52 validCloudSpec: simplestreams.CloudSpec{"us-east-1", aws.Regions["us-east-1"].EC2Endpoint}, 53 }, 54 "canonistack": { 55 baseURL: "https://swift.canonistack.canonical.com/v1/AUTH_526ad877f3e3464589dc1145dfeaac60/juju-tools", 56 requireSigned: false, 57 validCloudSpec: simplestreams.CloudSpec{"lcy01", "https://keystone.canonistack.canonical.com:443/v1.0/"}, 58 }, 59 } 60 61 func setupSimpleStreamsTests(t *testing.T) { 62 if *live { 63 if *vendor == "" { 64 t.Fatal("missing vendor") 65 } 66 var ok bool 67 var testData liveTestData 68 if testData, ok = liveURLs[*vendor]; !ok { 69 keys := reflect.ValueOf(liveURLs).MapKeys() 70 t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys) 71 } 72 registerLiveSimpleStreamsTests(testData.baseURL, 73 tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 74 CloudSpec: testData.validCloudSpec, 75 Series: []string{series.MustHostSeries()}, 76 Arches: []string{"amd64"}, 77 Stream: "released", 78 }), testData.requireSigned) 79 } 80 registerSimpleStreamsTests() 81 } 82 83 func registerSimpleStreamsTests() { 84 gc.Suite(&simplestreamsSuite{ 85 LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{ 86 Source: simplestreams.NewURLDataSource("test", "test:", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, false), 87 RequireSigned: false, 88 DataType: tools.ContentDownload, 89 StreamsVersion: tools.CurrentStreamsVersion, 90 ValidConstraint: tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 91 CloudSpec: simplestreams.CloudSpec{ 92 Region: "us-east-1", 93 Endpoint: "https://ec2.us-east-1.amazonaws.com", 94 }, 95 Series: []string{"precise"}, 96 Arches: []string{"amd64", "arm"}, 97 Stream: "released", 98 }), 99 }, 100 }) 101 gc.Suite(&signedSuite{}) 102 } 103 104 func registerLiveSimpleStreamsTests(baseURL string, validToolsConstraint simplestreams.LookupConstraint, requireSigned bool) { 105 gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{ 106 Source: simplestreams.NewURLDataSource("test", baseURL, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, requireSigned), 107 RequireSigned: requireSigned, 108 DataType: tools.ContentDownload, 109 StreamsVersion: tools.CurrentStreamsVersion, 110 ValidConstraint: validToolsConstraint, 111 }) 112 } 113 114 type simplestreamsSuite struct { 115 sstesting.LocalLiveSimplestreamsSuite 116 sstesting.TestDataSuite 117 } 118 119 func (s *simplestreamsSuite) SetUpSuite(c *gc.C) { 120 s.LocalLiveSimplestreamsSuite.SetUpSuite(c) 121 s.TestDataSuite.SetUpSuite(c) 122 } 123 124 func (s *simplestreamsSuite) TearDownSuite(c *gc.C) { 125 s.TestDataSuite.TearDownSuite(c) 126 s.LocalLiveSimplestreamsSuite.TearDownSuite(c) 127 } 128 129 var fetchTests = []struct { 130 region string 131 series string 132 version string 133 stream string 134 major int 135 minor int 136 arches []string 137 tools []*tools.ToolsMetadata 138 }{{ 139 series: "precise", 140 arches: []string{"amd64", "arm"}, 141 version: "1.13.0", 142 tools: []*tools.ToolsMetadata{ 143 { 144 Release: "precise", 145 Version: "1.13.0", 146 Arch: "amd64", 147 Size: 2973595, 148 Path: "tools/released/20130806/juju-1.13.0-precise-amd64.tgz", 149 FileType: "tar.gz", 150 SHA256: "447aeb6a934a5eaec4f703eda4ef2dde", 151 }, 152 }, 153 }, { 154 series: "raring", 155 arches: []string{"amd64", "arm"}, 156 version: "1.13.0", 157 tools: []*tools.ToolsMetadata{ 158 { 159 Release: "raring", 160 Version: "1.13.0", 161 Arch: "amd64", 162 Size: 2973173, 163 Path: "tools/released/20130806/juju-1.13.0-raring-amd64.tgz", 164 FileType: "tar.gz", 165 SHA256: "df07ac5e1fb4232d4e9aa2effa57918a", 166 }, 167 }, 168 }, { 169 series: "raring", 170 arches: []string{"amd64", "arm"}, 171 version: "1.11.4", 172 tools: []*tools.ToolsMetadata{ 173 { 174 Release: "raring", 175 Version: "1.11.4", 176 Arch: "arm", 177 Size: 1950327, 178 Path: "tools/released/20130806/juju-1.11.4-raring-arm.tgz", 179 FileType: "tar.gz", 180 SHA256: "6472014e3255e3fe7fbd3550ef3f0a11", 181 }, 182 }, 183 }, { 184 series: "precise", 185 arches: []string{"amd64", "arm"}, 186 major: 2, 187 tools: []*tools.ToolsMetadata{ 188 { 189 Release: "precise", 190 Version: "2.0.1", 191 Arch: "arm", 192 Size: 1951096, 193 Path: "tools/released/20130806/juju-2.0.1-precise-arm.tgz", 194 FileType: "tar.gz", 195 SHA256: "f65a92b3b41311bdf398663ee1c5cd0c", 196 }, 197 }, 198 }, { 199 series: "precise", 200 arches: []string{"amd64", "arm"}, 201 major: 1, 202 minor: 11, 203 tools: []*tools.ToolsMetadata{ 204 { 205 Release: "precise", 206 Version: "1.11.4", 207 Arch: "arm", 208 Size: 1951096, 209 Path: "tools/released/20130806/juju-1.11.4-precise-arm.tgz", 210 FileType: "tar.gz", 211 SHA256: "f65a92b3b41311bdf398663ee1c5cd0c", 212 }, 213 { 214 Release: "precise", 215 Version: "1.11.5", 216 Arch: "arm", 217 Size: 2031281, 218 Path: "tools/released/20130803/juju-1.11.5-precise-arm.tgz", 219 FileType: "tar.gz", 220 SHA256: "df07ac5e1fb4232d4e9aa2effa57918a", 221 }, 222 }, 223 }, { 224 series: "trusty", 225 arches: []string{"amd64"}, 226 version: "1.16.0", 227 stream: "testing", 228 tools: []*tools.ToolsMetadata{ 229 { 230 Release: "trusty", 231 Version: "1.16.0", 232 Arch: "amd64", 233 Size: 2973512, 234 Path: "tools/testing/20130806/juju-1.16.0-trusty-amd64.tgz", 235 FileType: "tar.gz", 236 SHA256: "447aeb6a934a5eaec4f703eda4ef2dac", 237 }, 238 }, 239 }} 240 241 func (s *simplestreamsSuite) TestFetch(c *gc.C) { 242 for i, t := range fetchTests { 243 c.Logf("test %d", i) 244 if t.stream == "" { 245 t.stream = "released" 246 } 247 var toolsConstraint *tools.ToolsConstraint 248 if t.version == "" { 249 toolsConstraint = tools.NewGeneralToolsConstraint(t.major, t.minor, simplestreams.LookupParams{ 250 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 251 Series: []string{t.series}, 252 Arches: t.arches, 253 Stream: t.stream, 254 }) 255 } else { 256 toolsConstraint = tools.NewVersionedToolsConstraint(version.MustParse(t.version), 257 simplestreams.LookupParams{ 258 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 259 Series: []string{t.series}, 260 Arches: t.arches, 261 Stream: t.stream, 262 }) 263 } 264 // Add invalid datasource and check later that resolveInfo is correct. 265 invalidSource := simplestreams.NewURLDataSource("invalid", "file://invalid", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, s.RequireSigned) 266 tools, resolveInfo, err := tools.Fetch( 267 []simplestreams.DataSource{invalidSource, s.Source}, toolsConstraint) 268 if !c.Check(err, jc.ErrorIsNil) { 269 continue 270 } 271 for _, tm := range t.tools { 272 tm.FullPath, err = s.Source.URL(tm.Path) 273 c.Assert(err, jc.ErrorIsNil) 274 } 275 c.Check(tools, gc.DeepEquals, t.tools) 276 c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 277 Source: "test", 278 Signed: s.RequireSigned, 279 IndexURL: "test:/streams/v1/index.json", 280 MirrorURL: "", 281 }) 282 } 283 } 284 285 func (s *simplestreamsSuite) TestFetchNoMatchingStream(c *gc.C) { 286 toolsConstraint := tools.NewGeneralToolsConstraint(2, -1, simplestreams.LookupParams{ 287 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 288 Series: []string{"precise"}, 289 Arches: []string{}, 290 Stream: "proposed", 291 }) 292 _, _, err := tools.Fetch( 293 []simplestreams.DataSource{s.Source}, toolsConstraint) 294 c.Assert(err, gc.ErrorMatches, `"content-download" data not found`) 295 } 296 297 func (s *simplestreamsSuite) TestFetchWithMirror(c *gc.C) { 298 toolsConstraint := tools.NewGeneralToolsConstraint(1, 13, simplestreams.LookupParams{ 299 CloudSpec: simplestreams.CloudSpec{"us-west-2", "https://ec2.us-west-2.amazonaws.com"}, 300 Series: []string{"precise"}, 301 Arches: []string{"amd64"}, 302 Stream: "released", 303 }) 304 toolsMetadata, resolveInfo, err := tools.Fetch( 305 []simplestreams.DataSource{s.Source}, toolsConstraint) 306 c.Assert(err, jc.ErrorIsNil) 307 c.Assert(len(toolsMetadata), gc.Equals, 1) 308 309 expectedMetadata := &tools.ToolsMetadata{ 310 Release: "precise", 311 Version: "1.13.0", 312 Arch: "amd64", 313 Size: 2973595, 314 Path: "mirrored-path/juju-1.13.0-precise-amd64.tgz", 315 FullPath: "test:/mirrored-path/juju-1.13.0-precise-amd64.tgz", 316 FileType: "tar.gz", 317 SHA256: "447aeb6a934a5eaec4f703eda4ef2dde", 318 } 319 c.Assert(err, jc.ErrorIsNil) 320 c.Assert(toolsMetadata[0], gc.DeepEquals, expectedMetadata) 321 c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 322 Source: "test", 323 Signed: s.RequireSigned, 324 IndexURL: "test:/streams/v1/index.json", 325 MirrorURL: "test:/", 326 }) 327 } 328 329 func assertMetadataMatches(c *gc.C, storageDir string, stream string, toolList coretools.List, metadata []*tools.ToolsMetadata) { 330 var expectedMetadata []*tools.ToolsMetadata = make([]*tools.ToolsMetadata, len(toolList)) 331 for i, tool := range toolList { 332 expectedMetadata[i] = &tools.ToolsMetadata{ 333 Release: tool.Version.Series, 334 Version: tool.Version.Number.String(), 335 Arch: tool.Version.Arch, 336 Size: tool.Size, 337 Path: fmt.Sprintf("%s/juju-%s.tgz", stream, tool.Version.String()), 338 FileType: "tar.gz", 339 SHA256: tool.SHA256, 340 } 341 } 342 c.Assert(metadata, gc.DeepEquals, expectedMetadata) 343 } 344 345 func (s *simplestreamsSuite) TestWriteMetadataNoFetch(c *gc.C) { 346 toolsList := coretools.List{ 347 { 348 Version: version.MustParseBinary("1.2.3-precise-amd64"), 349 Size: 123, 350 SHA256: "abcd", 351 }, { 352 Version: version.MustParseBinary("2.0.1-raring-amd64"), 353 Size: 456, 354 SHA256: "xyz", 355 }, 356 } 357 expected := toolsList 358 359 // Add tools with an unknown series. 360 // We need to support this case for times when a new Ubuntu series 361 // is released and jujud does not know about it yet. 362 vers, err := version.ParseBinary("3.2.1-xuanhuaceratops-amd64") 363 c.Assert(err, jc.ErrorIsNil) 364 toolsList = append(toolsList, &coretools.Tools{ 365 Version: vers, 366 Size: 456, 367 SHA256: "wqe", 368 }) 369 370 dir := c.MkDir() 371 writer, err := filestorage.NewFileStorageWriter(dir) 372 c.Assert(err, jc.ErrorIsNil) 373 err = tools.MergeAndWriteMetadata(writer, "proposed", "proposed", toolsList, tools.DoNotWriteMirrors) 374 c.Assert(err, jc.ErrorIsNil) 375 metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", false) 376 assertMetadataMatches(c, dir, "proposed", expected, metadata) 377 } 378 379 func (s *simplestreamsSuite) assertWriteMetadata(c *gc.C, withMirrors bool) { 380 var versionStrings = []string{ 381 "1.2.3-precise-amd64", 382 "2.0.1-raring-amd64", 383 } 384 dir := c.MkDir() 385 toolstesting.MakeTools(c, dir, "proposed", versionStrings) 386 387 toolsList := coretools.List{ 388 { 389 // If sha256/size is already known, do not recalculate 390 Version: version.MustParseBinary("1.2.3-precise-amd64"), 391 Size: 123, 392 SHA256: "abcd", 393 }, { 394 Version: version.MustParseBinary("2.0.1-raring-amd64"), 395 // The URL is not used for generating metadata. 396 URL: "bogus://", 397 }, 398 } 399 writer, err := filestorage.NewFileStorageWriter(dir) 400 c.Assert(err, jc.ErrorIsNil) 401 writeMirrors := tools.DoNotWriteMirrors 402 if withMirrors { 403 writeMirrors = tools.WriteMirrors 404 } 405 err = tools.MergeAndWriteMetadata(writer, "proposed", "proposed", toolsList, writeMirrors) 406 c.Assert(err, jc.ErrorIsNil) 407 metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", withMirrors) 408 assertMetadataMatches(c, dir, "proposed", toolsList, metadata) 409 410 // No release stream generated so there will not be a legacy index file created. 411 _, err = writer.Get("tools/streams/v1/index.json") 412 c.Assert(err, gc.NotNil) 413 } 414 415 func (s *simplestreamsSuite) TestWriteMetadata(c *gc.C) { 416 s.assertWriteMetadata(c, false) 417 } 418 419 func (s *simplestreamsSuite) TestWriteMetadataWithMirrors(c *gc.C) { 420 s.assertWriteMetadata(c, true) 421 } 422 423 func (s *simplestreamsSuite) TestWriteMetadataMergeWithExisting(c *gc.C) { 424 dir := c.MkDir() 425 existingToolsList := coretools.List{ 426 { 427 Version: version.MustParseBinary("1.2.3-precise-amd64"), 428 Size: 123, 429 SHA256: "abc", 430 }, { 431 Version: version.MustParseBinary("2.0.1-raring-amd64"), 432 Size: 456, 433 SHA256: "xyz", 434 }, 435 } 436 writer, err := filestorage.NewFileStorageWriter(dir) 437 c.Assert(err, jc.ErrorIsNil) 438 err = tools.MergeAndWriteMetadata(writer, "testing", "testing", existingToolsList, tools.WriteMirrors) 439 c.Assert(err, jc.ErrorIsNil) 440 newToolsList := coretools.List{ 441 existingToolsList[0], 442 { 443 Version: version.MustParseBinary("2.1.0-raring-amd64"), 444 Size: 789, 445 SHA256: "def", 446 }, 447 } 448 err = tools.MergeAndWriteMetadata(writer, "testing", "testing", newToolsList, tools.WriteMirrors) 449 c.Assert(err, jc.ErrorIsNil) 450 requiredToolsList := append(existingToolsList, newToolsList[1]) 451 metadata := toolstesting.ParseMetadataFromDir(c, dir, "testing", true) 452 assertMetadataMatches(c, dir, "testing", requiredToolsList, metadata) 453 454 err = tools.MergeAndWriteMetadata(writer, "devel", "devel", newToolsList, tools.WriteMirrors) 455 c.Assert(err, jc.ErrorIsNil) 456 metadata = toolstesting.ParseMetadataFromDir(c, dir, "testing", true) 457 assertMetadataMatches(c, dir, "testing", requiredToolsList, metadata) 458 metadata = toolstesting.ParseMetadataFromDir(c, dir, "devel", true) 459 assertMetadataMatches(c, dir, "devel", newToolsList, metadata) 460 } 461 462 type productSpecSuite struct{} 463 464 var _ = gc.Suite(&productSpecSuite{}) 465 466 func (s *productSpecSuite) TestIndexIdNoStream(c *gc.C) { 467 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 468 Series: []string{"precise"}, 469 Arches: []string{"amd64"}, 470 }) 471 ids := toolsConstraint.IndexIds() 472 c.Assert(ids, gc.HasLen, 0) 473 } 474 475 func (s *productSpecSuite) TestIndexId(c *gc.C) { 476 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 477 Series: []string{"precise"}, 478 Arches: []string{"amd64"}, 479 Stream: "proposed", 480 }) 481 ids := toolsConstraint.IndexIds() 482 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:proposed:tools"}) 483 } 484 485 func (s *productSpecSuite) TestProductId(c *gc.C) { 486 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 487 Series: []string{"precise"}, 488 Arches: []string{"amd64"}, 489 }) 490 ids, err := toolsConstraint.ProductIds() 491 c.Assert(err, jc.ErrorIsNil) 492 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:12.04:amd64"}) 493 } 494 495 func (s *productSpecSuite) TestIdMultiArch(c *gc.C) { 496 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{ 497 Series: []string{"precise"}, 498 Arches: []string{"amd64", "arm"}, 499 }) 500 ids, err := toolsConstraint.ProductIds() 501 c.Assert(err, jc.ErrorIsNil) 502 c.Assert(ids, gc.DeepEquals, []string{ 503 "com.ubuntu.juju:12.04:amd64", 504 "com.ubuntu.juju:12.04:arm"}) 505 } 506 507 func (s *productSpecSuite) TestIdMultiSeries(c *gc.C) { 508 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{ 509 Series: []string{"precise", "raring"}, 510 Arches: []string{"amd64"}, 511 Stream: "released", 512 }) 513 ids, err := toolsConstraint.ProductIds() 514 c.Assert(err, jc.ErrorIsNil) 515 c.Assert(ids, gc.DeepEquals, []string{ 516 "com.ubuntu.juju:12.04:amd64", 517 "com.ubuntu.juju:13.04:amd64"}) 518 } 519 520 func (s *productSpecSuite) TestIdIgnoresInvalidSeries(c *gc.C) { 521 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{ 522 Series: []string{"precise", "foobar"}, 523 Arches: []string{"amd64"}, 524 Stream: "released", 525 }) 526 ids, err := toolsConstraint.ProductIds() 527 c.Assert(err, jc.ErrorIsNil) 528 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:12.04:amd64"}) 529 } 530 531 func (s *productSpecSuite) TestIdWithMajorVersionOnly(c *gc.C) { 532 toolsConstraint := tools.NewGeneralToolsConstraint(1, -1, simplestreams.LookupParams{ 533 Series: []string{"precise"}, 534 Arches: []string{"amd64"}, 535 Stream: "released", 536 }) 537 ids, err := toolsConstraint.ProductIds() 538 c.Assert(err, jc.ErrorIsNil) 539 c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`}) 540 } 541 542 func (s *productSpecSuite) TestIdWithMajorMinorVersion(c *gc.C) { 543 toolsConstraint := tools.NewGeneralToolsConstraint(1, 2, simplestreams.LookupParams{ 544 Series: []string{"precise"}, 545 Arches: []string{"amd64"}, 546 Stream: "released", 547 }) 548 ids, err := toolsConstraint.ProductIds() 549 c.Assert(err, jc.ErrorIsNil) 550 c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`}) 551 } 552 553 func (s *productSpecSuite) TestLargeNumber(c *gc.C) { 554 json := `{ 555 "updated": "Fri, 30 Aug 2013 16:12:58 +0800", 556 "format": "products:1.0", 557 "products": { 558 "com.ubuntu.juju:1.10.0:amd64": { 559 "version": "1.10.0", 560 "arch": "amd64", 561 "versions": { 562 "20133008": { 563 "items": { 564 "1.10.0-precise-amd64": { 565 "release": "precise", 566 "version": "1.10.0", 567 "arch": "amd64", 568 "size": 9223372036854775807, 569 "path": "releases/juju-1.10.0-precise-amd64.tgz", 570 "ftype": "tar.gz", 571 "sha256": "" 572 } 573 } 574 } 575 } 576 } 577 } 578 }` 579 cloudMetadata, err := simplestreams.ParseCloudMetadata([]byte(json), "products:1.0", "", tools.ToolsMetadata{}) 580 c.Assert(err, jc.ErrorIsNil) 581 c.Assert(cloudMetadata.Products, gc.HasLen, 1) 582 product := cloudMetadata.Products["com.ubuntu.juju:1.10.0:amd64"] 583 c.Assert(product, gc.NotNil) 584 c.Assert(product.Items, gc.HasLen, 1) 585 version := product.Items["20133008"] 586 c.Assert(version, gc.NotNil) 587 c.Assert(version.Items, gc.HasLen, 1) 588 item := version.Items["1.10.0-precise-amd64"] 589 c.Assert(item, gc.NotNil) 590 c.Assert(item, gc.FitsTypeOf, &tools.ToolsMetadata{}) 591 c.Assert(item.(*tools.ToolsMetadata).Size, gc.Equals, int64(9223372036854775807)) 592 } 593 594 type metadataHelperSuite struct { 595 coretesting.BaseSuite 596 } 597 598 var _ = gc.Suite(&metadataHelperSuite{}) 599 600 func (*metadataHelperSuite) TestMetadataFromTools(c *gc.C) { 601 metadata := tools.MetadataFromTools(nil, "proposed") 602 c.Assert(metadata, gc.HasLen, 0) 603 604 toolsList := coretools.List{{ 605 Version: version.MustParseBinary("1.2.3-precise-amd64"), 606 Size: 123, 607 SHA256: "abc", 608 }, { 609 Version: version.MustParseBinary("2.0.1-raring-amd64"), 610 URL: "file:///tmp/proposed/juju-2.0.1-raring-amd64.tgz", 611 Size: 456, 612 SHA256: "xyz", 613 }} 614 metadata = tools.MetadataFromTools(toolsList, "proposed") 615 c.Assert(metadata, gc.HasLen, len(toolsList)) 616 for i, t := range toolsList { 617 md := metadata[i] 618 c.Assert(md.Release, gc.Equals, t.Version.Series) 619 c.Assert(md.Version, gc.Equals, t.Version.Number.String()) 620 c.Assert(md.Arch, gc.Equals, t.Version.Arch) 621 // FullPath is only filled out when reading tools using simplestreams. 622 // It's not needed elsewhere and requires a URL() call. 623 c.Assert(md.FullPath, gc.Equals, "") 624 c.Assert(md.Path, gc.Equals, tools.StorageName(t.Version, "proposed")[len("tools/"):]) 625 c.Assert(md.FileType, gc.Equals, "tar.gz") 626 c.Assert(md.Size, gc.Equals, t.Size) 627 c.Assert(md.SHA256, gc.Equals, t.SHA256) 628 } 629 } 630 631 type countingStorage struct { 632 storage.StorageReader 633 counter int 634 } 635 636 func (c *countingStorage) Get(name string) (io.ReadCloser, error) { 637 c.counter++ 638 return c.StorageReader.Get(name) 639 } 640 641 func (*metadataHelperSuite) TestResolveMetadata(c *gc.C) { 642 var versionStrings = []string{"1.2.3-precise-amd64"} 643 dir := c.MkDir() 644 toolstesting.MakeTools(c, dir, "released", versionStrings) 645 toolsList := coretools.List{{ 646 Version: version.MustParseBinary(versionStrings[0]), 647 Size: 123, 648 SHA256: "abc", 649 }} 650 651 stor, err := filestorage.NewFileStorageReader(dir) 652 c.Assert(err, jc.ErrorIsNil) 653 err = tools.ResolveMetadata(stor, "released", nil) 654 c.Assert(err, jc.ErrorIsNil) 655 656 // We already have size/sha256, so ensure that storage isn't consulted. 657 countingStorage := &countingStorage{StorageReader: stor} 658 metadata := tools.MetadataFromTools(toolsList, "released") 659 err = tools.ResolveMetadata(countingStorage, "released", metadata) 660 c.Assert(err, jc.ErrorIsNil) 661 c.Assert(countingStorage.counter, gc.Equals, 0) 662 663 // Now clear size/sha256, and check that it is called, and 664 // the size/sha256 sum are updated. 665 metadata[0].Size = 0 666 metadata[0].SHA256 = "" 667 err = tools.ResolveMetadata(countingStorage, "released", metadata) 668 c.Assert(err, jc.ErrorIsNil) 669 c.Assert(countingStorage.counter, gc.Equals, 1) 670 c.Assert(metadata[0].Size, gc.Not(gc.Equals), 0) 671 c.Assert(metadata[0].SHA256, gc.Not(gc.Equals), "") 672 } 673 674 func (*metadataHelperSuite) TestResolveMetadataLegacyPPC64(c *gc.C) { 675 var versionStrings = []string{"1.2.3-precise-amd64", "1.2.3-precise-ppc64el"} 676 dir := c.MkDir() 677 toolstesting.MakeTools(c, dir, "released", versionStrings) 678 679 toolsList := coretools.List{ 680 { 681 Version: version.MustParseBinary(versionStrings[0]), 682 }, { 683 Version: version.MustParseBinary(versionStrings[1]), 684 }, { 685 Version: version.MustParseBinary("1.2.3-precise-ppc64"), 686 }, 687 } 688 toolsMetadata := tools.MetadataFromTools(toolsList, dir) 689 stor, err := filestorage.NewFileStorageReader(dir) 690 c.Assert(err, jc.ErrorIsNil) 691 err = tools.ResolveMetadata(stor, "released", toolsMetadata) 692 c.Assert(err, jc.ErrorIsNil) 693 c.Assert(toolsMetadata, gc.DeepEquals, []*tools.ToolsMetadata{ 694 { 695 Release: "precise", 696 Version: "1.2.3", 697 Arch: "amd64", 698 Size: 19, 699 FileType: "tar.gz", 700 SHA256: "dcdd65b962b804a3d63b108d670290ee95a867a97fe9b9f99b2b77b5c7173e59", 701 Path: fmt.Sprintf("%s/juju-1.2.3-precise-amd64.tgz", dir), 702 }, 703 { 704 Release: "precise", 705 Version: "1.2.3", 706 Arch: "ppc64el", 707 Size: 21, 708 FileType: "tar.gz", 709 SHA256: "a3460ed45eb07a69adfcd541413a495f988c5842d715c6a40353075c3ad47af2", 710 Path: fmt.Sprintf("%s/juju-1.2.3-precise-ppc64el.tgz", dir), 711 }, 712 { 713 Release: "precise", 714 Version: "1.2.3", 715 Arch: "ppc64", 716 Size: 21, 717 FileType: "tar.gz", 718 SHA256: "a3460ed45eb07a69adfcd541413a495f988c5842d715c6a40353075c3ad47af2", 719 Path: fmt.Sprintf("%s/juju-1.2.3-precise-ppc64el.tgz", dir), 720 }, 721 }) 722 } 723 724 func (*metadataHelperSuite) TestMergeMetadata(c *gc.C) { 725 md1 := &tools.ToolsMetadata{ 726 Release: "precise", 727 Version: "1.2.3", 728 Arch: "amd64", 729 Path: "path1", 730 } 731 md2 := &tools.ToolsMetadata{ 732 Release: "precise", 733 Version: "1.2.3", 734 Arch: "amd64", 735 Path: "path2", 736 } 737 md3 := &tools.ToolsMetadata{ 738 Release: "raring", 739 Version: "1.2.3", 740 Arch: "amd64", 741 Path: "path3", 742 } 743 744 withSize := func(md *tools.ToolsMetadata, size int64) *tools.ToolsMetadata { 745 clone := *md 746 clone.Size = size 747 return &clone 748 } 749 withSHA256 := func(md *tools.ToolsMetadata, sha256 string) *tools.ToolsMetadata { 750 clone := *md 751 clone.SHA256 = sha256 752 return &clone 753 } 754 755 type mdlist []*tools.ToolsMetadata 756 type test struct { 757 name string 758 lhs, rhs, merged []*tools.ToolsMetadata 759 err string 760 } 761 tests := []test{{ 762 name: "non-empty lhs, empty rhs", 763 lhs: mdlist{md1}, 764 rhs: nil, 765 merged: mdlist{md1}, 766 }, { 767 name: "empty lhs, non-empty rhs", 768 lhs: nil, 769 rhs: mdlist{md2}, 770 merged: mdlist{md2}, 771 }, { 772 name: "identical lhs, rhs", 773 lhs: mdlist{md1}, 774 rhs: mdlist{md1}, 775 merged: mdlist{md1}, 776 }, { 777 name: "same tools in lhs and rhs, neither have size: prefer lhs", 778 lhs: mdlist{md1}, 779 rhs: mdlist{md2}, 780 merged: mdlist{md1}, 781 }, { 782 name: "same tools in lhs and rhs, only lhs has a size: prefer lhs", 783 lhs: mdlist{withSize(md1, 123)}, 784 rhs: mdlist{md2}, 785 merged: mdlist{withSize(md1, 123)}, 786 }, { 787 name: "same tools in lhs and rhs, only rhs has a size: prefer rhs", 788 lhs: mdlist{md1}, 789 rhs: mdlist{withSize(md2, 123)}, 790 merged: mdlist{withSize(md2, 123)}, 791 }, { 792 name: "same tools in lhs and rhs, both have the same size: prefer lhs", 793 lhs: mdlist{withSize(md1, 123)}, 794 rhs: mdlist{withSize(md2, 123)}, 795 merged: mdlist{withSize(md1, 123)}, 796 }, { 797 name: "same tools in lhs and rhs, both have different sizes: error", 798 lhs: mdlist{withSize(md1, 123)}, 799 rhs: mdlist{withSize(md2, 456)}, 800 err: "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,456\\) sha256=\\(,\\)", 801 }, { 802 name: "same tools in lhs and rhs, both have same size but different sha256: error", 803 lhs: mdlist{withSHA256(withSize(md1, 123), "a")}, 804 rhs: mdlist{withSHA256(withSize(md2, 123), "b")}, 805 err: "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,123\\) sha256=\\(a,b\\)", 806 }, { 807 name: "lhs is a proper superset of rhs: union of lhs and rhs", 808 lhs: mdlist{md1, md3}, 809 rhs: mdlist{md1}, 810 merged: mdlist{md1, md3}, 811 }, { 812 name: "rhs is a proper superset of lhs: union of lhs and rhs", 813 lhs: mdlist{md1}, 814 rhs: mdlist{md1, md3}, 815 merged: mdlist{md1, md3}, 816 }} 817 for i, test := range tests { 818 c.Logf("test %d: %s", i, test.name) 819 merged, err := tools.MergeMetadata(test.lhs, test.rhs) 820 if test.err == "" { 821 c.Assert(err, jc.ErrorIsNil) 822 c.Assert(merged, gc.DeepEquals, test.merged) 823 } else { 824 c.Assert(err, gc.ErrorMatches, test.err) 825 c.Assert(merged, gc.IsNil) 826 } 827 } 828 } 829 830 func (*metadataHelperSuite) TestReadWriteMetadataSingleStream(c *gc.C) { 831 metadata := map[string][]*tools.ToolsMetadata{ 832 "released": {{ 833 Release: "precise", 834 Version: "1.2.3", 835 Arch: "amd64", 836 Path: "path1", 837 }, { 838 Release: "raring", 839 Version: "1.2.3", 840 Arch: "amd64", 841 Path: "path2", 842 }}, 843 } 844 845 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 846 c.Assert(err, jc.ErrorIsNil) 847 out, err := tools.ReadAllMetadata(stor) 848 c.Assert(err, jc.ErrorIsNil) // non-existence is not an error 849 c.Assert(out, gc.HasLen, 0) 850 err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors) 851 c.Assert(err, jc.ErrorIsNil) 852 853 // Read back what was just written. 854 out, err = tools.ReadAllMetadata(stor) 855 c.Assert(err, jc.ErrorIsNil) 856 for _, outMetadata := range out { 857 for _, md := range outMetadata { 858 // FullPath is set by ReadAllMetadata. 859 c.Assert(md.FullPath, gc.Not(gc.Equals), "") 860 md.FullPath = "" 861 } 862 } 863 c.Assert(out, jc.DeepEquals, metadata) 864 } 865 866 func (*metadataHelperSuite) writeMetadataMultipleStream(c *gc.C) (storage.StorageReader, map[string][]*tools.ToolsMetadata) { 867 metadata := map[string][]*tools.ToolsMetadata{ 868 "released": {{ 869 Release: "precise", 870 Version: "1.2.3", 871 Arch: "amd64", 872 Path: "path1", 873 }}, 874 "proposed": {{ 875 Release: "raring", 876 Version: "1.2.3", 877 Arch: "amd64", 878 Path: "path2", 879 }}, 880 } 881 882 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 883 c.Assert(err, jc.ErrorIsNil) 884 out, err := tools.ReadAllMetadata(stor) 885 c.Assert(out, gc.HasLen, 0) 886 c.Assert(err, jc.ErrorIsNil) // non-existence is not an error 887 err = tools.WriteMetadata(stor, metadata, []string{"released", "proposed"}, tools.DoNotWriteMirrors) 888 c.Assert(err, jc.ErrorIsNil) 889 return stor, metadata 890 } 891 892 func (s *metadataHelperSuite) TestReadWriteMetadataMultipleStream(c *gc.C) { 893 stor, metadata := s.writeMetadataMultipleStream(c) 894 // Read back what was just written. 895 out, err := tools.ReadAllMetadata(stor) 896 c.Assert(err, jc.ErrorIsNil) 897 for _, outMetadata := range out { 898 for _, md := range outMetadata { 899 // FullPath is set by ReadAllMetadata. 900 c.Assert(md.FullPath, gc.Not(gc.Equals), "") 901 md.FullPath = "" 902 } 903 } 904 c.Assert(out, jc.DeepEquals, metadata) 905 } 906 907 func (s *metadataHelperSuite) TestWriteMetadataLegacyIndex(c *gc.C) { 908 stor, _ := s.writeMetadataMultipleStream(c) 909 // Read back the legacy index 910 rdr, err := stor.Get("tools/streams/v1/index.json") 911 defer rdr.Close() 912 c.Assert(err, jc.ErrorIsNil) 913 data, err := ioutil.ReadAll(rdr) 914 c.Assert(err, jc.ErrorIsNil) 915 var indices simplestreams.Indices 916 err = json.Unmarshal(data, &indices) 917 c.Assert(err, jc.ErrorIsNil) 918 c.Assert(indices.Indexes, gc.HasLen, 1) 919 indices.Updated = "" 920 c.Assert(indices.Indexes["com.ubuntu.juju:released:tools"], gc.NotNil) 921 indices.Indexes["com.ubuntu.juju:released:tools"].Updated = "" 922 expected := simplestreams.Indices{ 923 Format: "index:1.0", 924 Indexes: map[string]*simplestreams.IndexMetadata{ 925 "com.ubuntu.juju:released:tools": { 926 Format: "products:1.0", 927 DataType: "content-download", 928 ProductsFilePath: "streams/v1/com.ubuntu.juju-released-tools.json", 929 ProductIds: []string{"com.ubuntu.juju:12.04:amd64"}, 930 }, 931 }, 932 } 933 c.Assert(indices, jc.DeepEquals, expected) 934 } 935 936 func (s *metadataHelperSuite) TestReadWriteMetadataUnchanged(c *gc.C) { 937 metadata := map[string][]*tools.ToolsMetadata{ 938 "released": {{ 939 Release: "precise", 940 Version: "1.2.3", 941 Arch: "amd64", 942 Path: "path1", 943 }, { 944 Release: "raring", 945 Version: "1.2.3", 946 Arch: "amd64", 947 Path: "path2", 948 }}, 949 } 950 951 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 952 c.Assert(err, jc.ErrorIsNil) 953 err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors) 954 c.Assert(err, jc.ErrorIsNil) 955 956 s.PatchValue(tools.WriteMetadataFiles, func(stor storage.Storage, metadataInfo []tools.MetadataFile) error { 957 // The product data is the same, we only write the indices. 958 c.Assert(metadataInfo, gc.HasLen, 2) 959 c.Assert(metadataInfo[0].Path, gc.Equals, "streams/v1/index2.json") 960 c.Assert(metadataInfo[1].Path, gc.Equals, "streams/v1/index.json") 961 return nil 962 }) 963 err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors) 964 c.Assert(err, jc.ErrorIsNil) 965 } 966 967 func (*metadataHelperSuite) TestReadMetadataPrefersNewIndex(c *gc.C) { 968 if runtime.GOOS == "windows" { 969 c.Skip("Skipped for now because of introduced regression") 970 } 971 metadataDir := c.MkDir() 972 973 // Generate metadata and rename index to index.json 974 metadata := map[string][]*tools.ToolsMetadata{ 975 "proposed": {{ 976 Release: "precise", 977 Version: "1.2.3", 978 Arch: "amd64", 979 Path: "path1", 980 }}, 981 "released": {{ 982 Release: "trusty", 983 Version: "1.2.3", 984 Arch: "amd64", 985 Path: "path1", 986 }}, 987 } 988 stor, err := filestorage.NewFileStorageWriter(metadataDir) 989 c.Assert(err, jc.ErrorIsNil) 990 err = tools.WriteMetadata(stor, metadata, []string{"proposed", "released"}, tools.DoNotWriteMirrors) 991 c.Assert(err, jc.ErrorIsNil) 992 err = os.Rename( 993 filepath.Join(metadataDir, "tools", "streams", "v1", "index2.json"), 994 filepath.Join(metadataDir, "tools", "streams", "v1", "index.json"), 995 ) 996 c.Assert(err, jc.ErrorIsNil) 997 998 // Generate different metadata with index2.json 999 metadata = map[string][]*tools.ToolsMetadata{ 1000 "released": {{ 1001 Release: "precise", 1002 Version: "1.2.3", 1003 Arch: "amd64", 1004 Path: "path1", 1005 }}, 1006 } 1007 err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors) 1008 c.Assert(err, jc.ErrorIsNil) 1009 1010 // Read back all metadata, expecting to find metadata in index2.json. 1011 out, err := tools.ReadAllMetadata(stor) 1012 for _, outMetadata := range out { 1013 for _, md := range outMetadata { 1014 // FullPath is set by ReadAllMetadata. 1015 c.Assert(md.FullPath, gc.Not(gc.Equals), "") 1016 md.FullPath = "" 1017 } 1018 } 1019 c.Assert(out, jc.DeepEquals, metadata) 1020 } 1021 1022 type signedSuite struct { 1023 coretesting.BaseSuite 1024 } 1025 1026 func (s *signedSuite) SetUpSuite(c *gc.C) { 1027 s.BaseSuite.SetUpSuite(c) 1028 var imageData = map[string]string{ 1029 "/unsigned/streams/v1/index.json": unsignedIndex, 1030 "/unsigned/streams/v1/tools_metadata.json": unsignedProduct, 1031 } 1032 1033 // Set up some signed data from the unsigned data. 1034 // Overwrite the product path to use the sjson suffix. 1035 rawUnsignedIndex := strings.Replace( 1036 unsignedIndex, "streams/v1/tools_metadata.json", "streams/v1/tools_metadata.sjson", -1) 1037 r := bytes.NewReader([]byte(rawUnsignedIndex)) 1038 signedData, err := simplestreams.Encode( 1039 r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 1040 c.Assert(err, jc.ErrorIsNil) 1041 imageData["/signed/streams/v1/index.sjson"] = string(signedData) 1042 1043 // Replace the tools path in the unsigned data with a different one so we can test that the right 1044 // tools path is used. 1045 rawUnsignedProduct := strings.Replace( 1046 unsignedProduct, "juju-1.13.0", "juju-1.13.1", -1) 1047 r = bytes.NewReader([]byte(rawUnsignedProduct)) 1048 signedData, err = simplestreams.Encode( 1049 r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 1050 c.Assert(err, jc.ErrorIsNil) 1051 imageData["/signed/streams/v1/tools_metadata.sjson"] = string(signedData) 1052 sstesting.SetRoundTripperFiles(imageData, map[string]int{"signedtest://unauth": http.StatusUnauthorized}) 1053 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 1054 } 1055 1056 func (s *signedSuite) TearDownSuite(c *gc.C) { 1057 sstesting.SetRoundTripperFiles(nil, nil) 1058 s.BaseSuite.TearDownSuite(c) 1059 } 1060 1061 func (s *signedSuite) TestSignedToolsMetadata(c *gc.C) { 1062 signedSource := simplestreams.NewURLSignedDataSource( 1063 "test", "signedtest://host/signed", sstesting.SignedMetadataPublicKey, 1064 utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true) 1065 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 1066 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 1067 Series: []string{"precise"}, 1068 Arches: []string{"amd64"}, 1069 Stream: "released", 1070 }) 1071 toolsMetadata, resolveInfo, err := tools.Fetch( 1072 []simplestreams.DataSource{signedSource}, toolsConstraint) 1073 c.Assert(err, jc.ErrorIsNil) 1074 c.Assert(len(toolsMetadata), gc.Equals, 1) 1075 c.Assert(toolsMetadata[0].Path, gc.Equals, "tools/releases/20130806/juju-1.13.1-precise-amd64.tgz") 1076 c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 1077 Source: "test", 1078 Signed: true, 1079 IndexURL: "signedtest://host/signed/streams/v1/index.sjson", 1080 MirrorURL: "", 1081 }) 1082 } 1083 1084 var unsignedIndex = ` 1085 { 1086 "index": { 1087 "com.ubuntu.juju:released:tools": { 1088 "updated": "Mon, 05 Aug 2013 11:07:04 +0000", 1089 "datatype": "content-download", 1090 "format": "products:1.0", 1091 "products": [ 1092 "com.ubuntu.juju:12.04:amd64" 1093 ], 1094 "path": "streams/v1/tools_metadata.json" 1095 } 1096 }, 1097 "updated": "Wed, 01 May 2013 13:31:26 +0000", 1098 "format": "index:1.0" 1099 } 1100 ` 1101 var unsignedProduct = ` 1102 { 1103 "updated": "Wed, 01 May 2013 13:31:26 +0000", 1104 "content_id": "com.ubuntu.cloud:released:aws", 1105 "datatype": "content-download", 1106 "products": { 1107 "com.ubuntu.juju:12.04:amd64": { 1108 "arch": "amd64", 1109 "release": "precise", 1110 "versions": { 1111 "20130806": { 1112 "items": { 1113 "1130preciseamd64": { 1114 "version": "1.13.0", 1115 "size": 2973595, 1116 "path": "tools/releases/20130806/juju-1.13.0-precise-amd64.tgz", 1117 "ftype": "tar.gz", 1118 "sha256": "447aeb6a934a5eaec4f703eda4ef2dde" 1119 } 1120 } 1121 } 1122 } 1123 } 1124 }, 1125 "format": "products:1.0" 1126 } 1127 `