github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 gitjujutesting "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/cmd/envcmd" 19 cmdtesting "github.com/juju/juju/cmd/testing" 20 "github.com/juju/juju/constraints" 21 "github.com/juju/juju/environs" 22 "github.com/juju/juju/environs/bootstrap" 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/imagemetadata" 27 "github.com/juju/juju/environs/simplestreams" 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/instance" 33 "github.com/juju/juju/juju" 34 "github.com/juju/juju/juju/arch" 35 "github.com/juju/juju/network" 36 "github.com/juju/juju/provider/dummy" 37 coretesting "github.com/juju/juju/testing" 38 coretools "github.com/juju/juju/tools" 39 "github.com/juju/juju/version" 40 ) 41 42 type BootstrapSuite struct { 43 coretesting.FakeJujuHomeSuite 44 gitjujutesting.MgoSuite 45 envtesting.ToolsFixture 46 } 47 48 var _ = gc.Suite(&BootstrapSuite{}) 49 50 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 51 s.FakeJujuHomeSuite.SetUpSuite(c) 52 s.MgoSuite.SetUpSuite(c) 53 } 54 55 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 56 s.FakeJujuHomeSuite.SetUpTest(c) 57 s.MgoSuite.SetUpTest(c) 58 s.ToolsFixture.SetUpTest(c) 59 60 // Set version.Current to a known value, for which we 61 // will make tools available. Individual tests may 62 // override this. 63 s.PatchValue(&version.Current, v100p64) 64 65 // Set up a local source with tools. 66 sourceDir := createToolsSource(c, vAll) 67 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 68 69 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 70 } 71 72 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 73 s.MgoSuite.TearDownSuite(c) 74 s.FakeJujuHomeSuite.TearDownSuite(c) 75 } 76 77 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 78 s.ToolsFixture.TearDownTest(c) 79 s.MgoSuite.TearDownTest(c) 80 s.FakeJujuHomeSuite.TearDownTest(c) 81 dummy.Reset() 82 } 83 84 func (s *BootstrapSuite) TestRunTests(c *gc.C) { 85 for i, test := range bootstrapTests { 86 c.Logf("\ntest %d: %s", i, test.info) 87 restore := s.run(c, test) 88 restore() 89 } 90 } 91 92 type bootstrapTest struct { 93 info string 94 // binary version string used to set version.Current 95 version string 96 sync bool 97 args []string 98 err string 99 // binary version string for expected tools; if set, no default tools 100 // will be uploaded before running the test. 101 upload string 102 constraints constraints.Value 103 placement string 104 hostArch string 105 keepBroken bool 106 } 107 108 func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) (restore gitjujutesting.Restorer) { 109 // Create home with dummy provider and remove all 110 // of its envtools. 111 env := resetJujuHome(c, "peckham") 112 113 // Although we're testing PrepareEndpointsForCaching interactions 114 // separately in the juju package, here we just ensure it gets 115 // called with the right arguments. 116 prepareCalled := false 117 addrConnectedTo := "localhost:17070" 118 restore = gitjujutesting.PatchValue( 119 &prepareEndpointsForCaching, 120 func(info configstore.EnvironInfo, hps [][]network.HostPort, addr network.HostPort) (_, _ []string, _ bool) { 121 prepareCalled = true 122 addrs, hosts, changed := juju.PrepareEndpointsForCaching(info, hps, addr) 123 // Because we're bootstrapping the addresses will always 124 // change, as there's no .jenv file saved yet. 125 c.Assert(changed, jc.IsTrue) 126 return addrs, hosts, changed 127 }, 128 ) 129 130 if test.version != "" { 131 useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1) 132 origVersion := version.Current 133 version.Current = version.MustParseBinary(useVersion) 134 restore = restore.Add(func() { 135 version.Current = origVersion 136 }) 137 } 138 139 if test.hostArch != "" { 140 origArch := arch.HostArch 141 arch.HostArch = func() string { 142 return test.hostArch 143 } 144 restore = restore.Add(func() { 145 arch.HostArch = origArch 146 }) 147 } 148 149 // Run command and check for uploads. 150 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), test.args...) 151 // Check for remaining operations/errors. 152 if test.err != "" { 153 err := <-errc 154 stripped := strings.Replace(err.Error(), "\n", "", -1) 155 c.Check(stripped, gc.Matches, test.err) 156 return restore 157 } 158 if !c.Check(<-errc, gc.IsNil) { 159 return restore 160 } 161 162 opBootstrap := (<-opc).(dummy.OpBootstrap) 163 c.Check(opBootstrap.Env, gc.Equals, "peckham") 164 c.Check(opBootstrap.Args.Constraints, gc.DeepEquals, test.constraints) 165 c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement) 166 167 opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap) 168 c.Check(opFinalizeBootstrap.Env, gc.Equals, "peckham") 169 c.Check(opFinalizeBootstrap.MachineConfig.Tools, gc.NotNil) 170 if test.upload != "" { 171 c.Check(opFinalizeBootstrap.MachineConfig.Tools.Version.String(), gc.Equals, test.upload) 172 } 173 174 store, err := configstore.Default() 175 c.Assert(err, jc.ErrorIsNil) 176 // Check a CA cert/key was generated by reloading the environment. 177 env, err = environs.NewFromName("peckham", store) 178 c.Assert(err, jc.ErrorIsNil) 179 _, hasCert := env.Config().CACert() 180 c.Check(hasCert, jc.IsTrue) 181 _, hasKey := env.Config().CAPrivateKey() 182 c.Check(hasKey, jc.IsTrue) 183 info, err := store.ReadInfo("peckham") 184 c.Assert(err, jc.ErrorIsNil) 185 c.Assert(info, gc.NotNil) 186 c.Assert(prepareCalled, jc.IsTrue) 187 c.Assert(info.APIEndpoint().Addresses, gc.DeepEquals, []string{addrConnectedTo}) 188 return restore 189 } 190 191 var bootstrapTests = []bootstrapTest{{ 192 info: "no args, no error, no upload, no constraints", 193 }, { 194 info: "bad --constraints", 195 args: []string{"--constraints", "bad=wrong"}, 196 err: `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`, 197 }, { 198 info: "conflicting --constraints", 199 args: []string{"--constraints", "instance-type=foo mem=4G"}, 200 err: `failed to bootstrap environment: ambiguous constraints: "instance-type" overlaps with "mem"`, 201 }, { 202 info: "bad --series", 203 args: []string{"--series", "1bad1"}, 204 err: `invalid value "1bad1" for flag --series: invalid series name "1bad1"`, 205 }, { 206 info: "lonely --series", 207 args: []string{"--series", "fine"}, 208 err: `--series requires --upload-tools`, 209 }, { 210 info: "lonely --upload-series", 211 args: []string{"--upload-series", "fine"}, 212 err: `--upload-series requires --upload-tools`, 213 }, { 214 info: "--upload-series with --series", 215 args: []string{"--upload-tools", "--upload-series", "foo", "--series", "bar"}, 216 err: `--upload-series and --series can't be used together`, 217 }, { 218 info: "bad environment", 219 version: "1.2.3-%LTS%-amd64", 220 args: []string{"-e", "brokenenv"}, 221 err: `failed to bootstrap environment: dummy.Bootstrap is broken`, 222 }, { 223 info: "constraints", 224 args: []string{"--constraints", "mem=4G cpu-cores=4"}, 225 constraints: constraints.MustParse("mem=4G cpu-cores=4"), 226 }, { 227 info: "unsupported constraint passed through but no error", 228 args: []string{"--constraints", "mem=4G cpu-cores=4 cpu-power=10"}, 229 constraints: constraints.MustParse("mem=4G cpu-cores=4 cpu-power=10"), 230 }, { 231 info: "--upload-tools uses arch from constraint if it matches current version", 232 version: "1.3.3-saucy-ppc64el", 233 hostArch: "ppc64el", 234 args: []string{"--upload-tools", "--constraints", "arch=ppc64el"}, 235 upload: "1.3.3.1-raring-ppc64el", // from version.Current 236 constraints: constraints.MustParse("arch=ppc64el"), 237 }, { 238 info: "--upload-tools rejects mismatched arch", 239 version: "1.3.3-saucy-amd64", 240 hostArch: "amd64", 241 args: []string{"--upload-tools", "--constraints", "arch=ppc64el"}, 242 err: `failed to bootstrap environment: cannot build tools for "ppc64el" using a machine running on "amd64"`, 243 }, { 244 info: "--upload-tools rejects non-supported arch", 245 version: "1.3.3-saucy-arm64", 246 hostArch: "arm64", 247 args: []string{"--upload-tools"}, 248 err: `failed to bootstrap environment: environment "peckham" of type dummy does not support instances running on "arm64"`, 249 }, { 250 info: "--upload-tools always bumps build number", 251 version: "1.2.3.4-raring-amd64", 252 args: []string{"--upload-tools"}, 253 upload: "1.2.3.5-raring-amd64", 254 }, { 255 info: "placement", 256 args: []string{"--to", "something"}, 257 placement: "something", 258 }, { 259 info: "keep broken", 260 args: []string{"--keep-broken"}, 261 keepBroken: true, 262 }, { 263 info: "additional args", 264 args: []string{"anything", "else"}, 265 err: `unrecognized args: \["anything" "else"\]`, 266 }} 267 268 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 269 env := resetJujuHome(c, "devenv") 270 defaultSeriesVersion := version.Current 271 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 272 // Force a dev version by having a non zero build number. 273 // This is because we have not uploaded any tools and auto 274 // upload is only enabled for dev versions. 275 defaultSeriesVersion.Build = 1234 276 s.PatchValue(&version.Current, defaultSeriesVersion) 277 278 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 279 c.Assert(err, jc.ErrorIsNil) 280 281 _, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 282 c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") 283 } 284 285 type mockBootstrapInstance struct { 286 instance.Instance 287 } 288 289 func (*mockBootstrapInstance) Addresses() ([]network.Address, error) { 290 return []network.Address{{Value: "localhost"}}, nil 291 } 292 293 func (s *BootstrapSuite) TestSeriesDeprecation(c *gc.C) { 294 ctx := s.checkSeriesArg(c, "--series") 295 c.Check(coretesting.Stderr(ctx), gc.Equals, 296 "Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.\n") 297 } 298 299 func (s *BootstrapSuite) TestUploadSeriesDeprecation(c *gc.C) { 300 ctx := s.checkSeriesArg(c, "--upload-series") 301 c.Check(coretesting.Stderr(ctx), gc.Equals, 302 "Use of --upload-series is obsolete. --upload-tools now expands to all supported series of the same operating system.\n") 303 } 304 305 func (s *BootstrapSuite) checkSeriesArg(c *gc.C, argVariant string) *cmd.Context { 306 _bootstrap := &fakeBootstrapFuncs{} 307 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 308 return _bootstrap 309 }) 310 resetJujuHome(c, "devenv") 311 s.PatchValue(&allInstances, func(environ environs.Environ) ([]instance.Instance, error) { 312 return []instance.Instance{&mockBootstrapInstance{}}, nil 313 }) 314 315 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--upload-tools", argVariant, "foo,bar") 316 317 c.Assert(err, jc.ErrorIsNil) 318 return ctx 319 } 320 321 // In the case where we cannot examine an environment, we want the 322 // error to propagate back up to the user. 323 func (s *BootstrapSuite) TestBootstrapPropagatesEnvErrors(c *gc.C) { 324 325 const envName = "devenv" 326 env := resetJujuHome(c, envName) 327 defaultSeriesVersion := version.Current 328 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 329 // Force a dev version by having a non zero build number. 330 // This is because we have not uploaded any tools and auto 331 // upload is only enabled for dev versions. 332 defaultSeriesVersion.Build = 1234 333 s.PatchValue(&version.Current, defaultSeriesVersion) 334 335 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", envName) 336 c.Assert(err, jc.ErrorIsNil) 337 338 // Change permissions on the jenv file to simulate some kind of 339 // unexpected error when trying to read info from the environment 340 jenvFile := gitjujutesting.HomePath(".juju", "environments", envName+".jenv") 341 err = os.Chmod(jenvFile, os.FileMode(0200)) 342 c.Assert(err, jc.ErrorIsNil) 343 344 // The second bootstrap should fail b/c of the propogated error 345 _, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", envName) 346 c.Assert(err, gc.ErrorMatches, "there was an issue examining the environment: .*") 347 } 348 349 func (s *BootstrapSuite) TestBootstrapCleansUpIfEnvironPrepFails(c *gc.C) { 350 351 cleanupRan := false 352 353 s.PatchValue( 354 &environFromName, 355 func( 356 *cmd.Context, 357 string, 358 string, 359 func(environs.Environ) error, 360 ) (environs.Environ, func(), error) { 361 return nil, func() { cleanupRan = true }, fmt.Errorf("mock") 362 }, 363 ) 364 365 ctx := coretesting.Context(c) 366 _, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham") 367 c.Check(<-errc, gc.Not(gc.IsNil)) 368 c.Check(cleanupRan, jc.IsTrue) 369 } 370 371 // When attempting to bootstrap, check that when prepare errors out, 372 // the code cleans up the created jenv file, but *not* any existing 373 // environment that may have previously been bootstrapped. 374 func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) { 375 376 destroyedEnvRan := false 377 destroyedInfoRan := false 378 379 // Mock functions 380 mockDestroyPreparedEnviron := func( 381 *cmd.Context, 382 environs.Environ, 383 configstore.Storage, 384 string, 385 ) { 386 destroyedEnvRan = true 387 } 388 389 mockDestroyEnvInfo := func( 390 ctx *cmd.Context, 391 cfgName string, 392 store configstore.Storage, 393 action string, 394 ) { 395 destroyedInfoRan = true 396 } 397 398 mockEnvironFromName := func( 399 ctx *cmd.Context, 400 envName string, 401 action string, 402 _ func(environs.Environ) error, 403 ) (environs.Environ, func(), error) { 404 // Always show that the environment is bootstrapped. 405 return environFromNameProductionFunc( 406 ctx, 407 envName, 408 action, 409 func(env environs.Environ) error { 410 return environs.ErrAlreadyBootstrapped 411 }) 412 } 413 414 mockPrepare := func( 415 string, 416 environs.BootstrapContext, 417 configstore.Storage, 418 ) (environs.Environ, error) { 419 return nil, fmt.Errorf("mock-prepare") 420 } 421 422 // Simulation: prepare should fail and we should only clean up the 423 // jenv file. Any existing environment should not be destroyed. 424 s.PatchValue(&destroyPreparedEnviron, mockDestroyPreparedEnviron) 425 s.PatchValue(&environFromName, mockEnvironFromName) 426 s.PatchValue(&environs.PrepareFromName, mockPrepare) 427 s.PatchValue(&destroyEnvInfo, mockDestroyEnvInfo) 428 429 ctx := coretesting.Context(c) 430 _, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham") 431 c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$") 432 c.Check(destroyedEnvRan, jc.IsFalse) 433 c.Check(destroyedInfoRan, jc.IsTrue) 434 } 435 436 func (s *BootstrapSuite) TestBootstrapJenvWarning(c *gc.C) { 437 env := resetJujuHome(c, "devenv") 438 defaultSeriesVersion := version.Current 439 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 440 // Force a dev version by having a non zero build number. 441 // This is because we have not uploaded any tools and auto 442 // upload is only enabled for dev versions. 443 defaultSeriesVersion.Build = 1234 444 s.PatchValue(&version.Current, defaultSeriesVersion) 445 446 store, err := configstore.Default() 447 c.Assert(err, jc.ErrorIsNil) 448 ctx := coretesting.Context(c) 449 environs.PrepareFromName("devenv", envcmd.BootstrapContext(ctx), store) 450 451 logger := "jenv.warning.test" 452 var testWriter loggo.TestWriter 453 loggo.RegisterWriter(logger, &testWriter, loggo.WARNING) 454 defer loggo.RemoveWriter(logger) 455 456 _, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "devenv") 457 c.Assert(<-errc, gc.IsNil) 458 c.Assert(testWriter.Log(), jc.LogMatches, []string{"ignoring environments.yaml: using bootstrap config in .*"}) 459 } 460 461 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 462 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 463 env := resetJujuHome(c, "devenv") 464 465 // Bootstrap the environment with an invalid source. 466 // The command returns with an error. 467 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", c.MkDir()) 468 c.Check(err, gc.ErrorMatches, `failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*`) 469 470 // Now check that there are no tools available. 471 _, err = envtools.FindTools( 472 env, version.Current.Major, version.Current.Minor, coretools.Filter{}) 473 c.Assert(err, gc.FitsTypeOf, errors.NotFoundf("")) 474 } 475 476 // createImageMetadata creates some image metadata in a local directory. 477 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 478 // Generate some image metadata. 479 im := []*imagemetadata.ImageMetadata{ 480 { 481 Id: "1234", 482 Arch: "amd64", 483 Version: "13.04", 484 RegionName: "region", 485 Endpoint: "endpoint", 486 }, 487 } 488 cloudSpec := &simplestreams.CloudSpec{ 489 Region: "region", 490 Endpoint: "endpoint", 491 } 492 sourceDir := c.MkDir() 493 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 494 c.Assert(err, jc.ErrorIsNil) 495 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 496 c.Assert(err, jc.ErrorIsNil) 497 return sourceDir, im 498 } 499 500 func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) { 501 sourceDir, _ := createImageMetadata(c) 502 resetJujuHome(c, "devenv") 503 504 _bootstrap := &fakeBootstrapFuncs{} 505 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 506 return _bootstrap 507 }) 508 509 coretesting.RunCommand( 510 c, envcmd.Wrap(&BootstrapCommand{}), 511 "--metadata-source", sourceDir, "--constraints", "mem=4G", 512 ) 513 c.Assert(_bootstrap.args.MetadataDir, gc.Equals, sourceDir) 514 } 515 516 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 517 sourceDir := createToolsSource(c, vAll) 518 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 519 env := resetJujuHome(c, "peckham") 520 521 // Bootstrap the environment with the valid source. 522 // The bootstrapping has to show no error, because the tools 523 // are automatically synchronized. 524 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", sourceDir) 525 c.Assert(err, jc.ErrorIsNil) 526 527 // Now check the available tools which are the 1.2.0 envtools. 528 checkTools(c, env, v120All) 529 } 530 531 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, series string) environs.Environ { 532 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 533 sourceDir := createToolsSource(c, vAll) 534 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 535 536 // Change the tools location to be the test location and also 537 // the version and ensure their later restoring. 538 // Set the current version to be something for which there are no tools 539 // so we can test that an upload is forced. 540 s.PatchValue(&version.Current, version.MustParseBinary(vers+"-"+series+"-"+version.Current.Arch)) 541 542 // Create home with dummy provider and remove all 543 // of its envtools. 544 return resetJujuHome(c, "devenv") 545 } 546 547 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 548 s.PatchValue(&version.Current.Series, config.LatestLtsSeries()) 549 s.setupAutoUploadTest(c, "1.7.3", "quantal") 550 // Run command and check for that upload has been run for tools matching 551 // the current juju version. 552 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "devenv") 553 c.Assert(<-errc, gc.IsNil) 554 c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, "devenv") 555 mcfg := (<-opc).(dummy.OpFinalizeBootstrap).MachineConfig 556 c.Assert(mcfg, gc.NotNil) 557 c.Assert(mcfg.Tools.Version.String(), gc.Equals, "1.7.3.1-raring-"+version.Current.Arch) 558 } 559 560 func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) { 561 s.setupAutoUploadTest(c, "1.8.3", "precise") 562 _, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand))) 563 err := <-errc 564 c.Assert(err, gc.ErrorMatches, 565 "failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*") 566 } 567 568 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 569 s.setupAutoUploadTest(c, "1.8.3", "precise") 570 571 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 572 c.Assert(err, gc.ErrorMatches, 573 "failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*") 574 } 575 576 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 577 578 buildToolsTarballAlwaysFails := func(forceVersion *version.Number, stream string) (*sync.BuiltTools, error) { 579 return nil, fmt.Errorf("an error") 580 } 581 582 s.setupAutoUploadTest(c, "1.7.3", "precise") 583 s.PatchValue(&sync.BuildToolsTarball, buildToolsTarballAlwaysFails) 584 585 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 586 587 c.Check(coretesting.Stderr(ctx), gc.Equals, fmt.Sprintf(` 588 Bootstrapping environment "devenv" 589 Starting new instance for initial state server 590 Building tools to upload (1.7.3.1-raring-%s) 591 `[1:], version.Current.Arch)) 592 c.Check(err, gc.ErrorMatches, "failed to bootstrap environment: cannot upload bootstrap tools: an error") 593 } 594 595 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 596 resetJujuHome(c, "devenv") 597 devVersion := version.Current 598 // Force a dev version by having a non zero build number. 599 // This is because we have not uploaded any tools and auto 600 // upload is only enabled for dev versions. 601 devVersion.Build = 1234 602 s.PatchValue(&version.Current, devVersion) 603 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv") 604 err := <-errc 605 c.Assert(err, gc.ErrorMatches, "failed to bootstrap environment: dummy.Bootstrap is broken") 606 var opDestroy *dummy.OpDestroy 607 for opDestroy == nil { 608 select { 609 case op := <-opc: 610 switch op := op.(type) { 611 case dummy.OpDestroy: 612 opDestroy = &op 613 } 614 default: 615 c.Error("expected call to env.Destroy") 616 return 617 } 618 } 619 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 620 } 621 622 func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) { 623 resetJujuHome(c, "devenv") 624 devVersion := version.Current 625 // Force a dev version by having a non zero build number. 626 // This is because we have not uploaded any tools and auto 627 // upload is only enabled for dev versions. 628 devVersion.Build = 1234 629 s.PatchValue(&version.Current, devVersion) 630 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv", "--keep-broken") 631 err := <-errc 632 c.Assert(err, gc.ErrorMatches, "failed to bootstrap environment: dummy.Bootstrap is broken") 633 done := false 634 for !done { 635 select { 636 case op, ok := <-opc: 637 if !ok { 638 done = true 639 break 640 } 641 switch op.(type) { 642 case dummy.OpDestroy: 643 c.Error("unexpected call to env.Destroy") 644 break 645 } 646 default: 647 break 648 } 649 } 650 } 651 652 // createToolsSource writes the mock tools and metadata into a temporary 653 // directory and returns it. 654 func createToolsSource(c *gc.C, versions []version.Binary) string { 655 versionStrings := make([]string, len(versions)) 656 for i, vers := range versions { 657 versionStrings[i] = vers.String() 658 } 659 source := c.MkDir() 660 toolstesting.MakeTools(c, source, "released", versionStrings) 661 return source 662 } 663 664 // resetJujuHome restores an new, clean Juju home environment without tools. 665 func resetJujuHome(c *gc.C, envName string) environs.Environ { 666 jenvDir := gitjujutesting.HomePath(".juju", "environments") 667 err := os.RemoveAll(jenvDir) 668 c.Assert(err, jc.ErrorIsNil) 669 coretesting.WriteEnvironments(c, envConfig) 670 dummy.Reset() 671 store, err := configstore.Default() 672 c.Assert(err, jc.ErrorIsNil) 673 env, err := environs.PrepareFromName(envName, envcmd.BootstrapContext(cmdtesting.NullContext(c)), store) 674 c.Assert(err, jc.ErrorIsNil) 675 return env 676 } 677 678 // checkTools check if the environment contains the passed envtools. 679 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 680 list, err := envtools.FindTools( 681 env, version.Current.Major, version.Current.Minor, coretools.Filter{}) 682 c.Check(err, jc.ErrorIsNil) 683 c.Logf("found: " + list.String()) 684 urls := list.URLs() 685 c.Check(urls, gc.HasLen, len(expected)) 686 } 687 688 var ( 689 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 690 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 691 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 692 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 693 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 694 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 695 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 696 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 697 v120t32 = version.MustParseBinary("1.2.0-trusty-i386") 698 v120t64 = version.MustParseBinary("1.2.0-trusty-amd64") 699 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 700 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 701 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 702 v100All = []version.Binary{ 703 v100d64, v100p64, v100q64, v100q32, 704 } 705 v120All = []version.Binary{ 706 v120d64, v120p64, v120q64, v120q32, v120t32, v120t64, 707 } 708 v190All = []version.Binary{ 709 v190p32, v190q64, 710 } 711 v200All = []version.Binary{ 712 v200p64, 713 } 714 vAll = joinBinaryVersions(v100All, v120All, v190All, v200All) 715 ) 716 717 func joinBinaryVersions(versions ...[]version.Binary) []version.Binary { 718 var all []version.Binary 719 for _, versions := range versions { 720 all = append(all, versions...) 721 } 722 return all 723 } 724 725 // TODO(menn0): This fake BootstrapInterface implementation is 726 // currently quite minimal but could be easily extended to cover more 727 // test scenarios. This could help improve some of the tests in this 728 // file which execute large amounts of external functionality. 729 type fakeBootstrapFuncs struct { 730 args bootstrap.BootstrapParams 731 } 732 733 func (fake *fakeBootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error { 734 return nil 735 } 736 737 func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error { 738 fake.args = args 739 return nil 740 }