github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "bytes" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "runtime" 13 "sort" 14 "strings" 15 "time" 16 17 "github.com/juju/cmd" 18 "github.com/juju/errors" 19 "github.com/juju/testing" 20 jc "github.com/juju/testing/checkers" 21 "github.com/juju/utils" 22 "github.com/juju/utils/arch" 23 jujuos "github.com/juju/utils/os" 24 "github.com/juju/utils/series" 25 "github.com/juju/version" 26 gc "gopkg.in/check.v1" 27 28 "github.com/juju/juju/cloud" 29 "github.com/juju/juju/cmd/modelcmd" 30 cmdtesting "github.com/juju/juju/cmd/testing" 31 "github.com/juju/juju/constraints" 32 "github.com/juju/juju/environs" 33 "github.com/juju/juju/environs/bootstrap" 34 "github.com/juju/juju/environs/config" 35 "github.com/juju/juju/environs/filestorage" 36 "github.com/juju/juju/environs/gui" 37 "github.com/juju/juju/environs/imagemetadata" 38 "github.com/juju/juju/environs/simplestreams" 39 sstesting "github.com/juju/juju/environs/simplestreams/testing" 40 "github.com/juju/juju/environs/sync" 41 envtesting "github.com/juju/juju/environs/testing" 42 envtools "github.com/juju/juju/environs/tools" 43 toolstesting "github.com/juju/juju/environs/tools/testing" 44 "github.com/juju/juju/instance" 45 "github.com/juju/juju/juju/keys" 46 "github.com/juju/juju/juju/osenv" 47 "github.com/juju/juju/jujuclient" 48 "github.com/juju/juju/jujuclient/jujuclienttesting" 49 "github.com/juju/juju/network" 50 "github.com/juju/juju/provider/dummy" 51 coretesting "github.com/juju/juju/testing" 52 coretools "github.com/juju/juju/tools" 53 jujuversion "github.com/juju/juju/version" 54 ) 55 56 type BootstrapSuite struct { 57 coretesting.FakeJujuXDGDataHomeSuite 58 testing.MgoSuite 59 envtesting.ToolsFixture 60 store *jujuclienttesting.MemStore 61 } 62 63 var _ = gc.Suite(&BootstrapSuite{}) 64 65 func init() { 66 dummyProvider, err := environs.Provider("dummy") 67 if err != nil { 68 panic(err) 69 } 70 environs.RegisterProvider("no-cloud-region-detection", noCloudRegionDetectionProvider{}) 71 environs.RegisterProvider("no-cloud-regions", noCloudRegionsProvider{dummyProvider}) 72 environs.RegisterProvider("no-credentials", noCredentialsProvider{}) 73 environs.RegisterProvider("many-credentials", manyCredentialsProvider{dummyProvider}) 74 } 75 76 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 77 s.FakeJujuXDGDataHomeSuite.SetUpSuite(c) 78 s.MgoSuite.SetUpSuite(c) 79 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 80 } 81 82 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 83 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 84 s.MgoSuite.SetUpTest(c) 85 s.ToolsFixture.SetUpTest(c) 86 87 // Set jujuversion.Current to a known value, for which we 88 // will make tools available. Individual tests may 89 // override this. 90 s.PatchValue(&jujuversion.Current, v100p64.Number) 91 s.PatchValue(&arch.HostArch, func() string { return v100p64.Arch }) 92 s.PatchValue(&series.HostSeries, func() string { return v100p64.Series }) 93 s.PatchValue(&jujuos.HostOS, func() jujuos.OSType { return jujuos.Ubuntu }) 94 95 // Set up a local source with tools. 96 sourceDir := createToolsSource(c, vAll) 97 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 98 99 expectedNumber := jujuversion.Current 100 expectedNumber.Build = 1235 101 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &expectedNumber)) 102 103 s.PatchValue(&waitForAgentInitialisation, func(*cmd.Context, *modelcmd.ModelCommandBase, string, string) error { 104 return nil 105 }) 106 107 // TODO(wallyworld) - add test data when tests are improved 108 s.store = jujuclienttesting.NewMemStore() 109 } 110 111 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 112 s.MgoSuite.TearDownSuite(c) 113 s.FakeJujuXDGDataHomeSuite.TearDownSuite(c) 114 } 115 116 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 117 s.ToolsFixture.TearDownTest(c) 118 s.MgoSuite.TearDownTest(c) 119 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 120 dummy.Reset(c) 121 } 122 123 // bootstrapCommandWrapper wraps the bootstrap command. The wrapped command has 124 // the ability to disable fetching GUI information from simplestreams, so that 125 // it is possible to test the bootstrap process without connecting to the 126 // network. This ability can be turned on by setting disableGUI to true. 127 type bootstrapCommandWrapper struct { 128 bootstrapCommand 129 disableGUI bool 130 } 131 132 func (c *bootstrapCommandWrapper) Run(ctx *cmd.Context) error { 133 if c.disableGUI { 134 c.bootstrapCommand.noGUI = true 135 } 136 return c.bootstrapCommand.Run(ctx) 137 } 138 139 func (s *BootstrapSuite) newBootstrapCommand() cmd.Command { 140 return s.newBootstrapCommandWrapper(true) 141 } 142 143 func (s *BootstrapSuite) newBootstrapCommandWrapper(disableGUI bool) cmd.Command { 144 c := &bootstrapCommandWrapper{ 145 disableGUI: disableGUI, 146 } 147 c.SetClientStore(s.store) 148 return modelcmd.Wrap(c) 149 } 150 151 func (s *BootstrapSuite) TestRunTests(c *gc.C) { 152 for i, test := range bootstrapTests { 153 c.Logf("\ntest %d: %s", i, test.info) 154 restore := s.run(c, test) 155 restore() 156 } 157 } 158 159 type bootstrapTest struct { 160 info string 161 // binary version string used to set jujuversion.Current 162 version string 163 sync bool 164 args []string 165 err string 166 // binary version string for expected tools; if set, no default tools 167 // will be uploaded before running the test. 168 upload string 169 constraints constraints.Value 170 bootstrapConstraints constraints.Value 171 placement string 172 hostArch string 173 keepBroken bool 174 } 175 176 func (s *BootstrapSuite) patchVersionAndSeries(c *gc.C, hostSeries string) { 177 resetJujuXDGDataHome(c) 178 s.PatchValue(&series.HostSeries, func() string { return hostSeries }) 179 s.patchVersion(c) 180 } 181 182 func (s *BootstrapSuite) patchVersion(c *gc.C) { 183 // Force a dev version by having a non zero build number. 184 // This is because we have not uploaded any tools and auto 185 // upload is only enabled for dev versions. 186 num := jujuversion.Current 187 num.Build = 1234 188 s.PatchValue(&jujuversion.Current, num) 189 } 190 191 func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) testing.Restorer { 192 // Create home with dummy provider and remove all 193 // of its envtools. 194 resetJujuXDGDataHome(c) 195 dummy.Reset(c) 196 197 var restore testing.Restorer = func() { 198 s.store = jujuclienttesting.NewMemStore() 199 } 200 bootstrapVersion := v100p64 201 if test.version != "" { 202 useVersion := strings.Replace(test.version, "%LTS%", series.LatestLts(), 1) 203 bootstrapVersion = version.MustParseBinary(useVersion) 204 restore = restore.Add(testing.PatchValue(&jujuversion.Current, bootstrapVersion.Number)) 205 restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return bootstrapVersion.Arch })) 206 restore = restore.Add(testing.PatchValue(&series.HostSeries, func() string { return bootstrapVersion.Series })) 207 bootstrapVersion.Build = 1 208 if test.upload != "" { 209 uploadVers := version.MustParseBinary(test.upload) 210 bootstrapVersion.Number = uploadVers.Number 211 } 212 restore = restore.Add(testing.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &bootstrapVersion.Number))) 213 } 214 215 if test.hostArch != "" { 216 restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return test.hostArch })) 217 } 218 219 controllerName := "peckham-controller" 220 cloudName := "dummy" 221 222 // Run command and check for uploads. 223 args := append([]string{ 224 controllerName, cloudName, 225 "--config", "default-series=raring", 226 }, test.args...) 227 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), s.newBootstrapCommand(), args...) 228 var err error 229 select { 230 case err = <-errc: 231 case <-time.After(coretesting.LongWait): 232 c.Fatal("timed out") 233 } 234 // Check for remaining operations/errors. 235 if test.err != "" { 236 c.Assert(err, gc.NotNil) 237 stripped := strings.Replace(err.Error(), "\n", "", -1) 238 c.Check(stripped, gc.Matches, test.err) 239 return restore 240 } 241 if !c.Check(err, gc.IsNil) { 242 return restore 243 } 244 245 op, ok := <-opc 246 c.Assert(ok, gc.Equals, true) 247 opBootstrap := op.(dummy.OpBootstrap) 248 c.Check(opBootstrap.Env, gc.Equals, bootstrap.ControllerModelName) 249 c.Check(opBootstrap.Args.ModelConstraints, gc.DeepEquals, test.constraints) 250 if test.bootstrapConstraints == (constraints.Value{}) { 251 test.bootstrapConstraints = test.constraints 252 } 253 c.Check(opBootstrap.Args.BootstrapConstraints, gc.DeepEquals, test.bootstrapConstraints) 254 c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement) 255 256 opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap) 257 c.Check(opFinalizeBootstrap.Env, gc.Equals, bootstrap.ControllerModelName) 258 c.Check(opFinalizeBootstrap.InstanceConfig.ToolsList(), gc.Not(gc.HasLen), 0) 259 if test.upload != "" { 260 c.Check(opFinalizeBootstrap.InstanceConfig.AgentVersion().String(), gc.Equals, test.upload) 261 } 262 263 // Check controllers.yaml controller details. 264 addrConnectedTo := []string{"localhost:17070"} 265 266 controller, err := s.store.ControllerByName(controllerName) 267 c.Assert(err, jc.ErrorIsNil) 268 c.Assert(controller.CACert, gc.Not(gc.Equals), "") 269 c.Assert(controller.UnresolvedAPIEndpoints, gc.DeepEquals, addrConnectedTo) 270 c.Assert(controller.APIEndpoints, gc.DeepEquals, addrConnectedTo) 271 c.Assert(utils.IsValidUUIDString(controller.ControllerUUID), jc.IsTrue) 272 // We don't care about build numbers here. 273 bootstrapVers := bootstrapVersion.Number 274 bootstrapVers.Build = 0 275 controllerVers := version.MustParse(controller.AgentVersion) 276 controllerVers.Build = 0 277 c.Assert(controllerVers.String(), gc.Equals, bootstrapVers.String()) 278 279 controllerModel, err := s.store.ModelByName(controllerName, "admin@local/controller") 280 c.Assert(err, jc.ErrorIsNil) 281 c.Assert(utils.IsValidUUIDString(controllerModel.ModelUUID), jc.IsTrue) 282 283 // Bootstrap config should have been saved, and should only contain 284 // the type, name, and any user-supplied configuration. 285 bootstrapConfig, err := s.store.BootstrapConfigForController(controllerName) 286 c.Assert(err, jc.ErrorIsNil) 287 c.Assert(bootstrapConfig.Cloud, gc.Equals, "dummy") 288 c.Assert(bootstrapConfig.Credential, gc.Equals, "") 289 expected := map[string]interface{}{ 290 "name": bootstrap.ControllerModelName, 291 "type": "dummy", 292 "default-series": "raring", 293 "authorized-keys": "public auth key\n", 294 // Dummy provider defaults 295 "broken": "", 296 "secret": "pork", 297 "controller": false, 298 } 299 for k, v := range config.ConfigDefaults() { 300 if _, ok := expected[k]; !ok { 301 expected[k] = v 302 } 303 } 304 c.Assert(bootstrapConfig.Config, jc.DeepEquals, expected) 305 306 return restore 307 } 308 309 var bootstrapTests = []bootstrapTest{{ 310 info: "no args, no error, no upload, no constraints", 311 }, { 312 info: "bad --constraints", 313 args: []string{"--constraints", "bad=wrong"}, 314 err: `unknown constraint "bad"`, 315 }, { 316 info: "conflicting --constraints", 317 args: []string{"--constraints", "instance-type=foo mem=4G"}, 318 err: `ambiguous constraints: "instance-type" overlaps with "mem"`, 319 }, { 320 info: "bad model", 321 version: "1.2.3-%LTS%-amd64", 322 args: []string{"--config", "broken=Bootstrap Destroy", "--auto-upgrade"}, 323 err: `failed to bootstrap model: dummy.Bootstrap is broken`, 324 }, { 325 info: "constraints", 326 args: []string{"--constraints", "mem=4G cores=4"}, 327 constraints: constraints.MustParse("mem=4G cores=4"), 328 }, { 329 info: "bootstrap and environ constraints", 330 args: []string{"--constraints", "mem=4G cores=4", "--bootstrap-constraints", "mem=8G"}, 331 constraints: constraints.MustParse("mem=4G cores=4"), 332 bootstrapConstraints: constraints.MustParse("mem=8G cores=4"), 333 }, { 334 info: "unsupported constraint passed through but no error", 335 args: []string{"--constraints", "mem=4G cores=4 cpu-power=10"}, 336 constraints: constraints.MustParse("mem=4G cores=4 cpu-power=10"), 337 }, { 338 info: "--build-agent uses arch from constraint if it matches current version", 339 version: "1.3.3-saucy-ppc64el", 340 hostArch: "ppc64el", 341 args: []string{"--build-agent", "--constraints", "arch=ppc64el"}, 342 upload: "1.3.3.1-raring-ppc64el", // from jujuversion.Current 343 constraints: constraints.MustParse("arch=ppc64el"), 344 }, { 345 info: "--build-agent rejects mismatched arch", 346 version: "1.3.3-saucy-amd64", 347 hostArch: "amd64", 348 args: []string{"--build-agent", "--constraints", "arch=ppc64el"}, 349 err: `failed to bootstrap model: cannot use agent built for "ppc64el" using a machine running on "amd64"`, 350 }, { 351 info: "--build-agent rejects non-supported arch", 352 version: "1.3.3-saucy-mips64", 353 hostArch: "mips64", 354 args: []string{"--build-agent"}, 355 err: fmt.Sprintf(`failed to bootstrap model: model %q of type dummy does not support instances running on "mips64"`, bootstrap.ControllerModelName), 356 }, { 357 info: "--build-agent always bumps build number", 358 version: "1.2.3.4-raring-amd64", 359 hostArch: "amd64", 360 args: []string{"--build-agent"}, 361 upload: "1.2.3.5-raring-amd64", 362 }, { 363 info: "placement", 364 args: []string{"--to", "something"}, 365 placement: "something", 366 }, { 367 info: "keep broken", 368 args: []string{"--keep-broken"}, 369 keepBroken: true, 370 }, { 371 info: "additional args", 372 args: []string{"anything", "else"}, 373 err: `unrecognized args: \["anything" "else"\]`, 374 }, { 375 info: "--agent-version with --build-agent", 376 args: []string{"--agent-version", "1.1.0", "--build-agent"}, 377 err: `--agent-version and --build-agent can't be used together`, 378 }, { 379 info: "invalid --agent-version value", 380 args: []string{"--agent-version", "foo"}, 381 err: `invalid version "foo"`, 382 }, { 383 info: "agent-version doesn't match client version major", 384 version: "1.3.3-saucy-ppc64el", 385 args: []string{"--agent-version", "2.3.0"}, 386 err: `requested agent version major.minor mismatch`, 387 }, { 388 info: "agent-version doesn't match client version minor", 389 version: "1.3.3-saucy-ppc64el", 390 args: []string{"--agent-version", "1.4.0"}, 391 err: `requested agent version major.minor mismatch`, 392 }, { 393 info: "--clouds with --regions", 394 args: []string{"--clouds", "--regions", "aws"}, 395 err: `--clouds and --regions can't be used together`, 396 }} 397 398 func (s *BootstrapSuite) TestRunCloudNameMissing(c *gc.C) { 399 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "my-controller") 400 c.Check(err, gc.ErrorMatches, "controller name and cloud name are required") 401 } 402 403 func (s *BootstrapSuite) TestRunCloudNameUnknown(c *gc.C) { 404 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "my-controller", "unknown") 405 c.Check(err, gc.ErrorMatches, `unknown cloud "unknown", please try "juju update-clouds"`) 406 } 407 408 func (s *BootstrapSuite) TestCheckProviderProvisional(c *gc.C) { 409 err := checkProviderType("devcontroller") 410 c.Assert(err, jc.ErrorIsNil) 411 412 for name, flag := range provisionalProviders { 413 // vsphere is disabled for gccgo. See lp:1440940. 414 if name == "vsphere" && runtime.Compiler == "gccgo" { 415 continue 416 } 417 c.Logf(" - trying %q -", name) 418 err := checkProviderType(name) 419 c.Check(err, gc.ErrorMatches, ".* provider is provisional .* set JUJU_DEV_FEATURE_FLAGS=.*") 420 421 err = os.Setenv(osenv.JujuFeatureFlagEnvKey, flag) 422 c.Assert(err, jc.ErrorIsNil) 423 err = checkProviderType(name) 424 c.Check(err, jc.ErrorIsNil) 425 } 426 } 427 428 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 429 const controllerName = "dev" 430 s.patchVersionAndSeries(c, "raring") 431 432 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade") 433 c.Assert(err, jc.ErrorIsNil) 434 435 _, err = coretesting.RunCommand(c, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade") 436 c.Assert(err, gc.ErrorMatches, `controller "dev" already exists`) 437 } 438 439 func (s *BootstrapSuite) TestBootstrapSetsCurrentModel(c *gc.C) { 440 s.patchVersionAndSeries(c, "raring") 441 442 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 443 c.Assert(err, jc.ErrorIsNil) 444 currentController := s.store.CurrentControllerName 445 c.Assert(currentController, gc.Equals, "devcontroller") 446 modelName, err := s.store.CurrentModel(currentController) 447 c.Assert(err, jc.ErrorIsNil) 448 c.Assert(modelName, gc.Equals, "admin@local/default") 449 } 450 451 func (s *BootstrapSuite) TestBootstrapSetsControllerDetails(c *gc.C) { 452 s.patchVersionAndSeries(c, "raring") 453 454 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 455 c.Assert(err, jc.ErrorIsNil) 456 currentController := s.store.CurrentControllerName 457 c.Assert(currentController, gc.Equals, "devcontroller") 458 details, err := s.store.ControllerByName(currentController) 459 c.Assert(err, jc.ErrorIsNil) 460 c.Assert(*details.ModelCount, gc.Equals, 2) 461 c.Assert(*details.MachineCount, gc.Equals, 1) 462 c.Assert(details.AgentVersion, gc.Equals, jujuversion.Current.String()) 463 } 464 465 func (s *BootstrapSuite) TestBootstrapDefaultModel(c *gc.C) { 466 s.patchVersionAndSeries(c, "raring") 467 468 var bootstrap fakeBootstrapFuncs 469 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 470 return &bootstrap 471 }) 472 473 coretesting.RunCommand( 474 c, s.newBootstrapCommand(), 475 "devcontroller", "dummy", 476 "--auto-upgrade", 477 "--default-model", "mymodel", 478 "--config", "foo=bar", 479 ) 480 c.Assert(utils.IsValidUUIDString(bootstrap.args.ControllerConfig.ControllerUUID()), jc.IsTrue) 481 c.Assert(bootstrap.args.HostedModelConfig["name"], gc.Equals, "mymodel") 482 c.Assert(bootstrap.args.HostedModelConfig["foo"], gc.Equals, "bar") 483 } 484 485 func (s *BootstrapSuite) TestBootstrapTimeout(c *gc.C) { 486 s.patchVersionAndSeries(c, "raring") 487 488 var bootstrap fakeBootstrapFuncs 489 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 490 return &bootstrap 491 }) 492 coretesting.RunCommand( 493 c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade", 494 "--config", "bootstrap-timeout=99", 495 ) 496 c.Assert(bootstrap.args.DialOpts.Timeout, gc.Equals, 99*time.Second) 497 } 498 499 func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsProcessedAttributes(c *gc.C) { 500 s.patchVersionAndSeries(c, "raring") 501 502 var bootstrap fakeBootstrapFuncs 503 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 504 return &bootstrap 505 }) 506 507 fakeSSHFile := filepath.Join(c.MkDir(), "ssh") 508 err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600) 509 c.Assert(err, jc.ErrorIsNil) 510 coretesting.RunCommand( 511 c, s.newBootstrapCommand(), 512 "devcontroller", "dummy", 513 "--auto-upgrade", 514 "--config", "authorized-keys-path="+fakeSSHFile, 515 ) 516 _, ok := bootstrap.args.HostedModelConfig["authorized-keys-path"] 517 c.Assert(ok, jc.IsFalse) 518 } 519 520 func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsInheritedAttributes(c *gc.C) { 521 s.patchVersionAndSeries(c, "raring") 522 523 var bootstrap fakeBootstrapFuncs 524 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 525 return &bootstrap 526 }) 527 528 fakeSSHFile := filepath.Join(c.MkDir(), "ssh") 529 err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600) 530 c.Assert(err, jc.ErrorIsNil) 531 coretesting.RunCommand( 532 c, s.newBootstrapCommand(), 533 "devcontroller", "dummy", 534 "--auto-upgrade", 535 "--config", "authorized-keys=ssh-key", 536 "--config", "agent-version=1.19.0", 537 ) 538 _, ok := bootstrap.args.HostedModelConfig["authorized-keys"] 539 c.Assert(ok, jc.IsFalse) 540 _, ok = bootstrap.args.HostedModelConfig["agent-version"] 541 c.Assert(ok, jc.IsFalse) 542 } 543 544 func (s *BootstrapSuite) TestBootstrapWithGUI(c *gc.C) { 545 s.patchVersionAndSeries(c, "raring") 546 var bootstrap fakeBootstrapFuncs 547 548 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 549 return &bootstrap 550 }) 551 coretesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "devcontroller", "dummy") 552 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, gui.DefaultBaseURL) 553 } 554 555 func (s *BootstrapSuite) TestBootstrapWithCustomizedGUI(c *gc.C) { 556 s.patchVersionAndSeries(c, "raring") 557 s.PatchEnvironment("JUJU_GUI_SIMPLESTREAMS_URL", "https://1.2.3.4/gui/streams") 558 559 var bootstrap fakeBootstrapFuncs 560 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 561 return &bootstrap 562 }) 563 564 coretesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "devcontroller", "dummy") 565 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "https://1.2.3.4/gui/streams") 566 } 567 568 func (s *BootstrapSuite) TestBootstrapWithoutGUI(c *gc.C) { 569 s.patchVersionAndSeries(c, "raring") 570 var bootstrap fakeBootstrapFuncs 571 572 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 573 return &bootstrap 574 }) 575 coretesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "devcontroller", "dummy", "--no-gui") 576 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "") 577 } 578 579 type mockBootstrapInstance struct { 580 instance.Instance 581 } 582 583 func (*mockBootstrapInstance) Addresses() ([]network.Address, error) { 584 return []network.Address{{Value: "localhost"}}, nil 585 } 586 587 // In the case where we cannot examine the client store, we want the 588 // error to propagate back up to the user. 589 func (s *BootstrapSuite) TestBootstrapPropagatesStoreErrors(c *gc.C) { 590 const controllerName = "devcontroller" 591 s.patchVersionAndSeries(c, "raring") 592 593 store := jujuclienttesting.NewStubStore() 594 store.SetErrors(errors.New("oh noes")) 595 cmd := &bootstrapCommand{} 596 cmd.SetClientStore(store) 597 wrapped := modelcmd.Wrap(cmd, modelcmd.WrapSkipModelFlags, modelcmd.WrapSkipDefaultModel) 598 _, err := coretesting.RunCommand(c, wrapped, controllerName, "dummy", "--auto-upgrade") 599 store.CheckCallNames(c, "CredentialForCloud") 600 c.Assert(err, gc.ErrorMatches, `loading credentials: oh noes`) 601 } 602 603 // When attempting to bootstrap, check that when prepare errors out, 604 // bootstrap will stop immediately. Nothing will be destroyed. 605 func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) { 606 destroyed := false 607 s.PatchValue(&environsDestroy, func(name string, _ environs.Environ, _ jujuclient.ControllerStore) error { 608 c.Assert(name, gc.Equals, "decontroller") 609 destroyed = true 610 return nil 611 }) 612 613 s.PatchValue(&bootstrapPrepare, func( 614 environs.BootstrapContext, 615 jujuclient.ClientStore, 616 bootstrap.PrepareParams, 617 ) (environs.Environ, error) { 618 return nil, errors.New("mock-prepare") 619 }) 620 621 ctx := coretesting.Context(c) 622 _, errc := cmdtesting.RunCommand( 623 ctx, s.newBootstrapCommand(), 624 "devcontroller", "dummy", 625 ) 626 c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$") 627 c.Check(destroyed, jc.IsFalse) 628 } 629 630 type controllerModelAccountParams struct { 631 controller string 632 controllerUUID string 633 model string 634 user string 635 } 636 637 func (s *BootstrapSuite) writeControllerModelAccountInfo(c *gc.C, context *controllerModelAccountParams) { 638 controller := context.controller 639 model := context.model 640 user := context.user 641 controllerUUID := "a-uuid" 642 if context.controllerUUID != "" { 643 controllerUUID = context.controllerUUID 644 } 645 err := s.store.AddController(controller, jujuclient.ControllerDetails{ 646 CACert: "a-cert", 647 ControllerUUID: controllerUUID, 648 }) 649 c.Assert(err, jc.ErrorIsNil) 650 err = s.store.SetCurrentController(controller) 651 c.Assert(err, jc.ErrorIsNil) 652 err = s.store.UpdateAccount(controller, jujuclient.AccountDetails{ 653 User: user, 654 Password: "secret", 655 }) 656 c.Assert(err, jc.ErrorIsNil) 657 err = s.store.UpdateModel(controller, model, jujuclient.ModelDetails{ 658 ModelUUID: "model-uuid", 659 }) 660 c.Assert(err, jc.ErrorIsNil) 661 err = s.store.SetCurrentModel(controller, model) 662 c.Assert(err, jc.ErrorIsNil) 663 } 664 665 func (s *BootstrapSuite) TestBootstrapErrorRestoresOldMetadata(c *gc.C) { 666 s.patchVersionAndSeries(c, "raring") 667 s.PatchValue(&bootstrapPrepare, func( 668 environs.BootstrapContext, 669 jujuclient.ClientStore, 670 bootstrap.PrepareParams, 671 ) (environs.Environ, error) { 672 ctx := controllerModelAccountParams{ 673 controller: "foo", 674 model: "foobar@local/bar", 675 user: "foobar@local", 676 } 677 s.writeControllerModelAccountInfo(c, &ctx) 678 return nil, errors.New("mock-prepare") 679 }) 680 681 ctx := controllerModelAccountParams{ 682 controller: "olddevcontroller", 683 controllerUUID: "another-uuid", 684 model: "fred@local/fredmodel", 685 user: "fred@local", 686 } 687 s.writeControllerModelAccountInfo(c, &ctx) 688 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 689 c.Assert(err, gc.ErrorMatches, "mock-prepare") 690 691 currentController := s.store.CurrentControllerName 692 c.Assert(currentController, gc.Equals, "olddevcontroller") 693 accountDetails, err := s.store.AccountDetails(currentController) 694 c.Assert(err, jc.ErrorIsNil) 695 c.Assert(accountDetails.User, gc.Equals, "fred@local") 696 currentModel, err := s.store.CurrentModel(currentController) 697 c.Assert(err, jc.ErrorIsNil) 698 c.Assert(currentModel, gc.Equals, "fred@local/fredmodel") 699 } 700 701 func (s *BootstrapSuite) TestBootstrapAlreadyExists(c *gc.C) { 702 const controllerName = "devcontroller" 703 s.patchVersionAndSeries(c, "raring") 704 705 cmaCtx := controllerModelAccountParams{ 706 controller: "devcontroller", 707 model: "fred@local/fredmodel", 708 user: "fred@local", 709 } 710 s.writeControllerModelAccountInfo(c, &cmaCtx) 711 712 ctx := coretesting.Context(c) 713 _, errc := cmdtesting.RunCommand(ctx, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade") 714 err := <-errc 715 c.Assert(err, jc.Satisfies, errors.IsAlreadyExists) 716 c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`controller %q already exists`, controllerName)) 717 currentController := s.store.CurrentControllerName 718 c.Assert(currentController, gc.Equals, "devcontroller") 719 accountDetails, err := s.store.AccountDetails(currentController) 720 c.Assert(err, jc.ErrorIsNil) 721 c.Assert(accountDetails.User, gc.Equals, "fred@local") 722 currentModel, err := s.store.CurrentModel(currentController) 723 c.Assert(err, jc.ErrorIsNil) 724 c.Assert(currentModel, gc.Equals, "fred@local/fredmodel") 725 } 726 727 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 728 s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0")) 729 resetJujuXDGDataHome(c) 730 731 // Bootstrap the controller with an invalid source. 732 // The command returns with an error. 733 _, err := coretesting.RunCommand( 734 c, s.newBootstrapCommand(), "--metadata-source", c.MkDir(), 735 "devcontroller", "dummy", 736 ) 737 c.Check(err, gc.ErrorMatches, `failed to bootstrap model: Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*`) 738 } 739 740 // createImageMetadata creates some image metadata in a local directory. 741 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 742 // Generate some image metadata. 743 im := []*imagemetadata.ImageMetadata{ 744 { 745 Id: "1234", 746 Arch: "amd64", 747 Version: "13.04", 748 RegionName: "region", 749 Endpoint: "endpoint", 750 }, 751 } 752 cloudSpec := &simplestreams.CloudSpec{ 753 Region: "region", 754 Endpoint: "endpoint", 755 } 756 sourceDir := c.MkDir() 757 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 758 c.Assert(err, jc.ErrorIsNil) 759 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 760 c.Assert(err, jc.ErrorIsNil) 761 return sourceDir, im 762 } 763 764 func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) { 765 sourceDir, _ := createImageMetadata(c) 766 resetJujuXDGDataHome(c) 767 768 var bootstrap fakeBootstrapFuncs 769 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 770 return &bootstrap 771 }) 772 773 coretesting.RunCommand( 774 c, s.newBootstrapCommand(), 775 "--metadata-source", sourceDir, "--constraints", "mem=4G", 776 "devcontroller", "dummy-cloud/region-1", 777 "--config", "default-series=raring", 778 ) 779 c.Assert(bootstrap.args.MetadataDir, gc.Equals, sourceDir) 780 } 781 782 func (s *BootstrapSuite) checkBootstrapWithVersion(c *gc.C, vers, expect string) { 783 resetJujuXDGDataHome(c) 784 785 var bootstrap fakeBootstrapFuncs 786 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 787 return &bootstrap 788 }) 789 790 num := jujuversion.Current 791 num.Major = 2 792 num.Minor = 3 793 s.PatchValue(&jujuversion.Current, num) 794 coretesting.RunCommand( 795 c, s.newBootstrapCommand(), 796 "--agent-version", vers, 797 "devcontroller", "dummy-cloud/region-1", 798 "--config", "default-series=raring", 799 ) 800 c.Assert(bootstrap.args.AgentVersion, gc.NotNil) 801 c.Assert(*bootstrap.args.AgentVersion, gc.Equals, version.MustParse(expect)) 802 } 803 804 func (s *BootstrapSuite) TestBootstrapWithVersionNumber(c *gc.C) { 805 s.checkBootstrapWithVersion(c, "2.3.4", "2.3.4") 806 } 807 808 func (s *BootstrapSuite) TestBootstrapWithBinaryVersionNumber(c *gc.C) { 809 s.checkBootstrapWithVersion(c, "2.3.4-trusty-ppc64", "2.3.4") 810 } 811 812 func (s *BootstrapSuite) TestBootstrapWithAutoUpgrade(c *gc.C) { 813 resetJujuXDGDataHome(c) 814 815 var bootstrap fakeBootstrapFuncs 816 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 817 return &bootstrap 818 }) 819 coretesting.RunCommand( 820 c, s.newBootstrapCommand(), 821 "--auto-upgrade", 822 "devcontroller", "dummy-cloud/region-1", 823 ) 824 c.Assert(bootstrap.args.AgentVersion, gc.IsNil) 825 } 826 827 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 828 sourceDir := createToolsSource(c, vAll) 829 s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0")) 830 series.SetLatestLtsForTesting("trusty") 831 resetJujuXDGDataHome(c) 832 833 // Bootstrap the controller with the valid source. 834 // The bootstrapping has to show no error, because the tools 835 // are automatically synchronized. 836 _, err := coretesting.RunCommand( 837 c, s.newBootstrapCommand(), "--metadata-source", sourceDir, 838 "devcontroller", "dummy-cloud/region-1", "--config", "default-series=trusty", 839 ) 840 c.Assert(err, jc.ErrorIsNil) 841 842 bootstrapConfig, params, err := modelcmd.NewGetBootstrapConfigParamsFunc( 843 coretesting.Context(c), s.store, 844 )("devcontroller") 845 c.Assert(err, jc.ErrorIsNil) 846 provider, err := environs.Provider(bootstrapConfig.CloudType) 847 c.Assert(err, jc.ErrorIsNil) 848 cfg, err := provider.PrepareConfig(*params) 849 c.Assert(err, jc.ErrorIsNil) 850 851 env, err := environs.New(environs.OpenParams{ 852 Cloud: params.Cloud, 853 Config: cfg, 854 }) 855 c.Assert(err, jc.ErrorIsNil) 856 err = env.PrepareForBootstrap(envtesting.BootstrapContext(c)) 857 c.Assert(err, jc.ErrorIsNil) 858 859 // Now check the available tools which are the 1.2.0 envtools. 860 checkTools(c, env, v120All) 861 } 862 863 func (s *BootstrapSuite) TestInteractiveBootstrap(c *gc.C) { 864 s.patchVersionAndSeries(c, "raring") 865 866 cmd := s.newBootstrapCommand() 867 err := coretesting.InitCommand(cmd, nil) 868 c.Assert(err, jc.ErrorIsNil) 869 ctx := coretesting.Context(c) 870 out := bytes.Buffer{} 871 ctx.Stdin = strings.NewReader(` 872 dummy-cloud 873 region-1 874 my-dummy-cloud 875 `[1:]) 876 ctx.Stdout = &out 877 err = cmd.Run(ctx) 878 if err != nil { 879 c.Logf(out.String()) 880 } 881 c.Assert(err, jc.ErrorIsNil) 882 883 name := s.store.CurrentControllerName 884 c.Assert(name, gc.Equals, "my-dummy-cloud") 885 controller := s.store.Controllers[name] 886 c.Assert(controller.Cloud, gc.Equals, "dummy-cloud") 887 c.Assert(controller.CloudRegion, gc.Equals, "region-1") 888 } 889 890 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, ser string) { 891 patchedVersion := version.MustParse(vers) 892 patchedVersion.Build = 1 893 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &patchedVersion)) 894 sourceDir := createToolsSource(c, vAll) 895 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 896 897 // Change the tools location to be the test location and also 898 // the version and ensure their later restoring. 899 // Set the current version to be something for which there are no tools 900 // so we can test that an upload is forced. 901 s.PatchValue(&jujuversion.Current, version.MustParse(vers)) 902 s.PatchValue(&series.HostSeries, func() string { return ser }) 903 904 // Create home with dummy provider and remove all 905 // of its envtools. 906 resetJujuXDGDataHome(c) 907 } 908 909 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 910 s.PatchValue(&series.HostSeries, func() string { return series.LatestLts() }) 911 s.setupAutoUploadTest(c, "1.7.3", "quantal") 912 // Run command and check for that upload has been run for tools matching 913 // the current juju version. 914 opc, errc := cmdtesting.RunCommand( 915 cmdtesting.NullContext(c), s.newBootstrapCommand(), 916 "devcontroller", "dummy-cloud/region-1", 917 "--config", "default-series=raring", 918 "--auto-upgrade", 919 ) 920 select { 921 case err := <-errc: 922 c.Assert(err, jc.ErrorIsNil) 923 case <-time.After(coretesting.LongWait): 924 c.Fatal("timed out") 925 } 926 c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, bootstrap.ControllerModelName) 927 icfg := (<-opc).(dummy.OpFinalizeBootstrap).InstanceConfig 928 c.Assert(icfg, gc.NotNil) 929 c.Assert(icfg.AgentVersion().String(), gc.Equals, "1.7.3.1-raring-"+arch.HostArch()) 930 } 931 932 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 933 s.setupAutoUploadTest(c, "1.8.3", "precise") 934 935 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), 936 "devcontroller", "dummy-cloud/region-1", 937 "--config", "default-series=raring", "--agent-version=1.8.4", 938 ) 939 c.Assert(err, gc.ErrorMatches, 940 "failed to bootstrap model: Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*") 941 } 942 943 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 944 945 BuildAgentTarballAlwaysFails := func(build bool, forceVersion *version.Number, stream string) (*sync.BuiltAgent, error) { 946 return nil, errors.New("an error") 947 } 948 949 s.setupAutoUploadTest(c, "1.7.3", "precise") 950 s.PatchValue(&sync.BuildAgentTarball, BuildAgentTarballAlwaysFails) 951 952 ctx, err := coretesting.RunCommand( 953 c, s.newBootstrapCommand(), 954 "devcontroller", "dummy-cloud/region-1", 955 "--config", "default-series=raring", 956 "--config", "agent-stream=proposed", 957 "--auto-upgrade", "--agent-version=1.7.3", 958 ) 959 960 c.Check(coretesting.Stderr(ctx), gc.Equals, ` 961 Creating Juju controller "devcontroller" on dummy-cloud/region-1 962 Looking for packaged Juju agent version 1.7.3 for amd64 963 No packaged binary found, preparing local Juju agent binary 964 `[1:]) 965 c.Check(err, gc.ErrorMatches, "failed to bootstrap model: cannot package bootstrap agent binary: an error") 966 } 967 968 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 969 resetJujuXDGDataHome(c) 970 s.patchVersion(c) 971 972 opc, errc := cmdtesting.RunCommand( 973 cmdtesting.NullContext(c), s.newBootstrapCommand(), 974 "devcontroller", "dummy-cloud/region-1", 975 "--config", "broken=Bootstrap Destroy", 976 "--auto-upgrade", 977 ) 978 select { 979 case err := <-errc: 980 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 981 case <-time.After(coretesting.LongWait): 982 c.Fatal("timed out") 983 } 984 985 var opDestroy *dummy.OpDestroy 986 for opDestroy == nil { 987 select { 988 case op := <-opc: 989 switch op := op.(type) { 990 case dummy.OpDestroy: 991 opDestroy = &op 992 } 993 default: 994 c.Error("expected call to env.Destroy") 995 return 996 } 997 } 998 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 999 } 1000 1001 func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) { 1002 resetJujuXDGDataHome(c) 1003 s.patchVersion(c) 1004 1005 ctx := coretesting.Context(c) 1006 opc, errc := cmdtesting.RunCommand(ctx, s.newBootstrapCommand(), 1007 "--keep-broken", 1008 "devcontroller", "dummy-cloud/region-1", 1009 "--config", "broken=Bootstrap Destroy", 1010 "--auto-upgrade", 1011 ) 1012 select { 1013 case err := <-errc: 1014 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 1015 case <-time.After(coretesting.LongWait): 1016 c.Fatal("timed out") 1017 } 1018 done := false 1019 for !done { 1020 select { 1021 case op, ok := <-opc: 1022 if !ok { 1023 done = true 1024 break 1025 } 1026 switch op.(type) { 1027 case dummy.OpDestroy: 1028 c.Error("unexpected call to env.Destroy") 1029 break 1030 } 1031 default: 1032 break 1033 } 1034 } 1035 stderr := strings.Replace(coretesting.Stderr(ctx), "\n", " ", -1) 1036 c.Assert(stderr, gc.Matches, `.*See .*juju kill\-controller.*`) 1037 } 1038 1039 func (s *BootstrapSuite) TestBootstrapUnknownCloudOrProvider(c *gc.C) { 1040 s.patchVersionAndSeries(c, "raring") 1041 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-such-provider") 1042 c.Assert(err, gc.ErrorMatches, `unknown cloud "no-such-provider", please try "juju update-clouds"`) 1043 } 1044 1045 func (s *BootstrapSuite) TestBootstrapProviderNoRegionDetection(c *gc.C) { 1046 s.patchVersionAndSeries(c, "raring") 1047 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-cloud-region-detection") 1048 c.Assert(err, gc.ErrorMatches, `unknown cloud "no-cloud-region-detection", please try "juju update-clouds"`) 1049 } 1050 1051 func (s *BootstrapSuite) TestBootstrapProviderNoRegions(c *gc.C) { 1052 ctx, err := coretesting.RunCommand( 1053 c, s.newBootstrapCommand(), "ctrl", "no-cloud-regions", 1054 "--config", "default-series=precise", 1055 ) 1056 c.Check(coretesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"ctrl\" on no-cloud-regions(.|\n)*") 1057 c.Assert(err, jc.ErrorIsNil) 1058 } 1059 1060 func (s *BootstrapSuite) TestBootstrapCloudNoRegions(c *gc.C) { 1061 resetJujuXDGDataHome(c) 1062 ctx, err := coretesting.RunCommand( 1063 c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-without-regions", 1064 "--config", "default-series=precise", 1065 ) 1066 c.Check(coretesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"ctrl\" on dummy-cloud-without-regions(.|\n)*") 1067 c.Assert(err, jc.ErrorIsNil) 1068 } 1069 1070 func (s *BootstrapSuite) TestBootstrapCloudNoRegionsOneSpecified(c *gc.C) { 1071 resetJujuXDGDataHome(c) 1072 ctx, err := coretesting.RunCommand( 1073 c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-without-regions/my-region", 1074 "--config", "default-series=precise", 1075 ) 1076 c.Check(coretesting.Stderr(ctx), gc.Matches, 1077 "region \"my-region\" not found \\(expected one of \\[\\]\\)\n\n.*") 1078 c.Assert(err, gc.Equals, cmd.ErrSilent) 1079 } 1080 1081 func (s *BootstrapSuite) TestBootstrapProviderNoCredentials(c *gc.C) { 1082 s.patchVersionAndSeries(c, "raring") 1083 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-credentials") 1084 c.Assert(err, gc.ErrorMatches, `detecting credentials for "no-credentials" cloud provider: credentials not found`) 1085 } 1086 1087 func (s *BootstrapSuite) TestBootstrapProviderManyDetectedCredentials(c *gc.C) { 1088 s.patchVersionAndSeries(c, "raring") 1089 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "many-credentials") 1090 c.Assert(err, gc.ErrorMatches, ambiguousDetectedCredentialError.Error()) 1091 } 1092 1093 func (s *BootstrapSuite) TestBootstrapProviderDetectRegionsInvalid(c *gc.C) { 1094 s.patchVersionAndSeries(c, "raring") 1095 ctx, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy/not-dummy") 1096 c.Assert(err, gc.Equals, cmd.ErrSilent) 1097 stderr := strings.Replace(coretesting.Stderr(ctx), "\n", "", -1) 1098 c.Assert(stderr, gc.Matches, `region "not-dummy" not found \(expected one of \["dummy"\]\)Specify an alternative region, or try "juju update-clouds".`) 1099 } 1100 1101 func (s *BootstrapSuite) TestBootstrapProviderManyCredentialsCloudNoAuthTypes(c *gc.C) { 1102 var bootstrap fakeBootstrapFuncs 1103 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1104 return &bootstrap 1105 }) 1106 1107 s.patchVersionAndSeries(c, "raring") 1108 s.store.Credentials = map[string]cloud.CloudCredential{ 1109 "many-credentials-no-auth-types": { 1110 AuthCredentials: map[string]cloud.Credential{"one": cloud.NewCredential("one", nil)}, 1111 }, 1112 } 1113 coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", 1114 "many-credentials-no-auth-types", 1115 "--credential", "one", 1116 ) 1117 c.Assert(bootstrap.args.Cloud.AuthTypes, jc.SameContents, cloud.AuthTypes{"one", "two"}) 1118 } 1119 1120 func (s *BootstrapSuite) TestManyAvailableCredentialsNoneSpecified(c *gc.C) { 1121 var bootstrap fakeBootstrapFuncs 1122 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1123 return &bootstrap 1124 }) 1125 1126 s.patchVersionAndSeries(c, "raring") 1127 s.store.Credentials = map[string]cloud.CloudCredential{ 1128 "dummy": { 1129 AuthCredentials: map[string]cloud.Credential{ 1130 "one": cloud.NewCredential("one", nil), 1131 "two": cloud.NewCredential("two", nil), 1132 }, 1133 }, 1134 } 1135 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy") 1136 msg := strings.Replace(err.Error(), "\n", "", -1) 1137 c.Assert(msg, gc.Matches, "more than one credential is available.*") 1138 } 1139 1140 func (s *BootstrapSuite) TestBootstrapProviderDetectRegions(c *gc.C) { 1141 resetJujuXDGDataHome(c) 1142 1143 var bootstrap fakeBootstrapFuncs 1144 bootstrap.cloudRegionDetector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) { 1145 return []cloud.Region{{Name: "bruce", Endpoint: "endpoint"}}, nil 1146 }) 1147 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1148 return &bootstrap 1149 }) 1150 1151 s.patchVersionAndSeries(c, "raring") 1152 coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy") 1153 c.Assert(bootstrap.args.CloudRegion, gc.Equals, "bruce") 1154 c.Assert(bootstrap.args.CloudCredentialName, gc.Equals, "default") 1155 sort.Sort(bootstrap.args.Cloud.AuthTypes) 1156 c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{ 1157 Type: "dummy", 1158 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 1159 Regions: []cloud.Region{{Name: "bruce", Endpoint: "endpoint"}}, 1160 }) 1161 } 1162 1163 func (s *BootstrapSuite) TestBootstrapProviderDetectNoRegions(c *gc.C) { 1164 resetJujuXDGDataHome(c) 1165 1166 var bootstrap fakeBootstrapFuncs 1167 bootstrap.cloudRegionDetector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) { 1168 return nil, errors.NotFoundf("regions") 1169 }) 1170 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1171 return &bootstrap 1172 }) 1173 1174 s.patchVersionAndSeries(c, "raring") 1175 coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy") 1176 c.Assert(bootstrap.args.CloudRegion, gc.Equals, "") 1177 sort.Sort(bootstrap.args.Cloud.AuthTypes) 1178 c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{ 1179 Type: "dummy", 1180 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 1181 }) 1182 } 1183 1184 func (s *BootstrapSuite) TestBootstrapProviderCaseInsensitiveRegionCheck(c *gc.C) { 1185 s.patchVersionAndSeries(c, "raring") 1186 1187 var prepareParams bootstrap.PrepareParams 1188 s.PatchValue(&bootstrapPrepare, func( 1189 ctx environs.BootstrapContext, 1190 stor jujuclient.ClientStore, 1191 params bootstrap.PrepareParams, 1192 ) (environs.Environ, error) { 1193 prepareParams = params 1194 return nil, errors.New("mock-prepare") 1195 }) 1196 1197 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy/DUMMY") 1198 c.Assert(err, gc.ErrorMatches, "mock-prepare") 1199 c.Assert(prepareParams.Cloud.Region, gc.Equals, "dummy") 1200 } 1201 1202 func (s *BootstrapSuite) TestBootstrapConfigFile(c *gc.C) { 1203 tmpdir := c.MkDir() 1204 configFile := filepath.Join(tmpdir, "config.yaml") 1205 err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644) 1206 c.Assert(err, jc.ErrorIsNil) 1207 1208 s.patchVersionAndSeries(c, "raring") 1209 _, err = coretesting.RunCommand( 1210 c, s.newBootstrapCommand(), "ctrl", "dummy", 1211 "--config", configFile, 1212 ) 1213 c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got string.*`) 1214 } 1215 1216 func (s *BootstrapSuite) TestBootstrapMultipleConfigFiles(c *gc.C) { 1217 tmpdir := c.MkDir() 1218 configFile1 := filepath.Join(tmpdir, "config-1.yaml") 1219 err := ioutil.WriteFile(configFile1, []byte( 1220 "controller: not-a-bool\nbroken: Bootstrap\n", 1221 ), 0644) 1222 c.Assert(err, jc.ErrorIsNil) 1223 configFile2 := filepath.Join(tmpdir, "config-2.yaml") 1224 err = ioutil.WriteFile(configFile2, []byte( 1225 "controller: false\n", 1226 ), 0644) 1227 1228 s.patchVersionAndSeries(c, "raring") 1229 _, err = coretesting.RunCommand( 1230 c, s.newBootstrapCommand(), "ctrl", "dummy", 1231 "--auto-upgrade", 1232 // the second config file should replace attributes 1233 // with the same name from the first, but leave the 1234 // others alone. 1235 "--config", configFile1, 1236 "--config", configFile2, 1237 ) 1238 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 1239 } 1240 1241 func (s *BootstrapSuite) TestBootstrapConfigFileAndAdHoc(c *gc.C) { 1242 tmpdir := c.MkDir() 1243 configFile := filepath.Join(tmpdir, "config.yaml") 1244 err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644) 1245 c.Assert(err, jc.ErrorIsNil) 1246 1247 s.patchVersionAndSeries(c, "raring") 1248 _, err = coretesting.RunCommand( 1249 c, s.newBootstrapCommand(), "ctrl", "dummy", 1250 "--auto-upgrade", 1251 // Configuration specified on the command line overrides 1252 // anything specified in files, no matter what the order. 1253 "--config", "controller=false", 1254 "--config", configFile, 1255 ) 1256 c.Assert(err, jc.ErrorIsNil) 1257 } 1258 1259 func (s *BootstrapSuite) TestBootstrapAutocertDNSNameBadPort(c *gc.C) { 1260 s.patchVersionAndSeries(c, "raring") 1261 _, err := coretesting.RunCommand( 1262 c, s.newBootstrapCommand(), "ctrl", "dummy", 1263 "--config", "autocert-dns-name=foo.example", 1264 ) 1265 c.Assert(err, gc.ErrorMatches, `autocert-dns-name is set but it's not usually possible to obtain official certificates without api-port=443 config; use --force-api-port to override this if you plan on using a port forwarder`) 1266 } 1267 1268 func (s *BootstrapSuite) TestBootstrapAutocertDNSNameOKPort(c *gc.C) { 1269 s.patchVersionAndSeries(c, "raring") 1270 _, err := coretesting.RunCommand( 1271 c, s.newBootstrapCommand(), "ctrl", "dummy", 1272 "--config", "autocert-dns-name=foo.example", 1273 "--config", "api-port=443", 1274 ) 1275 c.Assert(err, jc.ErrorIsNil) 1276 } 1277 1278 func (s *BootstrapSuite) TestBootstrapAutocertDNSNameForceAPIPort(c *gc.C) { 1279 s.patchVersionAndSeries(c, "raring") 1280 _, err := coretesting.RunCommand( 1281 c, s.newBootstrapCommand(), "ctrl", "dummy", 1282 "--config", "autocert-dns-name=foo.example", 1283 "--force-api-port", 1284 ) 1285 c.Assert(err, jc.ErrorIsNil) 1286 } 1287 1288 func (s *BootstrapSuite) TestBootstrapCloudConfigAndAdHoc(c *gc.C) { 1289 s.patchVersionAndSeries(c, "raring") 1290 _, err := coretesting.RunCommand( 1291 c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-with-config", 1292 "--auto-upgrade", 1293 // Configuration specified on the command line overrides 1294 // anything specified in files, no matter what the order. 1295 "--config", "controller=not-a-bool", 1296 ) 1297 c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got .*`) 1298 } 1299 1300 func (s *BootstrapSuite) TestBootstrapPrintClouds(c *gc.C) { 1301 resetJujuXDGDataHome(c) 1302 s.store.Credentials = map[string]cloud.CloudCredential{ 1303 "aws": { 1304 DefaultRegion: "us-west-1", 1305 AuthCredentials: map[string]cloud.Credential{ 1306 "fred": {}, 1307 "mary": {}, 1308 }, 1309 }, 1310 "dummy-cloud": { 1311 DefaultRegion: "home", 1312 AuthCredentials: map[string]cloud.Credential{ 1313 "joe": {}, 1314 }, 1315 }, 1316 } 1317 defer func() { 1318 s.store = jujuclienttesting.NewMemStore() 1319 }() 1320 1321 ctx, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "--clouds") 1322 c.Assert(err, jc.ErrorIsNil) 1323 c.Assert(coretesting.Stdout(ctx), jc.DeepEquals, ` 1324 You can bootstrap on these clouds. See ‘--regions <cloud>’ for all regions. 1325 Cloud Credentials Default Region 1326 aws fred us-west-1 1327 mary 1328 aws-china 1329 aws-gov 1330 azure 1331 azure-china 1332 cloudsigma 1333 google 1334 joyent 1335 rackspace 1336 localhost 1337 dummy-cloud joe home 1338 dummy-cloud-with-config 1339 dummy-cloud-without-regions 1340 many-credentials-no-auth-types 1341 1342 You will need to have a credential if you want to bootstrap on a cloud, see 1343 ‘juju autoload-credentials’ and ‘juju add-credential’. The first credential 1344 listed is the default. Add more clouds with ‘juju add-cloud’. 1345 `[1:]) 1346 } 1347 1348 func (s *BootstrapSuite) TestBootstrapPrintCloudRegions(c *gc.C) { 1349 resetJujuXDGDataHome(c) 1350 ctx, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "--regions", "aws") 1351 c.Assert(err, jc.ErrorIsNil) 1352 c.Assert(coretesting.Stdout(ctx), jc.DeepEquals, ` 1353 Showing regions for aws: 1354 us-east-1 1355 us-west-1 1356 us-west-2 1357 eu-west-1 1358 eu-central-1 1359 ap-southeast-1 1360 ap-southeast-2 1361 ap-northeast-1 1362 ap-northeast-2 1363 sa-east-1 1364 `[1:]) 1365 } 1366 1367 func (s *BootstrapSuite) TestBootstrapPrintCloudRegionsNoSuchCloud(c *gc.C) { 1368 resetJujuXDGDataHome(c) 1369 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "--regions", "foo") 1370 c.Assert(err, gc.ErrorMatches, "cloud foo not found") 1371 } 1372 1373 func (s *BootstrapSuite) TestBootstrapSetsControllerOnBase(c *gc.C) { 1374 // This test ensures that the controller name is correctly set on 1375 // on the bootstrap commands embedded ModelCommandBase. Without 1376 // this, the concurrent bootstraps fail. 1377 // See https://pad.lv/1604223 1378 1379 resetJujuXDGDataHome(c) 1380 s.patchVersionAndSeries(c, "raring") 1381 1382 const controllerName = "dev" 1383 1384 // Record the controller name seen by ModelCommandBase at the end of bootstrap. 1385 var seenControllerName string 1386 s.PatchValue(&waitForAgentInitialisation, func(_ *cmd.Context, base *modelcmd.ModelCommandBase, _, _ string) error { 1387 seenControllerName = base.ControllerName() 1388 return nil 1389 }) 1390 1391 // Run the bootstrap command in another goroutine, sending the 1392 // dummy provider ops to opc. 1393 errc := make(chan error, 1) 1394 opc := make(chan dummy.Operation) 1395 dummy.Listen(opc) 1396 go func() { 1397 defer func() { 1398 dummy.Listen(nil) 1399 close(opc) 1400 }() 1401 com := s.newBootstrapCommand() 1402 args := []string{controllerName, "dummy", "--auto-upgrade"} 1403 if err := coretesting.InitCommand(com, args); err != nil { 1404 errc <- err 1405 return 1406 } 1407 errc <- com.Run(cmdtesting.NullContext(c)) 1408 }() 1409 1410 // Wait for bootstrap to start. 1411 select { 1412 case op := <-opc: 1413 _, ok := op.(dummy.OpBootstrap) 1414 c.Assert(ok, jc.IsTrue) 1415 case <-time.After(coretesting.LongWait): 1416 c.Fatal("timed out") 1417 } 1418 1419 // Simulate another controller being bootstrapped during the 1420 // bootstrap. Changing the current controller shouldn't affect the 1421 // bootstrap process. 1422 c.Assert(s.store.AddController("another", jujuclient.ControllerDetails{ 1423 ControllerUUID: "uuid", 1424 CACert: "cert", 1425 }), jc.ErrorIsNil) 1426 c.Assert(s.store.SetCurrentController("another"), jc.ErrorIsNil) 1427 1428 // Let bootstrap finish. 1429 select { 1430 case op := <-opc: 1431 _, ok := op.(dummy.OpFinalizeBootstrap) 1432 c.Assert(ok, jc.IsTrue) 1433 case <-time.After(coretesting.LongWait): 1434 c.Fatal("timed out") 1435 } 1436 1437 // Ensure there were no errors reported. 1438 select { 1439 case err := <-errc: 1440 c.Assert(err, jc.ErrorIsNil) 1441 case <-time.After(coretesting.LongWait): 1442 c.Fatal("timed out") 1443 } 1444 1445 // Wait for the ops channel to close. 1446 select { 1447 case _, ok := <-opc: 1448 c.Assert(ok, jc.IsFalse) 1449 case <-time.After(coretesting.LongWait): 1450 c.Fatal("timed out") 1451 } 1452 1453 // Expect to see that the correct controller was in use at the end 1454 // of bootstrap. 1455 c.Assert(seenControllerName, gc.Equals, controllerName) 1456 } 1457 1458 // createToolsSource writes the mock tools and metadata into a temporary 1459 // directory and returns it. 1460 func createToolsSource(c *gc.C, versions []version.Binary) string { 1461 versionStrings := make([]string, len(versions)) 1462 for i, vers := range versions { 1463 versionStrings[i] = vers.String() 1464 } 1465 source := c.MkDir() 1466 toolstesting.MakeTools(c, source, "released", versionStrings) 1467 return source 1468 } 1469 1470 // resetJujuXDGDataHome restores an new, clean Juju home environment without tools. 1471 func resetJujuXDGDataHome(c *gc.C) { 1472 cloudsPath := cloud.JujuPersonalCloudsPath() 1473 err := ioutil.WriteFile(cloudsPath, []byte(` 1474 clouds: 1475 dummy-cloud: 1476 type: dummy 1477 regions: 1478 region-1: 1479 region-2: 1480 dummy-cloud-without-regions: 1481 type: dummy 1482 dummy-cloud-with-config: 1483 type: dummy 1484 config: 1485 broken: Bootstrap 1486 controller: not-a-bool 1487 many-credentials-no-auth-types: 1488 type: many-credentials 1489 `[1:]), 0644) 1490 c.Assert(err, jc.ErrorIsNil) 1491 } 1492 1493 // checkTools check if the environment contains the passed envtools. 1494 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 1495 list, err := envtools.FindTools( 1496 env, jujuversion.Current.Major, jujuversion.Current.Minor, "released", coretools.Filter{}) 1497 c.Check(err, jc.ErrorIsNil) 1498 c.Logf("found: " + list.String()) 1499 urls := list.URLs() 1500 c.Check(urls, gc.HasLen, len(expected)) 1501 } 1502 1503 var ( 1504 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 1505 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 1506 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 1507 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 1508 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 1509 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 1510 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 1511 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 1512 v120t32 = version.MustParseBinary("1.2.0-trusty-i386") 1513 v120t64 = version.MustParseBinary("1.2.0-trusty-amd64") 1514 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 1515 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 1516 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 1517 v100All = []version.Binary{ 1518 v100d64, v100p64, v100q64, v100q32, 1519 } 1520 v120All = []version.Binary{ 1521 v120d64, v120p64, v120q64, v120q32, v120t32, v120t64, 1522 } 1523 v190All = []version.Binary{ 1524 v190p32, v190q64, 1525 } 1526 v200All = []version.Binary{ 1527 v200p64, 1528 } 1529 vAll = joinBinaryVersions(v100All, v120All, v190All, v200All) 1530 ) 1531 1532 func joinBinaryVersions(versions ...[]version.Binary) []version.Binary { 1533 var all []version.Binary 1534 for _, versions := range versions { 1535 all = append(all, versions...) 1536 } 1537 return all 1538 } 1539 1540 // TODO(menn0): This fake BootstrapInterface implementation is 1541 // currently quite minimal but could be easily extended to cover more 1542 // test scenarios. This could help improve some of the tests in this 1543 // file which execute large amounts of external functionality. 1544 type fakeBootstrapFuncs struct { 1545 args bootstrap.BootstrapParams 1546 cloudRegionDetector environs.CloudRegionDetector 1547 } 1548 1549 func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error { 1550 fake.args = args 1551 return nil 1552 } 1553 1554 func (fake *fakeBootstrapFuncs) CloudRegionDetector(environs.EnvironProvider) (environs.CloudRegionDetector, bool) { 1555 detector := fake.cloudRegionDetector 1556 if detector == nil { 1557 detector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) { 1558 return nil, errors.NotFoundf("regions") 1559 }) 1560 } 1561 return detector, true 1562 } 1563 1564 type noCloudRegionDetectionProvider struct { 1565 environs.EnvironProvider 1566 } 1567 1568 type noCloudRegionsProvider struct { 1569 environs.EnvironProvider 1570 } 1571 1572 func (noCloudRegionsProvider) DetectRegions() ([]cloud.Region, error) { 1573 return nil, errors.NotFoundf("regions") 1574 } 1575 1576 func (noCloudRegionsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 1577 return nil 1578 } 1579 1580 type noCredentialsProvider struct { 1581 environs.EnvironProvider 1582 } 1583 1584 func (noCredentialsProvider) DetectRegions() ([]cloud.Region, error) { 1585 return []cloud.Region{{Name: "region"}}, nil 1586 } 1587 1588 func (noCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) { 1589 return nil, errors.NotFoundf("credentials") 1590 } 1591 1592 func (noCredentialsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 1593 return nil 1594 } 1595 1596 type manyCredentialsProvider struct { 1597 environs.EnvironProvider 1598 } 1599 1600 func (manyCredentialsProvider) DetectRegions() ([]cloud.Region, error) { 1601 return []cloud.Region{{Name: "region"}}, nil 1602 } 1603 1604 func (manyCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) { 1605 return &cloud.CloudCredential{ 1606 AuthCredentials: map[string]cloud.Credential{ 1607 "one": cloud.NewCredential("one", nil), 1608 "two": {}, 1609 }, 1610 }, nil 1611 } 1612 1613 func (manyCredentialsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 1614 return map[cloud.AuthType]cloud.CredentialSchema{"one": {}, "two": {}} 1615 } 1616 1617 type cloudRegionDetectorFunc func() ([]cloud.Region, error) 1618 1619 func (c cloudRegionDetectorFunc) DetectRegions() ([]cloud.Region, error) { 1620 return c() 1621 }