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