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