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