github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cmd/juju/commands/bootstrap_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "fmt" 8 "os" 9 "runtime" 10 "strings" 11 "time" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 gitjujutesting "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/apiserver/params" 21 "github.com/juju/juju/cmd/envcmd" 22 "github.com/juju/juju/cmd/juju/block" 23 cmdtesting "github.com/juju/juju/cmd/testing" 24 "github.com/juju/juju/constraints" 25 "github.com/juju/juju/environs" 26 "github.com/juju/juju/environs/bootstrap" 27 "github.com/juju/juju/environs/config" 28 "github.com/juju/juju/environs/configstore" 29 "github.com/juju/juju/environs/filestorage" 30 "github.com/juju/juju/environs/imagemetadata" 31 "github.com/juju/juju/environs/simplestreams" 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 "github.com/juju/juju/instance" 37 "github.com/juju/juju/juju" 38 "github.com/juju/juju/juju/arch" 39 "github.com/juju/juju/juju/osenv" 40 "github.com/juju/juju/network" 41 "github.com/juju/juju/provider/dummy" 42 coretesting "github.com/juju/juju/testing" 43 coretools "github.com/juju/juju/tools" 44 "github.com/juju/juju/version" 45 ) 46 47 type BootstrapSuite struct { 48 coretesting.FakeJujuHomeSuite 49 gitjujutesting.MgoSuite 50 envtesting.ToolsFixture 51 mockBlockClient *mockBlockClient 52 } 53 54 var _ = gc.Suite(&BootstrapSuite{}) 55 56 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 57 s.FakeJujuHomeSuite.SetUpSuite(c) 58 s.MgoSuite.SetUpSuite(c) 59 } 60 61 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 62 s.FakeJujuHomeSuite.SetUpTest(c) 63 s.MgoSuite.SetUpTest(c) 64 s.ToolsFixture.SetUpTest(c) 65 66 // Set version.Current to a known value, for which we 67 // will make tools available. Individual tests may 68 // override this. 69 s.PatchValue(&version.Current, v100p64) 70 71 // Set up a local source with tools. 72 sourceDir := createToolsSource(c, vAll) 73 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 74 75 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 76 77 s.mockBlockClient = &mockBlockClient{} 78 s.PatchValue(&blockAPI, func(c *envcmd.EnvCommandBase) (block.BlockListAPI, error) { 79 return s.mockBlockClient, nil 80 }) 81 } 82 83 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 84 s.MgoSuite.TearDownSuite(c) 85 s.FakeJujuHomeSuite.TearDownSuite(c) 86 } 87 88 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 89 s.ToolsFixture.TearDownTest(c) 90 s.MgoSuite.TearDownTest(c) 91 s.FakeJujuHomeSuite.TearDownTest(c) 92 dummy.Reset() 93 } 94 95 type mockBlockClient struct { 96 retry_count int 97 num_retries int 98 } 99 100 func (c *mockBlockClient) List() ([]params.Block, error) { 101 c.retry_count += 1 102 if c.retry_count == 5 { 103 return nil, fmt.Errorf("upgrade in progress") 104 } 105 if c.num_retries < 0 { 106 return nil, fmt.Errorf("other error") 107 } 108 if c.retry_count < c.num_retries { 109 return nil, fmt.Errorf("upgrade in progress") 110 } 111 return []params.Block{}, nil 112 } 113 114 func (c *mockBlockClient) Close() error { 115 return nil 116 } 117 118 func (s *BootstrapSuite) TestBootstrapAPIReadyRetries(c *gc.C) { 119 s.PatchValue(&bootstrapReadyPollDelay, 1*time.Millisecond) 120 s.PatchValue(&bootstrapReadyPollCount, 5) 121 defaultSeriesVersion := version.Current 122 // Force a dev version by having a non zero build number. 123 // This is because we have not uploaded any tools and auto 124 // upload is only enabled for dev versions. 125 defaultSeriesVersion.Build = 1234 126 s.PatchValue(&version.Current, defaultSeriesVersion) 127 for _, t := range []struct { 128 num_retries int 129 err string 130 }{ 131 {0, ""}, // agent ready immediately 132 {2, ""}, // agent ready after 2 polls 133 {6, "upgrade in progress"}, // agent ready after 6 polls but that's too long 134 {-1, "other error"}, // another error is returned 135 } { 136 resetJujuHome(c, "devenv") 137 138 s.mockBlockClient.num_retries = t.num_retries 139 s.mockBlockClient.retry_count = 0 140 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 141 if t.err == "" { 142 c.Check(err, jc.ErrorIsNil) 143 } else { 144 c.Check(err, gc.ErrorMatches, t.err) 145 } 146 expectedRetries := t.num_retries 147 if t.num_retries <= 0 { 148 expectedRetries = 1 149 } 150 // Only retry maximum of bootstrapReadyPollCount times. 151 if expectedRetries > 5 { 152 expectedRetries = 5 153 } 154 c.Check(s.mockBlockClient.retry_count, gc.Equals, expectedRetries) 155 } 156 } 157 158 func (s *BootstrapSuite) TestRunTests(c *gc.C) { 159 for i, test := range bootstrapTests { 160 c.Logf("\ntest %d: %s", i, test.info) 161 restore := s.run(c, test) 162 restore() 163 } 164 } 165 166 type bootstrapTest struct { 167 info string 168 // binary version string used to set version.Current 169 version string 170 sync bool 171 args []string 172 err string 173 // binary version string for expected tools; if set, no default tools 174 // will be uploaded before running the test. 175 upload string 176 constraints constraints.Value 177 placement string 178 hostArch string 179 keepBroken bool 180 } 181 182 func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) (restore gitjujutesting.Restorer) { 183 // Create home with dummy provider and remove all 184 // of its envtools. 185 env := resetJujuHome(c, "peckham") 186 187 // Although we're testing PrepareEndpointsForCaching interactions 188 // separately in the juju package, here we just ensure it gets 189 // called with the right arguments. 190 prepareCalled := false 191 addrConnectedTo := "localhost:17070" 192 restore = gitjujutesting.PatchValue( 193 &prepareEndpointsForCaching, 194 func(info configstore.EnvironInfo, hps [][]network.HostPort, addr network.HostPort) (_, _ []string, _ bool) { 195 prepareCalled = true 196 addrs, hosts, changed := juju.PrepareEndpointsForCaching(info, hps, addr) 197 // Because we're bootstrapping the addresses will always 198 // change, as there's no .jenv file saved yet. 199 c.Assert(changed, jc.IsTrue) 200 return addrs, hosts, changed 201 }, 202 ) 203 204 if test.version != "" { 205 useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1) 206 origVersion := version.Current 207 version.Current = version.MustParseBinary(useVersion) 208 restore = restore.Add(func() { 209 version.Current = origVersion 210 }) 211 } 212 213 if test.hostArch != "" { 214 origArch := arch.HostArch 215 arch.HostArch = func() string { 216 return test.hostArch 217 } 218 restore = restore.Add(func() { 219 arch.HostArch = origArch 220 }) 221 } 222 223 // Run command and check for uploads. 224 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), test.args...) 225 // Check for remaining operations/errors. 226 if test.err != "" { 227 err := <-errc 228 c.Assert(err, gc.NotNil) 229 stripped := strings.Replace(err.Error(), "\n", "", -1) 230 c.Check(stripped, gc.Matches, test.err) 231 return restore 232 } 233 if !c.Check(<-errc, gc.IsNil) { 234 return restore 235 } 236 237 opBootstrap := (<-opc).(dummy.OpBootstrap) 238 c.Check(opBootstrap.Env, gc.Equals, "peckham") 239 c.Check(opBootstrap.Args.Constraints, gc.DeepEquals, test.constraints) 240 c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement) 241 242 opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap) 243 c.Check(opFinalizeBootstrap.Env, gc.Equals, "peckham") 244 c.Check(opFinalizeBootstrap.InstanceConfig.Tools, gc.NotNil) 245 if test.upload != "" { 246 c.Check(opFinalizeBootstrap.InstanceConfig.Tools.Version.String(), gc.Equals, test.upload) 247 } 248 249 store, err := configstore.Default() 250 c.Assert(err, jc.ErrorIsNil) 251 // Check a CA cert/key was generated by reloading the environment. 252 env, err = environs.NewFromName("peckham", store) 253 c.Assert(err, jc.ErrorIsNil) 254 _, hasCert := env.Config().CACert() 255 c.Check(hasCert, jc.IsTrue) 256 _, hasKey := env.Config().CAPrivateKey() 257 c.Check(hasKey, jc.IsTrue) 258 info, err := store.ReadInfo("peckham") 259 c.Assert(err, jc.ErrorIsNil) 260 c.Assert(info, gc.NotNil) 261 c.Assert(prepareCalled, jc.IsTrue) 262 c.Assert(info.APIEndpoint().Addresses, gc.DeepEquals, []string{addrConnectedTo}) 263 return restore 264 } 265 266 var bootstrapTests = []bootstrapTest{{ 267 info: "no args, no error, no upload, no constraints", 268 }, { 269 info: "bad --constraints", 270 args: []string{"--constraints", "bad=wrong"}, 271 err: `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`, 272 }, { 273 info: "conflicting --constraints", 274 args: []string{"--constraints", "instance-type=foo mem=4G"}, 275 err: `failed to bootstrap environment: ambiguous constraints: "instance-type" overlaps with "mem"`, 276 }, { 277 info: "bad --series", 278 args: []string{"--series", "1bad1"}, 279 err: `invalid value "1bad1" for flag --series: invalid series name "1bad1"`, 280 }, { 281 info: "lonely --series", 282 args: []string{"--series", "fine"}, 283 err: `--series requires --upload-tools`, 284 }, { 285 info: "lonely --upload-series", 286 args: []string{"--upload-series", "fine"}, 287 err: `--upload-series requires --upload-tools`, 288 }, { 289 info: "--upload-series with --series", 290 args: []string{"--upload-tools", "--upload-series", "foo", "--series", "bar"}, 291 err: `--upload-series and --series can't be used together`, 292 }, { 293 info: "bad environment", 294 version: "1.2.3-%LTS%-amd64", 295 args: []string{"-e", "brokenenv"}, 296 err: `failed to bootstrap environment: dummy.Bootstrap is broken`, 297 }, { 298 info: "constraints", 299 args: []string{"--constraints", "mem=4G cpu-cores=4"}, 300 constraints: constraints.MustParse("mem=4G cpu-cores=4"), 301 }, { 302 info: "unsupported constraint passed through but no error", 303 args: []string{"--constraints", "mem=4G cpu-cores=4 cpu-power=10"}, 304 constraints: constraints.MustParse("mem=4G cpu-cores=4 cpu-power=10"), 305 }, { 306 info: "--upload-tools uses arch from constraint if it matches current version", 307 version: "1.3.3-saucy-ppc64el", 308 hostArch: "ppc64el", 309 args: []string{"--upload-tools", "--constraints", "arch=ppc64el"}, 310 upload: "1.3.3.1-raring-ppc64el", // from version.Current 311 constraints: constraints.MustParse("arch=ppc64el"), 312 }, { 313 info: "--upload-tools rejects mismatched arch", 314 version: "1.3.3-saucy-amd64", 315 hostArch: "amd64", 316 args: []string{"--upload-tools", "--constraints", "arch=ppc64el"}, 317 err: `failed to bootstrap environment: cannot build tools for "ppc64el" using a machine running on "amd64"`, 318 }, { 319 info: "--upload-tools rejects non-supported arch", 320 version: "1.3.3-saucy-mips64", 321 hostArch: "mips64", 322 args: []string{"--upload-tools"}, 323 err: `failed to bootstrap environment: environment "peckham" of type dummy does not support instances running on "mips64"`, 324 }, { 325 info: "--upload-tools always bumps build number", 326 version: "1.2.3.4-raring-amd64", 327 args: []string{"--upload-tools"}, 328 upload: "1.2.3.5-raring-amd64", 329 }, { 330 info: "placement", 331 args: []string{"--to", "something"}, 332 placement: "something", 333 }, { 334 info: "keep broken", 335 args: []string{"--keep-broken"}, 336 keepBroken: true, 337 }, { 338 info: "additional args", 339 args: []string{"anything", "else"}, 340 err: `unrecognized args: \["anything" "else"\]`, 341 }, { 342 info: "--agent-version with --upload-tools", 343 args: []string{"--agent-version", "1.1.0", "--upload-tools"}, 344 err: `--agent-version and --upload-tools can't be used together`, 345 }, { 346 info: "--agent-version with --no-auto-upgrade", 347 args: []string{"--agent-version", "1.1.0", "--no-auto-upgrade"}, 348 err: `--agent-version and --no-auto-upgrade can't be used together`, 349 }, { 350 info: "invalid --agent-version value", 351 args: []string{"--agent-version", "foo"}, 352 err: `invalid version "foo"`, 353 }, { 354 info: "agent-version doesn't match client version major", 355 version: "1.3.3-saucy-ppc64el", 356 args: []string{"--agent-version", "2.3.0"}, 357 err: `requested agent version major.minor mismatch`, 358 }, { 359 info: "agent-version doesn't match client version minor", 360 version: "1.3.3-saucy-ppc64el", 361 args: []string{"--agent-version", "1.4.0"}, 362 err: `requested agent version major.minor mismatch`, 363 }} 364 365 func (s *BootstrapSuite) TestRunEnvNameMissing(c *gc.C) { 366 s.PatchValue(&getEnvName, func(*BootstrapCommand) string { return "" }) 367 368 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 369 370 c.Check(err, gc.ErrorMatches, "the name of the environment must be specified") 371 } 372 373 const provisionalEnvs = ` 374 environments: 375 devenv: 376 type: dummy 377 cloudsigma: 378 type: cloudsigma 379 vsphere: 380 type: vsphere 381 ` 382 383 func (s *BootstrapSuite) TestCheckProviderProvisional(c *gc.C) { 384 coretesting.WriteEnvironments(c, provisionalEnvs) 385 386 err := checkProviderType("devenv") 387 c.Assert(err, jc.ErrorIsNil) 388 389 for name, flag := range provisionalProviders { 390 // vsphere is disabled for gccgo. See lp:1440940. 391 if name == "vsphere" && runtime.Compiler == "gccgo" { 392 continue 393 } 394 c.Logf(" - trying %q -", name) 395 err := checkProviderType(name) 396 c.Check(err, gc.ErrorMatches, ".* provider is provisional .* set JUJU_DEV_FEATURE_FLAGS=.*") 397 398 err = os.Setenv(osenv.JujuFeatureFlagEnvKey, flag) 399 c.Assert(err, jc.ErrorIsNil) 400 err = checkProviderType(name) 401 c.Check(err, jc.ErrorIsNil) 402 } 403 } 404 405 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 406 env := resetJujuHome(c, "devenv") 407 defaultSeriesVersion := version.Current 408 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 409 // Force a dev version by having a non zero build number. 410 // This is because we have not uploaded any tools and auto 411 // upload is only enabled for dev versions. 412 defaultSeriesVersion.Build = 1234 413 s.PatchValue(&version.Current, defaultSeriesVersion) 414 415 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 416 c.Assert(err, jc.ErrorIsNil) 417 418 _, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 419 c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") 420 } 421 422 type mockBootstrapInstance struct { 423 instance.Instance 424 } 425 426 func (*mockBootstrapInstance) Addresses() ([]network.Address, error) { 427 return []network.Address{{Value: "localhost"}}, nil 428 } 429 430 func (s *BootstrapSuite) TestSeriesDeprecation(c *gc.C) { 431 ctx := s.checkSeriesArg(c, "--series") 432 c.Check(coretesting.Stderr(ctx), gc.Equals, 433 "Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.\nBootstrap complete\n") 434 } 435 436 func (s *BootstrapSuite) TestUploadSeriesDeprecation(c *gc.C) { 437 ctx := s.checkSeriesArg(c, "--upload-series") 438 c.Check(coretesting.Stderr(ctx), gc.Equals, 439 "Use of --upload-series is obsolete. --upload-tools now expands to all supported series of the same operating system.\nBootstrap complete\n") 440 } 441 442 func (s *BootstrapSuite) checkSeriesArg(c *gc.C, argVariant string) *cmd.Context { 443 _bootstrap := &fakeBootstrapFuncs{} 444 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 445 return _bootstrap 446 }) 447 resetJujuHome(c, "devenv") 448 s.PatchValue(&allInstances, func(environ environs.Environ) ([]instance.Instance, error) { 449 return []instance.Instance{&mockBootstrapInstance{}}, nil 450 }) 451 452 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--upload-tools", argVariant, "foo,bar") 453 454 c.Assert(err, jc.ErrorIsNil) 455 return ctx 456 } 457 458 // In the case where we cannot examine an environment, we want the 459 // error to propagate back up to the user. 460 func (s *BootstrapSuite) TestBootstrapPropagatesEnvErrors(c *gc.C) { 461 //TODO(bogdanteleaga): fix this for windows once permissions are fixed 462 if runtime.GOOS == "windows" { 463 c.Skip("bug 1403084: this is very platform specific. When/if we will support windows state machine, this will probably be rewritten.") 464 } 465 466 const envName = "devenv" 467 env := resetJujuHome(c, envName) 468 defaultSeriesVersion := version.Current 469 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 470 // Force a dev version by having a non zero build number. 471 // This is because we have not uploaded any tools and auto 472 // upload is only enabled for dev versions. 473 defaultSeriesVersion.Build = 1234 474 s.PatchValue(&version.Current, defaultSeriesVersion) 475 s.PatchValue(&environType, func(string) (string, error) { return "", nil }) 476 477 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", envName) 478 c.Assert(err, jc.ErrorIsNil) 479 480 // Change permissions on the jenv file to simulate some kind of 481 // unexpected error when trying to read info from the environment 482 jenvFile := gitjujutesting.HomePath(".juju", "environments", envName+".jenv") 483 err = os.Chmod(jenvFile, os.FileMode(0200)) 484 c.Assert(err, jc.ErrorIsNil) 485 486 // The second bootstrap should fail b/c of the propogated error 487 _, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", envName) 488 c.Assert(err, gc.ErrorMatches, "there was an issue examining the environment: .*") 489 } 490 491 func (s *BootstrapSuite) TestBootstrapCleansUpIfEnvironPrepFails(c *gc.C) { 492 493 cleanupRan := false 494 495 s.PatchValue(&environType, func(string) (string, error) { return "", nil }) 496 s.PatchValue( 497 &environFromName, 498 func( 499 *cmd.Context, 500 string, 501 string, 502 func(environs.Environ) error, 503 ) (environs.Environ, func(), error) { 504 return nil, func() { cleanupRan = true }, fmt.Errorf("mock") 505 }, 506 ) 507 508 ctx := coretesting.Context(c) 509 _, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham") 510 c.Check(<-errc, gc.Not(gc.IsNil)) 511 c.Check(cleanupRan, jc.IsTrue) 512 } 513 514 // When attempting to bootstrap, check that when prepare errors out, 515 // the code cleans up the created jenv file, but *not* any existing 516 // environment that may have previously been bootstrapped. 517 func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) { 518 519 destroyedEnvRan := false 520 destroyedInfoRan := false 521 522 // Mock functions 523 mockDestroyPreparedEnviron := func( 524 *cmd.Context, 525 environs.Environ, 526 configstore.Storage, 527 string, 528 ) { 529 destroyedEnvRan = true 530 } 531 532 mockDestroyEnvInfo := func( 533 ctx *cmd.Context, 534 cfgName string, 535 store configstore.Storage, 536 action string, 537 ) { 538 destroyedInfoRan = true 539 } 540 541 mockEnvironFromName := func( 542 ctx *cmd.Context, 543 envName string, 544 action string, 545 _ func(environs.Environ) error, 546 ) (environs.Environ, func(), error) { 547 // Always show that the environment is bootstrapped. 548 return environFromNameProductionFunc( 549 ctx, 550 envName, 551 action, 552 func(env environs.Environ) error { 553 return environs.ErrAlreadyBootstrapped 554 }) 555 } 556 557 mockPrepare := func( 558 string, 559 environs.BootstrapContext, 560 configstore.Storage, 561 ) (environs.Environ, error) { 562 return nil, fmt.Errorf("mock-prepare") 563 } 564 565 // Simulation: prepare should fail and we should only clean up the 566 // jenv file. Any existing environment should not be destroyed. 567 s.PatchValue(&destroyPreparedEnviron, mockDestroyPreparedEnviron) 568 s.PatchValue(&environType, func(string) (string, error) { return "", nil }) 569 s.PatchValue(&environFromName, mockEnvironFromName) 570 s.PatchValue(&environs.PrepareFromName, mockPrepare) 571 s.PatchValue(&destroyEnvInfo, mockDestroyEnvInfo) 572 573 ctx := coretesting.Context(c) 574 _, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham") 575 c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$") 576 c.Check(destroyedEnvRan, jc.IsFalse) 577 c.Check(destroyedInfoRan, jc.IsTrue) 578 } 579 580 func (s *BootstrapSuite) TestBootstrapJenvWarning(c *gc.C) { 581 env := resetJujuHome(c, "devenv") 582 defaultSeriesVersion := version.Current 583 defaultSeriesVersion.Series = config.PreferredSeries(env.Config()) 584 // Force a dev version by having a non zero build number. 585 // This is because we have not uploaded any tools and auto 586 // upload is only enabled for dev versions. 587 defaultSeriesVersion.Build = 1234 588 s.PatchValue(&version.Current, defaultSeriesVersion) 589 590 store, err := configstore.Default() 591 c.Assert(err, jc.ErrorIsNil) 592 ctx := coretesting.Context(c) 593 environs.PrepareFromName("devenv", envcmd.BootstrapContext(ctx), store) 594 595 logger := "jenv.warning.test" 596 var testWriter loggo.TestWriter 597 loggo.RegisterWriter(logger, &testWriter, loggo.WARNING) 598 defer loggo.RemoveWriter(logger) 599 600 _, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "devenv") 601 c.Assert(<-errc, gc.IsNil) 602 c.Assert(testWriter.Log(), jc.LogMatches, []string{"ignoring environments.yaml: using bootstrap config in .*"}) 603 } 604 605 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 606 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 607 env := resetJujuHome(c, "devenv") 608 609 // Bootstrap the environment with an invalid source. 610 // The command returns with an error. 611 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", c.MkDir()) 612 c.Check(err, gc.ErrorMatches, `failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*`) 613 614 // Now check that there are no tools available. 615 _, err = envtools.FindTools( 616 env, version.Current.Major, version.Current.Minor, coretools.Filter{}) 617 c.Assert(err, gc.FitsTypeOf, errors.NotFoundf("")) 618 } 619 620 // createImageMetadata creates some image metadata in a local directory. 621 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 622 // Generate some image metadata. 623 im := []*imagemetadata.ImageMetadata{ 624 { 625 Id: "1234", 626 Arch: "amd64", 627 Version: "13.04", 628 RegionName: "region", 629 Endpoint: "endpoint", 630 }, 631 } 632 cloudSpec := &simplestreams.CloudSpec{ 633 Region: "region", 634 Endpoint: "endpoint", 635 } 636 sourceDir := c.MkDir() 637 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 638 c.Assert(err, jc.ErrorIsNil) 639 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 640 c.Assert(err, jc.ErrorIsNil) 641 return sourceDir, im 642 } 643 644 func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) { 645 sourceDir, _ := createImageMetadata(c) 646 resetJujuHome(c, "devenv") 647 648 _bootstrap := &fakeBootstrapFuncs{} 649 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 650 return _bootstrap 651 }) 652 653 coretesting.RunCommand( 654 c, envcmd.Wrap(&BootstrapCommand{}), 655 "--metadata-source", sourceDir, "--constraints", "mem=4G", 656 ) 657 c.Assert(_bootstrap.args.MetadataDir, gc.Equals, sourceDir) 658 } 659 660 func (s *BootstrapSuite) checkBootstrapWithVersion(c *gc.C, vers, expect string) { 661 resetJujuHome(c, "devenv") 662 663 _bootstrap := &fakeBootstrapFuncs{} 664 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 665 return _bootstrap 666 }) 667 668 currentVersion := version.Current 669 currentVersion.Major = 2 670 currentVersion.Minor = 3 671 s.PatchValue(&version.Current, currentVersion) 672 coretesting.RunCommand( 673 c, envcmd.Wrap(&BootstrapCommand{}), 674 "--agent-version", vers, 675 ) 676 c.Assert(_bootstrap.args.AgentVersion, gc.NotNil) 677 c.Assert(*_bootstrap.args.AgentVersion, gc.Equals, version.MustParse(expect)) 678 } 679 680 func (s *BootstrapSuite) TestBootstrapWithVersionNumber(c *gc.C) { 681 s.checkBootstrapWithVersion(c, "2.3.4", "2.3.4") 682 } 683 684 func (s *BootstrapSuite) TestBootstrapWithBinaryVersionNumber(c *gc.C) { 685 s.checkBootstrapWithVersion(c, "2.3.4-trusty-ppc64", "2.3.4") 686 } 687 688 func (s *BootstrapSuite) TestBootstrapWithNoAutoUpgrade(c *gc.C) { 689 resetJujuHome(c, "devenv") 690 691 _bootstrap := &fakeBootstrapFuncs{} 692 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 693 return _bootstrap 694 }) 695 696 currentVersion := version.Current 697 currentVersion.Major = 2 698 currentVersion.Minor = 22 699 currentVersion.Patch = 46 700 currentVersion.Series = "trusty" 701 currentVersion.Arch = "amd64" 702 s.PatchValue(&version.Current, currentVersion) 703 coretesting.RunCommand( 704 c, envcmd.Wrap(&BootstrapCommand{}), 705 "--no-auto-upgrade", 706 ) 707 c.Assert(*_bootstrap.args.AgentVersion, gc.Equals, version.MustParse("2.22.46")) 708 } 709 710 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 711 sourceDir := createToolsSource(c, vAll) 712 s.PatchValue(&version.Current.Number, version.MustParse("1.2.0")) 713 env := resetJujuHome(c, "peckham") 714 715 // Bootstrap the environment with the valid source. 716 // The bootstrapping has to show no error, because the tools 717 // are automatically synchronized. 718 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", sourceDir) 719 c.Assert(err, jc.ErrorIsNil) 720 721 // Now check the available tools which are the 1.2.0 envtools. 722 checkTools(c, env, v120All) 723 } 724 725 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, series string) environs.Environ { 726 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 727 sourceDir := createToolsSource(c, vAll) 728 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 729 730 // Change the tools location to be the test location and also 731 // the version and ensure their later restoring. 732 // Set the current version to be something for which there are no tools 733 // so we can test that an upload is forced. 734 s.PatchValue(&version.Current, version.MustParseBinary(vers+"-"+series+"-"+version.Current.Arch)) 735 736 // Create home with dummy provider and remove all 737 // of its envtools. 738 return resetJujuHome(c, "devenv") 739 } 740 741 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 742 s.PatchValue(&version.Current.Series, config.LatestLtsSeries()) 743 s.setupAutoUploadTest(c, "1.7.3", "quantal") 744 // Run command and check for that upload has been run for tools matching 745 // the current juju version. 746 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "devenv") 747 c.Assert(<-errc, gc.IsNil) 748 c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, "devenv") 749 icfg := (<-opc).(dummy.OpFinalizeBootstrap).InstanceConfig 750 c.Assert(icfg, gc.NotNil) 751 c.Assert(icfg.Tools.Version.String(), gc.Equals, "1.7.3.1-raring-"+version.Current.Arch) 752 } 753 754 func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) { 755 s.setupAutoUploadTest(c, "1.8.3", "precise") 756 _, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand))) 757 err := <-errc 758 c.Assert(err, gc.ErrorMatches, 759 "failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*") 760 } 761 762 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 763 s.setupAutoUploadTest(c, "1.8.3", "precise") 764 765 _, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{})) 766 c.Assert(err, gc.ErrorMatches, 767 "failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*") 768 } 769 770 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 771 772 buildToolsTarballAlwaysFails := func(forceVersion *version.Number, stream string) (*sync.BuiltTools, error) { 773 return nil, fmt.Errorf("an error") 774 } 775 776 s.setupAutoUploadTest(c, "1.7.3", "precise") 777 s.PatchValue(&sync.BuildToolsTarball, buildToolsTarballAlwaysFails) 778 779 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv") 780 781 c.Check(coretesting.Stderr(ctx), gc.Equals, fmt.Sprintf(` 782 Bootstrapping environment "devenv" 783 Starting new instance for initial state server 784 Building tools to upload (1.7.3.1-raring-%s) 785 `[1:], version.Current.Arch)) 786 c.Check(err, gc.ErrorMatches, "failed to bootstrap environment: cannot upload bootstrap tools: an error") 787 } 788 789 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 790 resetJujuHome(c, "devenv") 791 devVersion := version.Current 792 // Force a dev version by having a non zero build number. 793 // This is because we have not uploaded any tools and auto 794 // upload is only enabled for dev versions. 795 devVersion.Build = 1234 796 s.PatchValue(&version.Current, devVersion) 797 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv") 798 err := <-errc 799 c.Assert(err, gc.ErrorMatches, "failed to bootstrap environment: dummy.Bootstrap is broken") 800 var opDestroy *dummy.OpDestroy 801 for opDestroy == nil { 802 select { 803 case op := <-opc: 804 switch op := op.(type) { 805 case dummy.OpDestroy: 806 opDestroy = &op 807 } 808 default: 809 c.Error("expected call to env.Destroy") 810 return 811 } 812 } 813 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 814 } 815 816 func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) { 817 resetJujuHome(c, "devenv") 818 devVersion := version.Current 819 // Force a dev version by having a non zero build number. 820 // This is because we have not uploaded any tools and auto 821 // upload is only enabled for dev versions. 822 devVersion.Build = 1234 823 s.PatchValue(&version.Current, devVersion) 824 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv", "--keep-broken") 825 err := <-errc 826 c.Assert(err, gc.ErrorMatches, "failed to bootstrap environment: dummy.Bootstrap is broken") 827 done := false 828 for !done { 829 select { 830 case op, ok := <-opc: 831 if !ok { 832 done = true 833 break 834 } 835 switch op.(type) { 836 case dummy.OpDestroy: 837 c.Error("unexpected call to env.Destroy") 838 break 839 } 840 default: 841 break 842 } 843 } 844 } 845 846 // createToolsSource writes the mock tools and metadata into a temporary 847 // directory and returns it. 848 func createToolsSource(c *gc.C, versions []version.Binary) string { 849 versionStrings := make([]string, len(versions)) 850 for i, vers := range versions { 851 versionStrings[i] = vers.String() 852 } 853 source := c.MkDir() 854 toolstesting.MakeTools(c, source, "released", versionStrings) 855 return source 856 } 857 858 // resetJujuHome restores an new, clean Juju home environment without tools. 859 func resetJujuHome(c *gc.C, envName string) environs.Environ { 860 jenvDir := gitjujutesting.HomePath(".juju", "environments") 861 err := os.RemoveAll(jenvDir) 862 c.Assert(err, jc.ErrorIsNil) 863 coretesting.WriteEnvironments(c, envConfig) 864 dummy.Reset() 865 store, err := configstore.Default() 866 c.Assert(err, jc.ErrorIsNil) 867 env, err := environs.PrepareFromName(envName, envcmd.BootstrapContext(cmdtesting.NullContext(c)), store) 868 c.Assert(err, jc.ErrorIsNil) 869 return env 870 } 871 872 // checkTools check if the environment contains the passed envtools. 873 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 874 list, err := envtools.FindTools( 875 env, version.Current.Major, version.Current.Minor, coretools.Filter{}) 876 c.Check(err, jc.ErrorIsNil) 877 c.Logf("found: " + list.String()) 878 urls := list.URLs() 879 c.Check(urls, gc.HasLen, len(expected)) 880 } 881 882 var ( 883 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 884 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 885 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 886 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 887 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 888 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 889 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 890 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 891 v120t32 = version.MustParseBinary("1.2.0-trusty-i386") 892 v120t64 = version.MustParseBinary("1.2.0-trusty-amd64") 893 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 894 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 895 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 896 v100All = []version.Binary{ 897 v100d64, v100p64, v100q64, v100q32, 898 } 899 v120All = []version.Binary{ 900 v120d64, v120p64, v120q64, v120q32, v120t32, v120t64, 901 } 902 v190All = []version.Binary{ 903 v190p32, v190q64, 904 } 905 v200All = []version.Binary{ 906 v200p64, 907 } 908 vAll = joinBinaryVersions(v100All, v120All, v190All, v200All) 909 ) 910 911 func joinBinaryVersions(versions ...[]version.Binary) []version.Binary { 912 var all []version.Binary 913 for _, versions := range versions { 914 all = append(all, versions...) 915 } 916 return all 917 } 918 919 // TODO(menn0): This fake BootstrapInterface implementation is 920 // currently quite minimal but could be easily extended to cover more 921 // test scenarios. This could help improve some of the tests in this 922 // file which execute large amounts of external functionality. 923 type fakeBootstrapFuncs struct { 924 args bootstrap.BootstrapParams 925 } 926 927 func (fake *fakeBootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error { 928 return nil 929 } 930 931 func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error { 932 fake.args = args 933 return nil 934 }