github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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 "flag" 9 "fmt" 10 "io" 11 "net/http" 12 "reflect" 13 "strings" 14 "testing" 15 16 "github.com/juju/utils" 17 "launchpad.net/goamz/aws" 18 gc "launchpad.net/gocheck" 19 20 "github.com/juju/juju/environs/filestorage" 21 "github.com/juju/juju/environs/jujutest" 22 "github.com/juju/juju/environs/simplestreams" 23 sstesting "github.com/juju/juju/environs/simplestreams/testing" 24 "github.com/juju/juju/environs/storage" 25 "github.com/juju/juju/environs/tools" 26 toolstesting "github.com/juju/juju/environs/tools/testing" 27 coretesting "github.com/juju/juju/testing" 28 coretools "github.com/juju/juju/tools" 29 "github.com/juju/juju/version" 30 ) 31 32 var live = flag.Bool("live", false, "Include live simplestreams tests") 33 var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data") 34 35 type liveTestData struct { 36 baseURL string 37 requireSigned bool 38 validCloudSpec simplestreams.CloudSpec 39 } 40 41 var liveUrls = map[string]liveTestData{ 42 "ec2": { 43 baseURL: tools.DefaultBaseURL, 44 requireSigned: true, 45 validCloudSpec: simplestreams.CloudSpec{"us-east-1", aws.Regions["us-east-1"].EC2Endpoint}, 46 }, 47 "canonistack": { 48 baseURL: "https://swift.canonistack.canonical.com/v1/AUTH_526ad877f3e3464589dc1145dfeaac60/juju-tools", 49 requireSigned: false, 50 validCloudSpec: simplestreams.CloudSpec{"lcy01", "https://keystone.canonistack.canonical.com:443/v2.0/"}, 51 }, 52 } 53 54 func setupSimpleStreamsTests(t *testing.T) { 55 if *live { 56 if *vendor == "" { 57 t.Fatal("missing vendor") 58 } 59 var ok bool 60 var testData liveTestData 61 if testData, ok = liveUrls[*vendor]; !ok { 62 keys := reflect.ValueOf(liveUrls).MapKeys() 63 t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys) 64 } 65 registerLiveSimpleStreamsTests(testData.baseURL, 66 tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 67 CloudSpec: testData.validCloudSpec, 68 Series: []string{version.Current.Series}, 69 Arches: []string{"amd64"}, 70 }), testData.requireSigned) 71 } 72 registerSimpleStreamsTests() 73 } 74 75 func registerSimpleStreamsTests() { 76 gc.Suite(&simplestreamsSuite{ 77 LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{ 78 Source: simplestreams.NewURLDataSource("test", "test:", utils.VerifySSLHostnames), 79 RequireSigned: false, 80 DataType: tools.ContentDownload, 81 ValidConstraint: tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 82 CloudSpec: simplestreams.CloudSpec{ 83 Region: "us-east-1", 84 Endpoint: "https://ec2.us-east-1.amazonaws.com", 85 }, 86 Series: []string{"precise"}, 87 Arches: []string{"amd64", "arm"}, 88 }), 89 }, 90 }) 91 gc.Suite(&signedSuite{}) 92 } 93 94 func registerLiveSimpleStreamsTests(baseURL string, validToolsConstraint simplestreams.LookupConstraint, requireSigned bool) { 95 gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{ 96 Source: simplestreams.NewURLDataSource("test", baseURL, utils.VerifySSLHostnames), 97 RequireSigned: requireSigned, 98 DataType: tools.ContentDownload, 99 ValidConstraint: validToolsConstraint, 100 }) 101 } 102 103 type simplestreamsSuite struct { 104 sstesting.LocalLiveSimplestreamsSuite 105 sstesting.TestDataSuite 106 } 107 108 func (s *simplestreamsSuite) SetUpSuite(c *gc.C) { 109 s.LocalLiveSimplestreamsSuite.SetUpSuite(c) 110 s.TestDataSuite.SetUpSuite(c) 111 } 112 113 func (s *simplestreamsSuite) TearDownSuite(c *gc.C) { 114 s.TestDataSuite.TearDownSuite(c) 115 s.LocalLiveSimplestreamsSuite.TearDownSuite(c) 116 } 117 118 var fetchTests = []struct { 119 region string 120 series string 121 version string 122 major int 123 minor int 124 released bool 125 arches []string 126 tools []*tools.ToolsMetadata 127 }{{ 128 series: "precise", 129 arches: []string{"amd64", "arm"}, 130 version: "1.13.0", 131 tools: []*tools.ToolsMetadata{ 132 { 133 Release: "precise", 134 Version: "1.13.0", 135 Arch: "amd64", 136 Size: 2973595, 137 Path: "tools/releases/20130806/juju-1.13.0-precise-amd64.tgz", 138 FileType: "tar.gz", 139 SHA256: "447aeb6a934a5eaec4f703eda4ef2dde", 140 }, 141 }, 142 }, { 143 series: "raring", 144 arches: []string{"amd64", "arm"}, 145 version: "1.13.0", 146 tools: []*tools.ToolsMetadata{ 147 { 148 Release: "raring", 149 Version: "1.13.0", 150 Arch: "amd64", 151 Size: 2973173, 152 Path: "tools/releases/20130806/juju-1.13.0-raring-amd64.tgz", 153 FileType: "tar.gz", 154 SHA256: "df07ac5e1fb4232d4e9aa2effa57918a", 155 }, 156 }, 157 }, { 158 series: "raring", 159 arches: []string{"amd64", "arm"}, 160 version: "1.11.4", 161 tools: []*tools.ToolsMetadata{ 162 { 163 Release: "raring", 164 Version: "1.11.4", 165 Arch: "arm", 166 Size: 1950327, 167 Path: "tools/releases/20130806/juju-1.11.4-raring-arm.tgz", 168 FileType: "tar.gz", 169 SHA256: "6472014e3255e3fe7fbd3550ef3f0a11", 170 }, 171 }, 172 }, { 173 series: "precise", 174 arches: []string{"amd64", "arm"}, 175 major: 2, 176 tools: []*tools.ToolsMetadata{ 177 { 178 Release: "precise", 179 Version: "2.0.1", 180 Arch: "arm", 181 Size: 1951096, 182 Path: "tools/releases/20130806/juju-2.0.1-precise-arm.tgz", 183 FileType: "tar.gz", 184 SHA256: "f65a92b3b41311bdf398663ee1c5cd0c", 185 }, 186 }, 187 }, { 188 series: "precise", 189 arches: []string{"amd64", "arm"}, 190 major: 1, 191 minor: 11, 192 tools: []*tools.ToolsMetadata{ 193 { 194 Release: "precise", 195 Version: "1.11.4", 196 Arch: "arm", 197 Size: 1951096, 198 Path: "tools/releases/20130806/juju-1.11.4-precise-arm.tgz", 199 FileType: "tar.gz", 200 SHA256: "f65a92b3b41311bdf398663ee1c5cd0c", 201 }, 202 { 203 Release: "precise", 204 Version: "1.11.5", 205 Arch: "arm", 206 Size: 2031281, 207 Path: "tools/releases/20130803/juju-1.11.5-precise-arm.tgz", 208 FileType: "tar.gz", 209 SHA256: "df07ac5e1fb4232d4e9aa2effa57918a", 210 }, 211 }, 212 }, { 213 series: "raring", 214 arches: []string{"amd64", "arm"}, 215 major: 1, 216 minor: -1, 217 released: true, 218 tools: []*tools.ToolsMetadata{ 219 { 220 Release: "raring", 221 Version: "1.14.0", 222 Arch: "amd64", 223 Size: 2973173, 224 Path: "tools/releases/20130806/juju-1.14.0-raring-amd64.tgz", 225 FileType: "tar.gz", 226 SHA256: "df07ac5e1fb4232d4e9aa2effa57918a", 227 }, 228 }, 229 }} 230 231 func (s *simplestreamsSuite) TestFetch(c *gc.C) { 232 for i, t := range fetchTests { 233 c.Logf("test %d", i) 234 var toolsConstraint *tools.ToolsConstraint 235 if t.version == "" { 236 toolsConstraint = tools.NewGeneralToolsConstraint(t.major, t.minor, t.released, simplestreams.LookupParams{ 237 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 238 Series: []string{t.series}, 239 Arches: t.arches, 240 }) 241 } else { 242 toolsConstraint = tools.NewVersionedToolsConstraint(version.MustParse(t.version), 243 simplestreams.LookupParams{ 244 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 245 Series: []string{t.series}, 246 Arches: t.arches, 247 }) 248 } 249 // Add invalid datasource and check later that resolveInfo is correct. 250 invalidSource := simplestreams.NewURLDataSource("invalid", "file://invalid", utils.VerifySSLHostnames) 251 tools, resolveInfo, err := tools.Fetch( 252 []simplestreams.DataSource{invalidSource, s.Source}, 253 simplestreams.DefaultIndexPath, toolsConstraint, s.RequireSigned) 254 if !c.Check(err, gc.IsNil) { 255 continue 256 } 257 for _, tm := range t.tools { 258 tm.FullPath, err = s.Source.URL(tm.Path) 259 c.Assert(err, gc.IsNil) 260 } 261 c.Check(tools, gc.DeepEquals, t.tools) 262 c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 263 Source: "test", 264 Signed: s.RequireSigned, 265 IndexURL: "test:/streams/v1/index.json", 266 MirrorURL: "", 267 }) 268 } 269 } 270 271 func (s *simplestreamsSuite) TestFetchWithMirror(c *gc.C) { 272 toolsConstraint := tools.NewGeneralToolsConstraint(1, 13, false, simplestreams.LookupParams{ 273 CloudSpec: simplestreams.CloudSpec{"us-west-2", "https://ec2.us-west-2.amazonaws.com"}, 274 Series: []string{"precise"}, 275 Arches: []string{"amd64"}, 276 }) 277 toolsMetadata, resolveInfo, err := tools.Fetch( 278 []simplestreams.DataSource{s.Source}, simplestreams.DefaultIndexPath, toolsConstraint, s.RequireSigned) 279 c.Assert(err, gc.IsNil) 280 c.Assert(len(toolsMetadata), gc.Equals, 1) 281 282 expectedMetadata := &tools.ToolsMetadata{ 283 Release: "precise", 284 Version: "1.13.0", 285 Arch: "amd64", 286 Size: 2973595, 287 Path: "mirrored-path/juju-1.13.0-precise-amd64.tgz", 288 FullPath: "test:/mirrored-path/juju-1.13.0-precise-amd64.tgz", 289 FileType: "tar.gz", 290 SHA256: "447aeb6a934a5eaec4f703eda4ef2dde", 291 } 292 c.Assert(err, gc.IsNil) 293 c.Assert(toolsMetadata[0], gc.DeepEquals, expectedMetadata) 294 c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 295 Source: "test", 296 Signed: s.RequireSigned, 297 IndexURL: "test:/streams/v1/index.json", 298 MirrorURL: "test:/", 299 }) 300 } 301 302 func assertMetadataMatches(c *gc.C, storageDir string, toolList coretools.List, metadata []*tools.ToolsMetadata) { 303 var expectedMetadata []*tools.ToolsMetadata = make([]*tools.ToolsMetadata, len(toolList)) 304 for i, tool := range toolList { 305 expectedMetadata[i] = &tools.ToolsMetadata{ 306 Release: tool.Version.Series, 307 Version: tool.Version.Number.String(), 308 Arch: tool.Version.Arch, 309 Size: tool.Size, 310 Path: fmt.Sprintf("releases/juju-%s.tgz", tool.Version.String()), 311 FileType: "tar.gz", 312 SHA256: tool.SHA256, 313 } 314 } 315 c.Assert(metadata, gc.DeepEquals, expectedMetadata) 316 } 317 318 func (s *simplestreamsSuite) TestWriteMetadataNoFetch(c *gc.C) { 319 toolsList := coretools.List{ 320 { 321 Version: version.MustParseBinary("1.2.3-precise-amd64"), 322 Size: 123, 323 SHA256: "abcd", 324 }, { 325 Version: version.MustParseBinary("2.0.1-raring-amd64"), 326 Size: 456, 327 SHA256: "xyz", 328 }, 329 } 330 dir := c.MkDir() 331 writer, err := filestorage.NewFileStorageWriter(dir) 332 c.Assert(err, gc.IsNil) 333 err = tools.MergeAndWriteMetadata(writer, toolsList, tools.DoNotWriteMirrors) 334 c.Assert(err, gc.IsNil) 335 metadata := toolstesting.ParseMetadataFromDir(c, dir, false) 336 assertMetadataMatches(c, dir, toolsList, metadata) 337 } 338 339 func (s *simplestreamsSuite) assertWriteMetadata(c *gc.C, withMirrors bool) { 340 var versionStrings = []string{ 341 "1.2.3-precise-amd64", 342 "2.0.1-raring-amd64", 343 } 344 dir := c.MkDir() 345 toolstesting.MakeTools(c, dir, "releases", versionStrings) 346 347 toolsList := coretools.List{ 348 { 349 // If sha256/size is already known, do not recalculate 350 Version: version.MustParseBinary("1.2.3-precise-amd64"), 351 Size: 123, 352 SHA256: "abcd", 353 }, { 354 Version: version.MustParseBinary("2.0.1-raring-amd64"), 355 // The URL is not used for generating metadata. 356 URL: "bogus://", 357 }, 358 } 359 writer, err := filestorage.NewFileStorageWriter(dir) 360 c.Assert(err, gc.IsNil) 361 writeMirrors := tools.DoNotWriteMirrors 362 if withMirrors { 363 writeMirrors = tools.WriteMirrors 364 } 365 err = tools.MergeAndWriteMetadata(writer, toolsList, writeMirrors) 366 c.Assert(err, gc.IsNil) 367 metadata := toolstesting.ParseMetadataFromDir(c, dir, withMirrors) 368 assertMetadataMatches(c, dir, toolsList, metadata) 369 } 370 371 func (s *simplestreamsSuite) TestWriteMetadata(c *gc.C) { 372 s.assertWriteMetadata(c, false) 373 } 374 375 func (s *simplestreamsSuite) TestWriteMetadataWithMirrors(c *gc.C) { 376 s.assertWriteMetadata(c, true) 377 } 378 379 func (s *simplestreamsSuite) TestWriteMetadataMergeWithExisting(c *gc.C) { 380 dir := c.MkDir() 381 existingToolsList := coretools.List{ 382 { 383 Version: version.MustParseBinary("1.2.3-precise-amd64"), 384 Size: 123, 385 SHA256: "abc", 386 }, { 387 Version: version.MustParseBinary("2.0.1-raring-amd64"), 388 Size: 456, 389 SHA256: "xyz", 390 }, 391 } 392 writer, err := filestorage.NewFileStorageWriter(dir) 393 c.Assert(err, gc.IsNil) 394 err = tools.MergeAndWriteMetadata(writer, existingToolsList, tools.DoNotWriteMirrors) 395 c.Assert(err, gc.IsNil) 396 newToolsList := coretools.List{ 397 existingToolsList[0], 398 { 399 Version: version.MustParseBinary("2.1.0-raring-amd64"), 400 Size: 789, 401 SHA256: "def", 402 }, 403 } 404 err = tools.MergeAndWriteMetadata(writer, newToolsList, tools.DoNotWriteMirrors) 405 c.Assert(err, gc.IsNil) 406 requiredToolsList := append(existingToolsList, newToolsList[1]) 407 metadata := toolstesting.ParseMetadataFromDir(c, dir, false) 408 assertMetadataMatches(c, dir, requiredToolsList, metadata) 409 } 410 411 type productSpecSuite struct{} 412 413 var _ = gc.Suite(&productSpecSuite{}) 414 415 func (s *productSpecSuite) TestId(c *gc.C) { 416 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 417 Series: []string{"precise"}, 418 Arches: []string{"amd64"}, 419 }) 420 ids, err := toolsConstraint.Ids() 421 c.Assert(err, gc.IsNil) 422 c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:12.04:amd64"}) 423 } 424 425 func (s *productSpecSuite) TestIdMultiArch(c *gc.C) { 426 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{ 427 Series: []string{"precise"}, 428 Arches: []string{"amd64", "arm"}, 429 }) 430 ids, err := toolsConstraint.Ids() 431 c.Assert(err, gc.IsNil) 432 c.Assert(ids, gc.DeepEquals, []string{ 433 "com.ubuntu.juju:12.04:amd64", 434 "com.ubuntu.juju:12.04:arm"}) 435 } 436 437 func (s *productSpecSuite) TestIdMultiSeries(c *gc.C) { 438 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{ 439 Series: []string{"precise", "raring"}, 440 Arches: []string{"amd64"}, 441 }) 442 ids, err := toolsConstraint.Ids() 443 c.Assert(err, gc.IsNil) 444 c.Assert(ids, gc.DeepEquals, []string{ 445 "com.ubuntu.juju:12.04:amd64", 446 "com.ubuntu.juju:13.04:amd64"}) 447 } 448 449 func (s *productSpecSuite) TestIdWithMajorVersionOnly(c *gc.C) { 450 toolsConstraint := tools.NewGeneralToolsConstraint(1, -1, false, simplestreams.LookupParams{ 451 Series: []string{"precise"}, 452 Arches: []string{"amd64"}, 453 }) 454 ids, err := toolsConstraint.Ids() 455 c.Assert(err, gc.IsNil) 456 c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`}) 457 } 458 459 func (s *productSpecSuite) TestIdWithMajorMinorVersion(c *gc.C) { 460 toolsConstraint := tools.NewGeneralToolsConstraint(1, 2, false, simplestreams.LookupParams{ 461 Series: []string{"precise"}, 462 Arches: []string{"amd64"}, 463 }) 464 ids, err := toolsConstraint.Ids() 465 c.Assert(err, gc.IsNil) 466 c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`}) 467 } 468 469 func (s *productSpecSuite) TestLargeNumber(c *gc.C) { 470 json := `{ 471 "updated": "Fri, 30 Aug 2013 16:12:58 +0800", 472 "format": "products:1.0", 473 "products": { 474 "com.ubuntu.juju:1.10.0:amd64": { 475 "version": "1.10.0", 476 "arch": "amd64", 477 "versions": { 478 "20133008": { 479 "items": { 480 "1.10.0-precise-amd64": { 481 "release": "precise", 482 "version": "1.10.0", 483 "arch": "amd64", 484 "size": 9223372036854775807, 485 "path": "releases/juju-1.10.0-precise-amd64.tgz", 486 "ftype": "tar.gz", 487 "sha256": "" 488 } 489 } 490 } 491 } 492 } 493 } 494 }` 495 cloudMetadata, err := simplestreams.ParseCloudMetadata([]byte(json), "products:1.0", "", tools.ToolsMetadata{}) 496 c.Assert(err, gc.IsNil) 497 c.Assert(cloudMetadata.Products, gc.HasLen, 1) 498 product := cloudMetadata.Products["com.ubuntu.juju:1.10.0:amd64"] 499 c.Assert(product, gc.NotNil) 500 c.Assert(product.Items, gc.HasLen, 1) 501 version := product.Items["20133008"] 502 c.Assert(version, gc.NotNil) 503 c.Assert(version.Items, gc.HasLen, 1) 504 item := version.Items["1.10.0-precise-amd64"] 505 c.Assert(item, gc.NotNil) 506 c.Assert(item, gc.FitsTypeOf, &tools.ToolsMetadata{}) 507 c.Assert(item.(*tools.ToolsMetadata).Size, gc.Equals, int64(9223372036854775807)) 508 } 509 510 type metadataHelperSuite struct { 511 coretesting.BaseSuite 512 } 513 514 var _ = gc.Suite(&metadataHelperSuite{}) 515 516 func (*metadataHelperSuite) TestMetadataFromTools(c *gc.C) { 517 metadata := tools.MetadataFromTools(nil) 518 c.Assert(metadata, gc.HasLen, 0) 519 520 toolsList := coretools.List{{ 521 Version: version.MustParseBinary("1.2.3-precise-amd64"), 522 Size: 123, 523 SHA256: "abc", 524 }, { 525 Version: version.MustParseBinary("2.0.1-raring-amd64"), 526 URL: "file:///tmp/releases/juju-2.0.1-raring-amd64.tgz", 527 Size: 456, 528 SHA256: "xyz", 529 }} 530 metadata = tools.MetadataFromTools(toolsList) 531 c.Assert(metadata, gc.HasLen, len(toolsList)) 532 for i, t := range toolsList { 533 md := metadata[i] 534 c.Assert(md.Release, gc.Equals, t.Version.Series) 535 c.Assert(md.Version, gc.Equals, t.Version.Number.String()) 536 c.Assert(md.Arch, gc.Equals, t.Version.Arch) 537 // FullPath is only filled out when reading tools using simplestreams. 538 // It's not needed elsewhere and requires a URL() call. 539 c.Assert(md.FullPath, gc.Equals, "") 540 c.Assert(md.Path, gc.Equals, tools.StorageName(t.Version)[len("tools/"):]) 541 c.Assert(md.FileType, gc.Equals, "tar.gz") 542 c.Assert(md.Size, gc.Equals, t.Size) 543 c.Assert(md.SHA256, gc.Equals, t.SHA256) 544 } 545 } 546 547 type countingStorage struct { 548 storage.StorageReader 549 counter int 550 } 551 552 func (c *countingStorage) Get(name string) (io.ReadCloser, error) { 553 c.counter++ 554 return c.StorageReader.Get(name) 555 } 556 557 func (*metadataHelperSuite) TestResolveMetadata(c *gc.C) { 558 var versionStrings = []string{"1.2.3-precise-amd64"} 559 dir := c.MkDir() 560 toolstesting.MakeTools(c, dir, "releases", versionStrings) 561 toolsList := coretools.List{{ 562 Version: version.MustParseBinary(versionStrings[0]), 563 Size: 123, 564 SHA256: "abc", 565 }} 566 567 stor, err := filestorage.NewFileStorageReader(dir) 568 c.Assert(err, gc.IsNil) 569 err = tools.ResolveMetadata(stor, nil) 570 c.Assert(err, gc.IsNil) 571 572 // We already have size/sha256, so ensure that storage isn't consulted. 573 countingStorage := &countingStorage{StorageReader: stor} 574 metadata := tools.MetadataFromTools(toolsList) 575 err = tools.ResolveMetadata(countingStorage, metadata) 576 c.Assert(err, gc.IsNil) 577 c.Assert(countingStorage.counter, gc.Equals, 0) 578 579 // Now clear size/sha256, and check that it is called, and 580 // the size/sha256 sum are updated. 581 metadata[0].Size = 0 582 metadata[0].SHA256 = "" 583 err = tools.ResolveMetadata(countingStorage, metadata) 584 c.Assert(err, gc.IsNil) 585 c.Assert(countingStorage.counter, gc.Equals, 1) 586 c.Assert(metadata[0].Size, gc.Not(gc.Equals), 0) 587 c.Assert(metadata[0].SHA256, gc.Not(gc.Equals), "") 588 } 589 590 func (*metadataHelperSuite) TestMergeMetadata(c *gc.C) { 591 md1 := &tools.ToolsMetadata{ 592 Release: "precise", 593 Version: "1.2.3", 594 Arch: "amd64", 595 Path: "path1", 596 } 597 md2 := &tools.ToolsMetadata{ 598 Release: "precise", 599 Version: "1.2.3", 600 Arch: "amd64", 601 Path: "path2", 602 } 603 md3 := &tools.ToolsMetadata{ 604 Release: "raring", 605 Version: "1.2.3", 606 Arch: "amd64", 607 Path: "path3", 608 } 609 610 withSize := func(md *tools.ToolsMetadata, size int64) *tools.ToolsMetadata { 611 clone := *md 612 clone.Size = size 613 return &clone 614 } 615 withSHA256 := func(md *tools.ToolsMetadata, sha256 string) *tools.ToolsMetadata { 616 clone := *md 617 clone.SHA256 = sha256 618 return &clone 619 } 620 621 type mdlist []*tools.ToolsMetadata 622 type test struct { 623 name string 624 lhs, rhs, merged []*tools.ToolsMetadata 625 err string 626 } 627 tests := []test{{ 628 name: "non-empty lhs, empty rhs", 629 lhs: mdlist{md1}, 630 rhs: nil, 631 merged: mdlist{md1}, 632 }, { 633 name: "empty lhs, non-empty rhs", 634 lhs: nil, 635 rhs: mdlist{md2}, 636 merged: mdlist{md2}, 637 }, { 638 name: "identical lhs, rhs", 639 lhs: mdlist{md1}, 640 rhs: mdlist{md1}, 641 merged: mdlist{md1}, 642 }, { 643 name: "same tools in lhs and rhs, neither have size: prefer lhs", 644 lhs: mdlist{md1}, 645 rhs: mdlist{md2}, 646 merged: mdlist{md1}, 647 }, { 648 name: "same tools in lhs and rhs, only lhs has a size: prefer lhs", 649 lhs: mdlist{withSize(md1, 123)}, 650 rhs: mdlist{md2}, 651 merged: mdlist{withSize(md1, 123)}, 652 }, { 653 name: "same tools in lhs and rhs, only rhs has a size: prefer rhs", 654 lhs: mdlist{md1}, 655 rhs: mdlist{withSize(md2, 123)}, 656 merged: mdlist{withSize(md2, 123)}, 657 }, { 658 name: "same tools in lhs and rhs, both have the same size: prefer lhs", 659 lhs: mdlist{withSize(md1, 123)}, 660 rhs: mdlist{withSize(md2, 123)}, 661 merged: mdlist{withSize(md1, 123)}, 662 }, { 663 name: "same tools in lhs and rhs, both have different sizes: error", 664 lhs: mdlist{withSize(md1, 123)}, 665 rhs: mdlist{withSize(md2, 456)}, 666 err: "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,456\\) sha256=\\(,\\)", 667 }, { 668 name: "same tools in lhs and rhs, both have same size but different sha256: error", 669 lhs: mdlist{withSHA256(withSize(md1, 123), "a")}, 670 rhs: mdlist{withSHA256(withSize(md2, 123), "b")}, 671 err: "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,123\\) sha256=\\(a,b\\)", 672 }, { 673 name: "lhs is a proper superset of rhs: union of lhs and rhs", 674 lhs: mdlist{md1, md3}, 675 rhs: mdlist{md1}, 676 merged: mdlist{md1, md3}, 677 }, { 678 name: "rhs is a proper superset of lhs: union of lhs and rhs", 679 lhs: mdlist{md1}, 680 rhs: mdlist{md1, md3}, 681 merged: mdlist{md1, md3}, 682 }} 683 for i, test := range tests { 684 c.Logf("test %d: %s", i, test.name) 685 merged, err := tools.MergeMetadata(test.lhs, test.rhs) 686 if test.err == "" { 687 c.Assert(err, gc.IsNil) 688 c.Assert(merged, gc.DeepEquals, test.merged) 689 } else { 690 c.Assert(err, gc.ErrorMatches, test.err) 691 c.Assert(merged, gc.IsNil) 692 } 693 } 694 } 695 696 func (*metadataHelperSuite) TestReadWriteMetadata(c *gc.C) { 697 metadata := []*tools.ToolsMetadata{{ 698 Release: "precise", 699 Version: "1.2.3", 700 Arch: "amd64", 701 Path: "path1", 702 }, { 703 Release: "raring", 704 Version: "1.2.3", 705 Arch: "amd64", 706 Path: "path2", 707 }} 708 709 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 710 c.Assert(err, gc.IsNil) 711 out, err := tools.ReadMetadata(stor) 712 c.Assert(out, gc.HasLen, 0) 713 c.Assert(err, gc.IsNil) // non-existence is not an error 714 err = tools.WriteMetadata(stor, metadata, tools.DoNotWriteMirrors) 715 c.Assert(err, gc.IsNil) 716 out, err = tools.ReadMetadata(stor) 717 for _, md := range out { 718 // FullPath is set by ReadMetadata. 719 c.Assert(md.FullPath, gc.Not(gc.Equals), "") 720 md.FullPath = "" 721 } 722 c.Assert(out, gc.DeepEquals, metadata) 723 } 724 725 type signedSuite struct { 726 origKey string 727 } 728 729 var testRoundTripper *jujutest.ProxyRoundTripper 730 731 func init() { 732 testRoundTripper = &jujutest.ProxyRoundTripper{} 733 testRoundTripper.RegisterForScheme("signedtest") 734 } 735 736 func (s *signedSuite) SetUpSuite(c *gc.C) { 737 var imageData = map[string]string{ 738 "/unsigned/streams/v1/index.json": unsignedIndex, 739 "/unsigned/streams/v1/tools_metadata.json": unsignedProduct, 740 } 741 742 // Set up some signed data from the unsigned data. 743 // Overwrite the product path to use the sjson suffix. 744 rawUnsignedIndex := strings.Replace( 745 unsignedIndex, "streams/v1/tools_metadata.json", "streams/v1/tools_metadata.sjson", -1) 746 r := bytes.NewReader([]byte(rawUnsignedIndex)) 747 signedData, err := simplestreams.Encode( 748 r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 749 c.Assert(err, gc.IsNil) 750 imageData["/signed/streams/v1/index.sjson"] = string(signedData) 751 752 // Replace the tools path in the unsigned data with a different one so we can test that the right 753 // tools path is used. 754 rawUnsignedProduct := strings.Replace( 755 unsignedProduct, "juju-1.13.0", "juju-1.13.1", -1) 756 r = bytes.NewReader([]byte(rawUnsignedProduct)) 757 signedData, err = simplestreams.Encode( 758 r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase) 759 c.Assert(err, gc.IsNil) 760 imageData["/signed/streams/v1/tools_metadata.sjson"] = string(signedData) 761 testRoundTripper.Sub = jujutest.NewCannedRoundTripper( 762 imageData, map[string]int{"signedtest://unauth": http.StatusUnauthorized}) 763 s.origKey = tools.SetSigningPublicKey(sstesting.SignedMetadataPublicKey) 764 } 765 766 func (s *signedSuite) TearDownSuite(c *gc.C) { 767 testRoundTripper.Sub = nil 768 tools.SetSigningPublicKey(s.origKey) 769 } 770 771 func (s *signedSuite) TestSignedToolsMetadata(c *gc.C) { 772 signedSource := simplestreams.NewURLDataSource("test", "signedtest://host/signed", utils.VerifySSLHostnames) 773 toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{ 774 CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"}, 775 Series: []string{"precise"}, 776 Arches: []string{"amd64"}, 777 }) 778 toolsMetadata, resolveInfo, err := tools.Fetch( 779 []simplestreams.DataSource{signedSource}, simplestreams.DefaultIndexPath, toolsConstraint, true) 780 c.Assert(err, gc.IsNil) 781 c.Assert(len(toolsMetadata), gc.Equals, 1) 782 c.Assert(toolsMetadata[0].Path, gc.Equals, "tools/releases/20130806/juju-1.13.1-precise-amd64.tgz") 783 c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{ 784 Source: "test", 785 Signed: true, 786 IndexURL: "signedtest://host/signed/streams/v1/index.sjson", 787 MirrorURL: "", 788 }) 789 } 790 791 var unsignedIndex = ` 792 { 793 "index": { 794 "com.ubuntu.juju:released:tools": { 795 "updated": "Mon, 05 Aug 2013 11:07:04 +0000", 796 "datatype": "content-download", 797 "format": "products:1.0", 798 "products": [ 799 "com.ubuntu.juju:12.04:amd64" 800 ], 801 "path": "streams/v1/tools_metadata.json" 802 } 803 }, 804 "updated": "Wed, 01 May 2013 13:31:26 +0000", 805 "format": "index:1.0" 806 } 807 ` 808 var unsignedProduct = ` 809 { 810 "updated": "Wed, 01 May 2013 13:31:26 +0000", 811 "content_id": "com.ubuntu.cloud:released:aws", 812 "datatype": "content-download", 813 "products": { 814 "com.ubuntu.juju:12.04:amd64": { 815 "arch": "amd64", 816 "release": "precise", 817 "versions": { 818 "20130806": { 819 "items": { 820 "1130preciseamd64": { 821 "version": "1.13.0", 822 "size": 2973595, 823 "path": "tools/releases/20130806/juju-1.13.0-precise-amd64.tgz", 824 "ftype": "tar.gz", 825 "sha256": "447aeb6a934a5eaec4f703eda4ef2dde" 826 } 827 } 828 } 829 } 830 } 831 }, 832 "format": "products:1.0" 833 } 834 `