github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/environs/sync/sync_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package sync_test 5 6 import ( 7 "bytes" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "os/exec" 13 "path" 14 "path/filepath" 15 "runtime" 16 "sort" 17 "testing" 18 19 "github.com/juju/errors" 20 gitjujutesting "github.com/juju/testing" 21 jc "github.com/juju/testing/checkers" 22 "github.com/juju/utils" 23 "github.com/juju/utils/arch" 24 "github.com/juju/utils/series" 25 "github.com/juju/version" 26 gc "gopkg.in/check.v1" 27 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/filestorage" 30 "github.com/juju/juju/environs/simplestreams" 31 "github.com/juju/juju/environs/storage" 32 "github.com/juju/juju/environs/sync" 33 envtesting "github.com/juju/juju/environs/testing" 34 envtools "github.com/juju/juju/environs/tools" 35 toolstesting "github.com/juju/juju/environs/tools/testing" 36 coretesting "github.com/juju/juju/testing" 37 coretools "github.com/juju/juju/tools" 38 jujuversion "github.com/juju/juju/version" 39 ) 40 41 func TestPackage(t *testing.T) { 42 gc.TestingT(t) 43 } 44 45 type syncSuite struct { 46 coretesting.FakeJujuXDGDataHomeSuite 47 envtesting.ToolsFixture 48 storage storage.Storage 49 localStorage string 50 } 51 52 var _ = gc.Suite(&syncSuite{}) 53 var _ = gc.Suite(&uploadSuite{}) 54 var _ = gc.Suite(&badBuildSuite{}) 55 56 func (s *syncSuite) setUpTest(c *gc.C) { 57 if runtime.GOOS == "windows" { 58 c.Skip("issue 1403084: Currently does not work because of jujud problems") 59 } 60 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 61 s.ToolsFixture.SetUpTest(c) 62 63 // It's important that this be v1.8.x to match the test data. 64 s.PatchValue(&jujuversion.Current, version.MustParse("1.8.3")) 65 66 // Create a source storage. 67 baseDir := c.MkDir() 68 stor, err := filestorage.NewFileStorageWriter(baseDir) 69 c.Assert(err, jc.ErrorIsNil) 70 s.storage = stor 71 72 // Create a local tools directory. 73 s.localStorage = c.MkDir() 74 75 // Populate both local and default tools locations with the public tools. 76 versionStrings := make([]string, len(vAll)) 77 for i, vers := range vAll { 78 versionStrings[i] = vers.String() 79 } 80 toolstesting.MakeTools(c, baseDir, "released", versionStrings) 81 toolstesting.MakeTools(c, s.localStorage, "released", versionStrings) 82 83 // Switch the default tools location. 84 baseURL, err := s.storage.URL(storage.BaseToolsPath) 85 c.Assert(err, jc.ErrorIsNil) 86 s.PatchValue(&envtools.DefaultBaseURL, baseURL) 87 } 88 89 func (s *syncSuite) tearDownTest(c *gc.C) { 90 s.ToolsFixture.TearDownTest(c) 91 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 92 } 93 94 var tests = []struct { 95 description string 96 ctx *sync.SyncContext 97 source bool 98 tools []version.Binary 99 version version.Number 100 major int 101 minor int 102 }{ 103 { 104 description: "copy newest from the filesystem", 105 ctx: &sync.SyncContext{}, 106 source: true, 107 tools: v180all, 108 }, 109 { 110 description: "copy newest from the dummy model", 111 ctx: &sync.SyncContext{}, 112 tools: v180all, 113 }, 114 { 115 description: "copy matching dev from the dummy model", 116 ctx: &sync.SyncContext{}, 117 version: version.MustParse("1.9.3"), 118 tools: v190all, 119 }, 120 { 121 description: "copy matching major, minor from the dummy model", 122 ctx: &sync.SyncContext{}, 123 major: 3, 124 minor: 2, 125 tools: []version.Binary{v320p64}, 126 }, 127 { 128 description: "copy matching major, minor dev from the dummy model", 129 ctx: &sync.SyncContext{}, 130 major: 3, 131 minor: 1, 132 tools: []version.Binary{v310p64}, 133 }, 134 { 135 description: "copy all from the dummy model", 136 ctx: &sync.SyncContext{ 137 AllVersions: true, 138 }, 139 tools: v1all, 140 }, 141 } 142 143 func (s *syncSuite) TestSyncing(c *gc.C) { 144 for i, test := range tests { 145 // Perform all tests in a "clean" environment. 146 func() { 147 s.setUpTest(c) 148 defer s.tearDownTest(c) 149 150 c.Logf("test %d: %s", i, test.description) 151 152 if test.source { 153 test.ctx.Source = s.localStorage 154 } 155 if test.version != version.Zero { 156 jujuversion.Current = test.version 157 } 158 if test.major > 0 { 159 test.ctx.MajorVersion = test.major 160 test.ctx.MinorVersion = test.minor 161 } 162 uploader := fakeToolsUploader{ 163 uploaded: make(map[version.Binary]bool), 164 } 165 test.ctx.TargetToolsFinder = mockToolsFinder{} 166 test.ctx.TargetToolsUploader = &uploader 167 168 err := sync.SyncTools(test.ctx) 169 c.Assert(err, jc.ErrorIsNil) 170 171 ds, err := sync.SelectSourceDatasource(test.ctx) 172 c.Assert(err, jc.ErrorIsNil) 173 174 // This data source does not require to contain signed data. 175 // However, it may still contain it. 176 // Since we will always try to read signed data first, 177 // we want to be able to try to read this signed data 178 // with public key with Juju-known public key for tools. 179 // Bugs #1542127, #1542131 180 c.Assert(ds.PublicSigningKey(), gc.Not(gc.Equals), "") 181 182 var uploaded []version.Binary 183 for v := range uploader.uploaded { 184 uploaded = append(uploaded, v) 185 } 186 c.Assert(uploaded, jc.SameContents, test.tools) 187 }() 188 } 189 } 190 191 type fakeToolsUploader struct { 192 uploaded map[version.Binary]bool 193 } 194 195 func (u *fakeToolsUploader) UploadTools(toolsDir, stream string, tools *coretools.Tools, data []byte) error { 196 u.uploaded[tools.Version] = true 197 return nil 198 } 199 200 var ( 201 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 202 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 203 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 204 v100all = []version.Binary{v100p64, v100q64, v100q32} 205 v180q64 = version.MustParseBinary("1.8.0-quantal-amd64") 206 v180p32 = version.MustParseBinary("1.8.0-precise-i386") 207 v180all = []version.Binary{v180q64, v180p32} 208 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 209 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 210 v190all = []version.Binary{v190q64, v190p32} 211 v1all = append(append(v100all, v180all...), v190all...) 212 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 213 v310p64 = version.MustParseBinary("3.1.0-precise-amd64") 214 v320p64 = version.MustParseBinary("3.2.0-precise-amd64") 215 vAll = append(append(v1all, v200p64), v310p64, v320p64) 216 ) 217 218 type uploadSuite struct { 219 env environs.Environ 220 coretesting.FakeJujuXDGDataHomeSuite 221 envtesting.ToolsFixture 222 targetStorage storage.Storage 223 } 224 225 func (s *uploadSuite) SetUpTest(c *gc.C) { 226 if runtime.GOOS == "windows" { 227 c.Skip("issue 1403084: Currently does not work because of jujud problems") 228 } 229 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 230 s.ToolsFixture.SetUpTest(c) 231 232 // Create a target storage. 233 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 234 c.Assert(err, jc.ErrorIsNil) 235 s.targetStorage = stor 236 237 // Mock out building of tools. Sync should not care about the contents 238 // of tools archives, other than that they hash correctly. 239 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 240 } 241 242 func (s *uploadSuite) assertEqualsCurrentVersion(c *gc.C, v version.Binary) { 243 c.Assert(v, gc.Equals, version.Binary{Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries()}) 244 } 245 246 func (s *uploadSuite) TearDownTest(c *gc.C) { 247 s.ToolsFixture.TearDownTest(c) 248 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 249 } 250 251 func (s *uploadSuite) TestUpload(c *gc.C) { 252 t, err := sync.Upload(s.targetStorage, "released", nil) 253 c.Assert(err, jc.ErrorIsNil) 254 s.assertEqualsCurrentVersion(c, t.Version) 255 c.Assert(t.URL, gc.Not(gc.Equals), "") 256 s.assertUploadedTools(c, t, []string{series.HostSeries()}, "released") 257 } 258 259 func (s *uploadSuite) TestUploadFakeSeries(c *gc.C) { 260 seriesToUpload := "precise" 261 if seriesToUpload == series.HostSeries() { 262 seriesToUpload = "raring" 263 } 264 t, err := sync.Upload(s.targetStorage, "released", nil, "quantal", seriesToUpload) 265 c.Assert(err, jc.ErrorIsNil) 266 s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "released") 267 } 268 269 func (s *uploadSuite) TestUploadAndForceVersion(c *gc.C) { 270 // This test actually tests three things: 271 // the writing of the FORCE-VERSION file; 272 // the reading of the FORCE-VERSION file by the version package; 273 // and the reading of the version from jujud. 274 vers := jujuversion.Current 275 vers.Patch++ 276 t, err := sync.Upload(s.targetStorage, "released", &vers) 277 c.Assert(err, jc.ErrorIsNil) 278 c.Assert(t.Version, gc.Equals, version.Binary{Number: vers, Arch: arch.HostArch(), Series: series.HostSeries()}) 279 } 280 281 func (s *uploadSuite) TestSyncTools(c *gc.C) { 282 builtTools, err := sync.BuildToolsTarball(nil, "released") 283 c.Assert(err, jc.ErrorIsNil) 284 t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools) 285 c.Assert(err, jc.ErrorIsNil) 286 s.assertEqualsCurrentVersion(c, t.Version) 287 c.Assert(t.URL, gc.Not(gc.Equals), "") 288 } 289 290 func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) { 291 seriesToUpload := "precise" 292 if seriesToUpload == series.HostSeries() { 293 seriesToUpload = "raring" 294 } 295 builtTools, err := sync.BuildToolsTarball(nil, "testing") 296 c.Assert(err, jc.ErrorIsNil) 297 298 t, err := sync.SyncBuiltTools(s.targetStorage, "testing", builtTools, "quantal", seriesToUpload) 299 c.Assert(err, jc.ErrorIsNil) 300 s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "testing") 301 } 302 303 func (s *uploadSuite) TestSyncAndForceVersion(c *gc.C) { 304 // This test actually tests three things: 305 // the writing of the FORCE-VERSION file; 306 // the reading of the FORCE-VERSION file by the version package; 307 // and the reading of the version from jujud. 308 vers := jujuversion.Current 309 vers.Patch++ 310 builtTools, err := sync.BuildToolsTarball(&vers, "released") 311 c.Assert(err, jc.ErrorIsNil) 312 t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools) 313 c.Assert(err, jc.ErrorIsNil) 314 c.Assert(t.Version, gc.Equals, version.Binary{Number: vers, Arch: arch.HostArch(), Series: series.HostSeries()}) 315 } 316 317 func (s *uploadSuite) assertUploadedTools(c *gc.C, t *coretools.Tools, expectSeries []string, stream string) { 318 s.assertEqualsCurrentVersion(c, t.Version) 319 expectRaw := downloadToolsRaw(c, t) 320 321 list, err := envtools.ReadList(s.targetStorage, stream, jujuversion.Current.Major, jujuversion.Current.Minor) 322 c.Assert(err, jc.ErrorIsNil) 323 c.Assert(list.AllSeries(), jc.SameContents, expectSeries) 324 sort.Strings(expectSeries) 325 c.Assert(list.AllSeries(), gc.DeepEquals, expectSeries) 326 for _, t := range list { 327 c.Logf("checking %s", t.URL) 328 c.Assert(t.Version.Number, gc.Equals, jujuversion.Current) 329 actualRaw := downloadToolsRaw(c, t) 330 c.Assert(string(actualRaw), gc.Equals, string(expectRaw)) 331 } 332 metadata, err := envtools.ReadMetadata(s.targetStorage, stream) 333 c.Assert(err, jc.ErrorIsNil) 334 c.Assert(metadata, gc.HasLen, 0) 335 } 336 337 // downloadToolsRaw downloads the supplied tools and returns the raw bytes. 338 func downloadToolsRaw(c *gc.C, t *coretools.Tools) []byte { 339 resp, err := utils.GetValidatingHTTPClient().Get(t.URL) 340 c.Assert(err, jc.ErrorIsNil) 341 defer resp.Body.Close() 342 c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) 343 var buf bytes.Buffer 344 _, err = io.Copy(&buf, resp.Body) 345 c.Assert(err, jc.ErrorIsNil) 346 return buf.Bytes() 347 } 348 349 func bundleTools(c *gc.C) (version.Binary, string, error) { 350 f, err := ioutil.TempFile("", "juju-tgz") 351 c.Assert(err, jc.ErrorIsNil) 352 defer f.Close() 353 defer os.Remove(f.Name()) 354 355 return envtools.BundleTools(f, &jujuversion.Current) 356 } 357 358 type badBuildSuite struct { 359 env environs.Environ 360 gitjujutesting.LoggingSuite 361 gitjujutesting.CleanupSuite 362 envtesting.ToolsFixture 363 } 364 365 var badGo = ` 366 #!/bin/bash --norc 367 exit 1 368 `[1:] 369 370 func (s *badBuildSuite) SetUpSuite(c *gc.C) { 371 if runtime.GOOS == "windows" { 372 c.Skip("issue 1403084: Currently does not work because of jujud problems") 373 } 374 s.CleanupSuite.SetUpSuite(c) 375 s.LoggingSuite.SetUpSuite(c) 376 } 377 378 func (s *badBuildSuite) TearDownSuite(c *gc.C) { 379 s.LoggingSuite.TearDownSuite(c) 380 s.CleanupSuite.TearDownSuite(c) 381 } 382 383 func (s *badBuildSuite) SetUpTest(c *gc.C) { 384 s.CleanupSuite.SetUpTest(c) 385 s.LoggingSuite.SetUpTest(c) 386 s.ToolsFixture.SetUpTest(c) 387 388 // Mock go cmd 389 testPath := c.MkDir() 390 s.PatchEnvPathPrepend(testPath) 391 path := filepath.Join(testPath, "go") 392 err := ioutil.WriteFile(path, []byte(badGo), 0755) 393 c.Assert(err, jc.ErrorIsNil) 394 395 // Check mocked go cmd errors 396 out, err := exec.Command("go").CombinedOutput() 397 c.Assert(err, gc.ErrorMatches, "exit status 1") 398 c.Assert(string(out), gc.Equals, "") 399 } 400 401 func (s *badBuildSuite) TearDownTest(c *gc.C) { 402 s.ToolsFixture.TearDownTest(c) 403 s.LoggingSuite.TearDownTest(c) 404 s.CleanupSuite.TearDownTest(c) 405 } 406 407 func (s *badBuildSuite) assertEqualsCurrentVersion(c *gc.C, v version.Binary) { 408 current := version.Binary{ 409 Number: jujuversion.Current, 410 Arch: arch.HostArch(), 411 Series: series.HostSeries(), 412 } 413 c.Assert(v, gc.Equals, current) 414 } 415 416 func (s *badBuildSuite) TestBundleToolsBadBuild(c *gc.C) { 417 // Test that original bundleTools Func fails as expected 418 vers, sha256Hash, err := bundleTools(c) 419 c.Assert(vers, gc.DeepEquals, version.Binary{}) 420 c.Assert(sha256Hash, gc.Equals, "") 421 c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `) 422 423 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 424 425 // Test that BundleTools func passes after it is 426 // mocked out 427 vers, sha256Hash, err = bundleTools(c) 428 c.Assert(err, jc.ErrorIsNil) 429 c.Assert(vers.Number, gc.Equals, jujuversion.Current) 430 c.Assert(sha256Hash, gc.Equals, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") 431 } 432 433 func (s *badBuildSuite) TestUploadToolsBadBuild(c *gc.C) { 434 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 435 c.Assert(err, jc.ErrorIsNil) 436 437 // Test that original Upload Func fails as expected 438 t, err := sync.Upload(stor, "released", nil) 439 c.Assert(t, gc.IsNil) 440 c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `) 441 442 // Test that Upload func passes after BundleTools func is mocked out 443 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 444 t, err = sync.Upload(stor, "released", nil) 445 c.Assert(err, jc.ErrorIsNil) 446 s.assertEqualsCurrentVersion(c, t.Version) 447 c.Assert(t.URL, gc.Not(gc.Equals), "") 448 } 449 450 func (s *badBuildSuite) TestBuildToolsBadBuild(c *gc.C) { 451 // Test that original BuildToolsTarball fails 452 builtTools, err := sync.BuildToolsTarball(nil, "released") 453 c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `) 454 c.Assert(builtTools, gc.IsNil) 455 456 // Test that BuildToolsTarball func passes after BundleTools func is 457 // mocked out 458 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 459 builtTools, err = sync.BuildToolsTarball(nil, "released") 460 s.assertEqualsCurrentVersion(c, builtTools.Version) 461 c.Assert(err, jc.ErrorIsNil) 462 } 463 464 func (s *uploadSuite) TestMockBundleTools(c *gc.C) { 465 var ( 466 writer io.Writer 467 forceVersion *version.Number 468 n int 469 p bytes.Buffer 470 ) 471 p.WriteString("Hello World") 472 473 s.PatchValue(&envtools.BundleTools, func(writerArg io.Writer, forceVersionArg *version.Number) (vers version.Binary, sha256Hash string, err error) { 474 writer = writerArg 475 n, err = writer.Write(p.Bytes()) 476 c.Assert(err, jc.ErrorIsNil) 477 forceVersion = forceVersionArg 478 return 479 }) 480 481 _, err := sync.BuildToolsTarball(&jujuversion.Current, "released") 482 c.Assert(err, jc.ErrorIsNil) 483 c.Assert(*forceVersion, gc.Equals, jujuversion.Current) 484 c.Assert(writer, gc.NotNil) 485 c.Assert(n, gc.Equals, len(p.Bytes())) 486 } 487 488 func (s *uploadSuite) TestMockBuildTools(c *gc.C) { 489 current := version.MustParseBinary("1.9.1-trusty-amd64") 490 s.PatchValue(&jujuversion.Current, current.Number) 491 s.PatchValue(&arch.HostArch, func() string { return current.Arch }) 492 s.PatchValue(&series.HostSeries, func() string { return current.Series }) 493 buildToolsFunc := toolstesting.GetMockBuildTools(c) 494 builtTools, err := buildToolsFunc(nil, "released") 495 c.Assert(err, jc.ErrorIsNil) 496 497 builtTools.Dir = "" 498 499 expectedBuiltTools := &sync.BuiltTools{ 500 StorageName: "name", 501 Version: current, 502 Size: 127, 503 Sha256Hash: "6a19d08ca4913382ca86508aa38eb8ee5b9ae2d74333fe8d862c0f9e29b82c39", 504 } 505 c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools) 506 507 vers := version.MustParseBinary("1.5.3-trusty-amd64") 508 builtTools, err = buildToolsFunc(&vers.Number, "released") 509 c.Assert(err, jc.ErrorIsNil) 510 builtTools.Dir = "" 511 expectedBuiltTools = &sync.BuiltTools{ 512 StorageName: "name", 513 Version: vers, 514 Size: 127, 515 Sha256Hash: "cad8ccedab8f26807ff379ddc2f2f78d9a7cac1276e001154cee5e39b9ddcc38", 516 } 517 c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools) 518 } 519 520 func (s *uploadSuite) TestStorageToolsUploaderWriteMirrors(c *gc.C) { 521 s.testStorageToolsUploaderWriteMirrors(c, envtools.WriteMirrors) 522 } 523 524 func (s *uploadSuite) TestStorageToolsUploaderDontWriteMirrors(c *gc.C) { 525 s.testStorageToolsUploaderWriteMirrors(c, envtools.DoNotWriteMirrors) 526 } 527 528 func (s *uploadSuite) testStorageToolsUploaderWriteMirrors(c *gc.C, writeMirrors envtools.ShouldWriteMirrors) { 529 storageDir := c.MkDir() 530 stor, err := filestorage.NewFileStorageWriter(storageDir) 531 c.Assert(err, jc.ErrorIsNil) 532 533 uploader := &sync.StorageToolsUploader{ 534 Storage: stor, 535 WriteMetadata: true, 536 WriteMirrors: writeMirrors, 537 } 538 539 err = uploader.UploadTools( 540 "released", 541 "released", 542 &coretools.Tools{ 543 Version: version.Binary{ 544 Number: jujuversion.Current, 545 Arch: arch.HostArch(), 546 Series: series.HostSeries(), 547 }, 548 Size: 7, 549 SHA256: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", 550 }, []byte("content")) 551 c.Assert(err, jc.ErrorIsNil) 552 553 mirrorsPath := simplestreams.MirrorsPath(envtools.StreamsVersionV1) + simplestreams.UnsignedSuffix 554 r, err := stor.Get(path.Join(storage.BaseToolsPath, mirrorsPath)) 555 if writeMirrors == envtools.WriteMirrors { 556 c.Assert(err, jc.ErrorIsNil) 557 data, err := ioutil.ReadAll(r) 558 r.Close() 559 c.Assert(err, jc.ErrorIsNil) 560 c.Assert(string(data), jc.Contains, `"mirrors":`) 561 } else { 562 c.Assert(err, jc.Satisfies, errors.IsNotFound) 563 } 564 } 565 566 type mockToolsFinder struct{} 567 568 func (mockToolsFinder) FindTools(major int, stream string) (coretools.List, error) { 569 return nil, coretools.ErrNoMatches 570 }