github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/cmd/juju/bootstrap_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "fmt" 8 "os" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 gitjujutesting "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "launchpad.net/gocheck" 16 17 "github.com/juju/juju/cmd" 18 "github.com/juju/juju/cmd/envcmd" 19 "github.com/juju/juju/constraints" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/environs/config" 22 "github.com/juju/juju/environs/configstore" 23 "github.com/juju/juju/environs/filestorage" 24 "github.com/juju/juju/environs/imagemetadata" 25 imtesting "github.com/juju/juju/environs/imagemetadata/testing" 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/juju/arch" 33 "github.com/juju/juju/provider/dummy" 34 coretesting "github.com/juju/juju/testing" 35 coretools "github.com/juju/juju/tools" 36 "github.com/juju/juju/version" 37 ) 38 39 type BootstrapSuite struct { 40 coretesting.FakeJujuHomeSuite 41 coretesting.MgoSuite 42 envtesting.ToolsFixture 43 } 44 45 var _ = gc.Suite(&BootstrapSuite{}) 46 47 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 48 s.FakeJujuHomeSuite.SetUpSuite(c) 49 s.MgoSuite.SetUpSuite(c) 50 } 51 52 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 53 s.FakeJujuHomeSuite.SetUpTest(c) 54 s.MgoSuite.SetUpTest(c) 55 s.ToolsFixture.SetUpTest(c) 56 57 // Set up a local source with tools. 58 sourceDir := createToolsSource(c, vAll) 59 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 60 61 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 62 } 63 64 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 65 s.MgoSuite.TearDownSuite(c) 66 s.FakeJujuHomeSuite.TearDownSuite(c) 67 } 68 69 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 70 s.ToolsFixture.TearDownTest(c) 71 s.MgoSuite.TearDownTest(c) 72 s.FakeJujuHomeSuite.TearDownTest(c) 73 dummy.Reset() 74 } 75 76 type bootstrapRetryTest struct { 77 info string 78 args []string 79 expectedAllowRetry []bool 80 err string 81 // If version != "", version.Current will be 82 // set to it for the duration of the test. 83 version string 84 // If addVersionToSource is true, then "version" 85 // above will be populated in the tools source. 86 addVersionToSource bool 87 } 88 89 var noToolsAvailableMessage = "cannot upload bootstrap tools: Juju cannot bootstrap because no tools are available for your environment.*" 90 var toolsNotFoundMessage = "cannot find bootstrap tools: tools not found" 91 92 var bootstrapRetryTests = []bootstrapRetryTest{{ 93 info: "no tools uploaded, first check has no retries; no matching binary in source; no second attempt", 94 expectedAllowRetry: []bool{false}, 95 err: noToolsAvailableMessage, 96 version: "1.16.0-precise-amd64", 97 }, { 98 info: "no tools uploaded, first check has no retries; matching binary in source; check after upload has retries", 99 expectedAllowRetry: []bool{false, true}, 100 err: toolsNotFoundMessage, 101 version: "1.17.0-precise-amd64", // dev version to force upload 102 addVersionToSource: true, 103 }, { 104 info: "no tools uploaded, first check has no retries; no matching binary in source; check after upload has retries", 105 expectedAllowRetry: []bool{false, true}, 106 err: toolsNotFoundMessage, 107 version: "1.15.1-precise-amd64", // dev version to force upload 108 }, { 109 info: "new tools uploaded, so we want to allow retries to give them a chance at showing up", 110 args: []string{"--upload-tools"}, 111 expectedAllowRetry: []bool{true}, 112 err: noToolsAvailableMessage, 113 }} 114 115 // Test test checks that bootstrap calls FindTools with the expected allowRetry flag. 116 func (s *BootstrapSuite) TestAllowRetries(c *gc.C) { 117 for i, test := range bootstrapRetryTests { 118 c.Logf("test %d: %s\n", i, test.info) 119 s.runAllowRetriesTest(c, test) 120 } 121 } 122 123 func (s *BootstrapSuite) runAllowRetriesTest(c *gc.C, test bootstrapRetryTest) { 124 toolsVersions := envtesting.VAll 125 if test.version != "" { 126 useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1) 127 testVersion := version.MustParseBinary(useVersion) 128 s.PatchValue(&version.Current, testVersion) 129 if test.addVersionToSource { 130 toolsVersions = append([]version.Binary{}, toolsVersions...) 131 toolsVersions = append(toolsVersions, testVersion) 132 } 133 } 134 resetJujuHome(c) 135 sourceDir := createToolsSource(c, toolsVersions) 136 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 137 138 var findToolsRetryValues []bool 139 mockFindTools := func(cloudInst environs.ConfigGetter, majorVersion, minorVersion int, 140 filter coretools.Filter, allowRetry bool) (list coretools.List, err error) { 141 findToolsRetryValues = append(findToolsRetryValues, allowRetry) 142 return nil, errors.NotFoundf("tools") 143 } 144 145 restore := envtools.TestingPatchBootstrapFindTools(mockFindTools) 146 defer restore() 147 148 _, errc := runCommand(nullContext(c), envcmd.Wrap(new(BootstrapCommand)), test.args...) 149 err := <-errc 150 c.Check(findToolsRetryValues, gc.DeepEquals, test.expectedAllowRetry) 151 stripped := strings.Replace(err.Error(), "\n", "", -1) 152 c.Check(stripped, gc.Matches, test.err) 153 } 154 155 func (s *BootstrapSuite) TestTest(c *gc.C) { 156 for i, test := range bootstrapTests { 157 c.Logf("\ntest %d: %s", i, test.info) 158 test.run(c) 159 } 160 } 161 162 type bootstrapTest struct { 163 info string 164 // binary version string used to set version.Current 165 version string 166 sync bool 167 args []string 168 err string 169 // binary version strings for expected tools; if set, no default tools 170 // will be uploaded before running the test. 171 uploads []string 172 constraints constraints.Value 173 placement string 174 hostArch string 175 } 176 177 func (test bootstrapTest) run(c *gc.C) { 178 // Create home with dummy provider and remove all 179 // of its envtools. 180 env := resetJujuHome(c) 181 182 if test.version != "" { 183 useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1) 184 origVersion := version.Current 185 version.Current = version.MustParseBinary(useVersion) 186 defer func() { version.Current = origVersion }() 187 } 188 189 if test.hostArch != "" { 190 origVersion := arch.HostArch 191 arch.HostArch = func() string { 192 return test.hostArch 193 } 194 defer func() { arch.HostArch = origVersion }() 195 } 196 197 uploadCount := len(test.uploads) 198 if uploadCount == 0 { 199 usefulVersion := version.Current 200 usefulVersion.Series = config.PreferredSeries(env.Config()) 201 envtesting.AssertUploadFakeToolsVersions(c, env.Storage(), usefulVersion) 202 } 203 204 // Run command and check for uploads. 205 opc, errc := runCommand(nullContext(c), envcmd.Wrap(new(BootstrapCommand)), test.args...) 206 // Check for remaining operations/errors. 207 if test.err != "" { 208 err := <-errc 209 stripped := strings.Replace(err.Error(), "\n", "", -1) 210 c.Check(stripped, gc.Matches, test.err) 211 return 212 } 213 if !c.Check(<-errc, gc.IsNil) { 214 return 215 } 216 217 if uploadCount > 0 { 218 for i := 0; i < uploadCount; i++ { 219 c.Check((<-opc).(dummy.OpPutFile).Env, gc.Equals, "peckham") 220 } 221 list, err := envtools.FindTools( 222 env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry) 223 c.Check(err, gc.IsNil) 224 c.Logf("found: " + list.String()) 225 urls := list.URLs() 226 c.Check(urls, gc.HasLen, len(test.uploads)) 227 for _, v := range test.uploads { 228 v := strings.Replace(v, "%LTS%", config.LatestLtsSeries(), 1) 229 c.Logf("seeking: " + v) 230 vers := version.MustParseBinary(v) 231 _, found := urls[vers] 232 c.Check(found, gc.Equals, true) 233 } 234 } 235 if len(test.uploads) > 0 { 236 indexFile := (<-opc).(dummy.OpPutFile) 237 c.Check(indexFile.FileName, gc.Equals, "tools/streams/v1/index.json") 238 productFile := (<-opc).(dummy.OpPutFile) 239 c.Check(productFile.FileName, gc.Equals, "tools/streams/v1/com.ubuntu.juju:released:tools.json") 240 } 241 opPutBootstrapVerifyFile := (<-opc).(dummy.OpPutFile) 242 c.Check(opPutBootstrapVerifyFile.Env, gc.Equals, "peckham") 243 c.Check(opPutBootstrapVerifyFile.FileName, gc.Equals, environs.VerificationFilename) 244 245 opPutBootstrapInitFile := (<-opc).(dummy.OpPutFile) 246 c.Check(opPutBootstrapInitFile.Env, gc.Equals, "peckham") 247 c.Check(opPutBootstrapInitFile.FileName, gc.Equals, "provider-state") 248 249 opBootstrap := (<-opc).(dummy.OpBootstrap) 250 c.Check(opBootstrap.Env, gc.Equals, "peckham") 251 c.Check(opBootstrap.Args.Constraints, gc.DeepEquals, test.constraints) 252 c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement) 253 254 store, err := configstore.Default() 255 c.Assert(err, gc.IsNil) 256 // Check a CA cert/key was generated by reloading the environment. 257 env, err = environs.NewFromName("peckham", store) 258 c.Assert(err, gc.IsNil) 259 _, hasCert := env.Config().CACert() 260 c.Check(hasCert, gc.Equals, true) 261 _, hasKey := env.Config().CAPrivateKey() 262 c.Check(hasKey, gc.Equals, true) 263 } 264 265 var bootstrapTests = []bootstrapTest{{ 266 info: "no args, no error, no uploads, no constraints", 267 }, { 268 info: "bad --constraints", 269 args: []string{"--constraints", "bad=wrong"}, 270 err: `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`, 271 }, { 272 info: "conflicting --constraints", 273 args: []string{"--constraints", "instance-type=foo mem=4G"}, 274 err: `ambiguous constraints: "instance-type" overlaps with "mem"`, 275 }, { 276 info: "bad --series", 277 args: []string{"--series", "1bad1"}, 278 err: `invalid value "1bad1" for flag --series: invalid series name "1bad1"`, 279 }, { 280 info: "lonely --series", 281 args: []string{"--series", "fine"}, 282 err: `--series requires --upload-tools`, 283 }, { 284 info: "lonely --upload-series", 285 args: []string{"--upload-series", "fine"}, 286 err: `--upload-series requires --upload-tools`, 287 }, { 288 info: "--upload-series with --series", 289 args: []string{"--upload-tools", "--upload-series", "foo", "--series", "bar"}, 290 err: `--upload-series and --series can't be used together`, 291 }, { 292 info: "bad environment", 293 version: "1.2.3-%LTS%-amd64", 294 args: []string{"-e", "brokenenv"}, 295 err: `dummy.Bootstrap is broken`, 296 }, { 297 info: "constraints", 298 args: []string{"--constraints", "mem=4G cpu-cores=4"}, 299 constraints: constraints.MustParse("mem=4G cpu-cores=4"), 300 }, { 301 info: "unsupported constraint passed through but no error", 302 args: []string{"--constraints", "mem=4G cpu-cores=4 cpu-power=10"}, 303 constraints: constraints.MustParse("mem=4G cpu-cores=4 cpu-power=10"), 304 }, { 305 info: "--upload-tools picks all reasonable series", 306 version: "1.2.3-saucy-amd64", 307 args: []string{"--upload-tools"}, 308 uploads: []string{ 309 "1.2.3.1-saucy-amd64", // from version.Current 310 "1.2.3.1-raring-amd64", // from env.Config().DefaultSeries() 311 "1.2.3.1-precise-amd64", 312 "1.2.3.1-trusty-amd64", 313 }, 314 }, { 315 info: "--upload-tools uses arch from constraint if it matches current version", 316 version: "1.3.3-saucy-ppc64", 317 hostArch: "ppc64", 318 args: []string{"--upload-tools", "--constraints", "arch=ppc64"}, 319 uploads: []string{ 320 "1.3.3.1-saucy-ppc64", // from version.Current 321 "1.3.3.1-raring-ppc64", // from env.Config().DefaultSeries() 322 "1.3.3.1-precise-ppc64", 323 "1.3.3.1-trusty-ppc64", 324 }, 325 constraints: constraints.MustParse("arch=ppc64"), 326 }, { 327 info: "--upload-tools only uploads each file once", 328 version: "1.2.3-%LTS%-amd64", 329 args: []string{"--upload-tools"}, 330 uploads: []string{ 331 "1.2.3.1-raring-amd64", 332 "1.2.3.1-precise-amd64", 333 "1.2.3.1-trusty-amd64", 334 }, 335 }, { 336 info: "--upload-tools rejects invalid series", 337 version: "1.2.3-saucy-amd64", 338 args: []string{"--upload-tools", "--upload-series", "ping,ping,pong"}, 339 err: `invalid series "ping"`, 340 }, { 341 info: "--upload-tools rejects mismatched arch", 342 version: "1.3.3-saucy-amd64", 343 hostArch: "amd64", 344 args: []string{"--upload-tools", "--constraints", "arch=ppc64"}, 345 err: `cannot build tools for "ppc64" using a machine running on "amd64"`, 346 }, { 347 info: "--upload-tools rejects non-supported arch", 348 version: "1.3.3-saucy-arm64", 349 hostArch: "arm64", 350 args: []string{"--upload-tools"}, 351 err: `environment "peckham" of type dummy does not support instances running on "arm64"`, 352 }, { 353 info: "--upload-tools always bumps build number", 354 version: "1.2.3.4-raring-amd64", 355 args: []string{"--upload-tools"}, 356 uploads: []string{ 357 "1.2.3.5-raring-amd64", 358 "1.2.3.5-precise-amd64", 359 "1.2.3.5-trusty-amd64", 360 }, 361 }, { 362 info: "placement", 363 args: []string{"--to", "something"}, 364 placement: "something", 365 }, { 366 info: "additional args", 367 args: []string{"anything", "else"}, 368 err: `unrecognized args: \["anything" "else"\]`, 369 }} 370 371 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 372 env := resetJujuHome(c) 373 defaultSeriesVersion := version.Current 374 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 375 // Force a dev version by having an odd minor version number. 376 // This is because we have not uploaded any tools and auto 377 // upload is only enabled for dev versions. 378 defaultSeriesVersion.Minor = 11 379 s.PatchValue(&version.Current, defaultSeriesVersion) 380 381 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 382 c.Assert(err, gc.IsNil) 383 384 _, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 385 c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") 386 } 387 388 func (s *BootstrapSuite) TestSeriesDeprecation(c *gc.C) { 389 ctx := s.checkSeriesArg(c, "--series") 390 c.Check(coretesting.Stderr(ctx), gc.Equals, 391 "Use of --series is deprecated. Please use --upload-series instead.\n") 392 } 393 394 func (s *BootstrapSuite) TestNoDeprecationWithUploadSeries(c *gc.C) { 395 ctx := s.checkSeriesArg(c, "--upload-series") 396 c.Check(coretesting.Stderr(ctx), gc.Equals, "") 397 } 398 399 func (s *BootstrapSuite) checkSeriesArg(c *gc.C, argVariant string) *cmd.Context { 400 _bootstrap := &fakeBootstrapFuncs{} 401 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 402 return _bootstrap 403 }) 404 resetJujuHome(c) 405 406 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--upload-tools", argVariant, "foo,bar") 407 408 c.Assert(err, gc.IsNil) 409 c.Check(_bootstrap.uploadToolsSeries, gc.DeepEquals, []string{"foo", "bar"}) 410 return ctx 411 } 412 413 func (s *BootstrapSuite) TestBootstrapJenvWarning(c *gc.C) { 414 env := resetJujuHome(c) 415 defaultSeriesVersion := version.Current 416 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 417 // Force a dev version by having an odd minor version number. 418 // This is because we have not uploaded any tools and auto 419 // upload is only enabled for dev versions. 420 defaultSeriesVersion.Minor = 11 421 s.PatchValue(&version.Current, defaultSeriesVersion) 422 423 store, err := configstore.Default() 424 c.Assert(err, gc.IsNil) 425 ctx := coretesting.Context(c) 426 environs.PrepareFromName("peckham", ctx, store) 427 428 logger := "jenv.warning.test" 429 testWriter := &loggo.TestWriter{} 430 loggo.RegisterWriter(logger, testWriter, loggo.WARNING) 431 defer loggo.RemoveWriter(logger) 432 433 _, errc := runCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham") 434 c.Assert(<-errc, gc.IsNil) 435 c.Assert(testWriter.Log, jc.LogMatches, []string{"ignoring environments.yaml: using bootstrap config in .*"}) 436 } 437 438 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 439 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 440 env := resetJujuHome(c) 441 442 // Bootstrap the environment with an invalid source. 443 // The command returns with an error. 444 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", c.MkDir()) 445 c.Check(err, gc.ErrorMatches, "cannot upload bootstrap tools: Juju "+ 446 "cannot bootstrap because no tools are available for your "+ 447 "environment(.|\n)*") 448 449 // Now check that there are no tools available. 450 _, err = envtools.FindTools( 451 env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry) 452 c.Assert(err, gc.FitsTypeOf, errors.NotFoundf("")) 453 } 454 455 // createImageMetadata creates some image metadata in a local directory. 456 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 457 // Generate some image metadata. 458 im := []*imagemetadata.ImageMetadata{ 459 { 460 Id: "1234", 461 Arch: "amd64", 462 Version: "13.04", 463 RegionName: "region", 464 Endpoint: "endpoint", 465 }, 466 } 467 cloudSpec := &simplestreams.CloudSpec{ 468 Region: "region", 469 Endpoint: "endpoint", 470 } 471 sourceDir := c.MkDir() 472 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 473 c.Assert(err, gc.IsNil) 474 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 475 c.Assert(err, gc.IsNil) 476 return sourceDir, im 477 } 478 479 // checkImageMetadata checks that the environment contains the expected image metadata. 480 func checkImageMetadata(c *gc.C, stor storage.StorageReader, expected []*imagemetadata.ImageMetadata) { 481 metadata := imtesting.ParseMetadataFromStorage(c, stor) 482 c.Assert(metadata, gc.HasLen, 1) 483 c.Assert(expected[0], gc.DeepEquals, metadata[0]) 484 } 485 486 func (s *BootstrapSuite) TestUploadLocalImageMetadata(c *gc.C) { 487 sourceDir, expected := createImageMetadata(c) 488 env := resetJujuHome(c) 489 490 // Bootstrap the environment with the valid source. 491 // Force a dev version by having an odd minor version number. 492 // This is because we have not uploaded any tools and auto 493 // upload is only enabled for dev versions. 494 devVersion := version.Current 495 devVersion.Minor = 11 496 s.PatchValue(&version.Current, devVersion) 497 498 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", sourceDir) 499 c.Assert(err, gc.IsNil) 500 c.Assert(imagemetadata.DefaultBaseURL, gc.Equals, imagemetadata.UbuntuCloudImagesURL) 501 502 // Now check the image metadata has been uploaded. 503 checkImageMetadata(c, env.Storage(), expected) 504 } 505 506 func (s *BootstrapSuite) TestValidateConstraintsCalledWithMetadatasource(c *gc.C) { 507 sourceDir, _ := createImageMetadata(c) 508 resetJujuHome(c) 509 var calledFuncs []string 510 s.PatchValue(&uploadCustomMetadata, func(metadataDir string, env environs.Environ) error { 511 c.Assert(metadataDir, gc.DeepEquals, sourceDir) 512 calledFuncs = append(calledFuncs, "uploadCustomMetadata") 513 return nil 514 }) 515 s.PatchValue(&validateConstraints, func(cons constraints.Value, env environs.Environ) error { 516 c.Assert(cons, gc.DeepEquals, constraints.MustParse("mem=4G")) 517 calledFuncs = append(calledFuncs, "validateConstraints") 518 return nil 519 }) 520 _, err := coretesting.RunCommand( 521 c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", sourceDir, "--constraints", "mem=4G") 522 c.Assert(err, gc.IsNil) 523 c.Assert(calledFuncs, gc.DeepEquals, []string{"uploadCustomMetadata", "validateConstraints"}) 524 } 525 526 func (s *BootstrapSuite) TestValidateConstraintsCalledWithoutMetadatasource(c *gc.C) { 527 validateCalled := 0 528 s.PatchValue(&validateConstraints, func(cons constraints.Value, env environs.Environ) error { 529 c.Assert(cons, gc.DeepEquals, constraints.MustParse("mem=4G")) 530 validateCalled++ 531 return nil 532 }) 533 resetJujuHome(c) 534 _, err := coretesting.RunCommand( 535 c, envcmd.Wrap(&BootstrapCommand{}), "--constraints", "mem=4G") 536 c.Assert(err, gc.IsNil) 537 c.Assert(validateCalled, gc.Equals, 1) 538 } 539 540 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 541 sourceDir := createToolsSource(c, vAll) 542 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 543 env := resetJujuHome(c) 544 545 // Bootstrap the environment with the valid source. 546 // The bootstrapping has to show no error, because the tools 547 // are automatically synchronized. 548 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", sourceDir) 549 c.Assert(err, gc.IsNil) 550 551 // Now check the available tools which are the 1.2.0 envtools. 552 checkTools(c, env, v120All) 553 } 554 555 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, series string) environs.Environ { 556 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 557 sourceDir := createToolsSource(c, vAll) 558 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 559 560 // Change the tools location to be the test location and also 561 // the version and ensure their later restoring. 562 // Set the current version to be something for which there are no tools 563 // so we can test that an upload is forced. 564 origVersion := version.Current 565 version.Current.Number = version.MustParse(vers) 566 version.Current.Series = series 567 s.AddCleanup(func(*gc.C) { version.Current = origVersion }) 568 569 // Create home with dummy provider and remove all 570 // of its envtools. 571 return resetJujuHome(c) 572 } 573 574 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 575 s.PatchValue(&version.Current.Series, config.LatestLtsSeries()) 576 otherSeries := "quantal" 577 578 env := s.setupAutoUploadTest(c, "1.7.3", otherSeries) 579 // Run command and check for that upload has been run for tools matching the current juju version. 580 opc, errc := runCommand(nullContext(c), envcmd.Wrap(new(BootstrapCommand))) 581 c.Assert(<-errc, gc.IsNil) 582 c.Assert((<-opc).(dummy.OpPutFile).Env, gc.Equals, "peckham") 583 list, err := envtools.FindTools(env, version.Current.Major, version.Current.Minor, coretools.Filter{}, false) 584 c.Assert(err, gc.IsNil) 585 c.Logf("found: " + list.String()) 586 urls := list.URLs() 587 588 // We expect: 589 // supported LTS series precise, trusty, 590 // the specified series (quantal), 591 // and the environment's default series (raring). 592 expectedVers := []version.Binary{ 593 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", "quantal", version.Current.Arch)), 594 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", "raring", version.Current.Arch)), 595 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", "precise", version.Current.Arch)), 596 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", "trusty", version.Current.Arch)), 597 } 598 c.Assert(urls, gc.HasLen, len(expectedVers)) 599 for _, vers := range expectedVers { 600 c.Logf("seeking: " + vers.String()) 601 _, found := urls[vers] 602 c.Check(found, gc.Equals, true) 603 } 604 } 605 606 func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) { 607 s.setupAutoUploadTest(c, "1.8.3", "precise") 608 _, errc := runCommand(nullContext(c), envcmd.Wrap(new(BootstrapCommand))) 609 err := <-errc 610 stripped := strings.Replace(err.Error(), "\n", "", -1) 611 c.Assert(stripped, gc.Matches, noToolsAvailableMessage) 612 } 613 614 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 615 s.setupAutoUploadTest(c, "1.8.3", "precise") 616 617 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 618 c.Assert(err, gc.ErrorMatches, "cannot upload bootstrap tools: Juju "+ 619 "cannot bootstrap because no tools are available for your "+ 620 "environment(.|\n)*") 621 } 622 623 func uploadToolsAlwaysFails(stor storage.Storage, forceVersion *version.Number, series ...string) (*coretools.Tools, error) { 624 return nil, fmt.Errorf("an error") 625 } 626 627 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 628 s.setupAutoUploadTest(c, "1.7.3", "precise") 629 s.PatchValue(&sync.Upload, uploadToolsAlwaysFails) 630 631 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 632 633 c.Check(coretesting.Stderr(ctx), gc.Matches, 634 "uploading tools for series \\[precise raring .*\\]\n") 635 c.Check(err, gc.ErrorMatches, "cannot upload bootstrap tools: an error") 636 } 637 638 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 639 resetJujuHome(c) 640 devVersion := version.Current 641 // Force a dev version by having an odd minor version number. 642 // This is because we have not uploaded any tools and auto 643 // upload is only enabled for dev versions. 644 devVersion.Minor = 11 645 s.PatchValue(&version.Current, devVersion) 646 opc, errc := runCommand(nullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv") 647 err := <-errc 648 c.Assert(err, gc.ErrorMatches, "dummy.Bootstrap is broken") 649 var opDestroy *dummy.OpDestroy 650 for opDestroy == nil { 651 select { 652 case op := <-opc: 653 switch op := op.(type) { 654 case dummy.OpDestroy: 655 opDestroy = &op 656 } 657 default: 658 c.Error("expected call to env.Destroy") 659 return 660 } 661 } 662 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 663 } 664 665 // createToolsSource writes the mock tools and metadata into a temporary 666 // directory and returns it. 667 func createToolsSource(c *gc.C, versions []version.Binary) string { 668 versionStrings := make([]string, len(versions)) 669 for i, vers := range versions { 670 versionStrings[i] = vers.String() 671 } 672 source := c.MkDir() 673 toolstesting.MakeTools(c, source, "releases", versionStrings) 674 return source 675 } 676 677 // resetJujuHome restores an new, clean Juju home environment without tools. 678 func resetJujuHome(c *gc.C) environs.Environ { 679 jenvDir := gitjujutesting.HomePath(".juju", "environments") 680 err := os.RemoveAll(jenvDir) 681 c.Assert(err, gc.IsNil) 682 coretesting.WriteEnvironments(c, envConfig) 683 dummy.Reset() 684 store, err := configstore.Default() 685 c.Assert(err, gc.IsNil) 686 env, err := environs.PrepareFromName("peckham", nullContext(c), store) 687 c.Assert(err, gc.IsNil) 688 envtesting.RemoveAllTools(c, env) 689 return env 690 } 691 692 // checkTools check if the environment contains the passed envtools. 693 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 694 list, err := envtools.FindTools( 695 env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry) 696 c.Check(err, gc.IsNil) 697 c.Logf("found: " + list.String()) 698 urls := list.URLs() 699 c.Check(urls, gc.HasLen, len(expected)) 700 } 701 702 var ( 703 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 704 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 705 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 706 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 707 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 708 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 709 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 710 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 711 v120t32 = version.MustParseBinary("1.2.0-trusty-i386") 712 v120t64 = version.MustParseBinary("1.2.0-trusty-amd64") 713 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 714 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 715 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 716 v100All = []version.Binary{ 717 v100d64, v100p64, v100q64, v100q32, 718 } 719 v120All = []version.Binary{ 720 v120d64, v120p64, v120q64, v120q32, v120t32, v120t64, 721 } 722 v190All = []version.Binary{ 723 v190p32, v190q64, 724 } 725 v200All = []version.Binary{ 726 v200p64, 727 } 728 vAll = joinBinaryVersions(v100All, v120All, v190All, v200All) 729 ) 730 731 func joinBinaryVersions(versions ...[]version.Binary) []version.Binary { 732 var all []version.Binary 733 for _, versions := range versions { 734 all = append(all, versions...) 735 } 736 return all 737 } 738 739 // TODO(menn0): This fake BootstrapInterface implementation is 740 // currently quite minimal but could be easily extended to cover more 741 // test scenarios. This could help improve some of the tests in this 742 // file which execute large amounts of external functionality. 743 type fakeBootstrapFuncs struct { 744 uploadToolsSeries []string 745 } 746 747 func (fake *fakeBootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error { 748 return nil 749 } 750 751 func (fake *fakeBootstrapFuncs) UploadTools(ctx environs.BootstrapContext, env environs.Environ, toolsArch *string, forceVersion bool, bootstrapSeries ...string) error { 752 fake.uploadToolsSeries = bootstrapSeries 753 return nil 754 } 755 756 func (fake fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams) error { 757 return nil 758 }