github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "time" 15 16 "github.com/juju/cmd" 17 "github.com/juju/errors" 18 "github.com/juju/testing" 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/utils" 21 "github.com/juju/utils/arch" 22 jujuos "github.com/juju/utils/os" 23 "github.com/juju/utils/series" 24 "github.com/juju/version" 25 gc "gopkg.in/check.v1" 26 27 "github.com/juju/juju/apiserver/params" 28 "github.com/juju/juju/cloud" 29 "github.com/juju/juju/cmd/juju/block" 30 "github.com/juju/juju/cmd/modelcmd" 31 cmdtesting "github.com/juju/juju/cmd/testing" 32 "github.com/juju/juju/constraints" 33 "github.com/juju/juju/environs" 34 "github.com/juju/juju/environs/bootstrap" 35 "github.com/juju/juju/environs/config" 36 "github.com/juju/juju/environs/filestorage" 37 "github.com/juju/juju/environs/gui" 38 "github.com/juju/juju/environs/imagemetadata" 39 "github.com/juju/juju/environs/simplestreams" 40 sstesting "github.com/juju/juju/environs/simplestreams/testing" 41 "github.com/juju/juju/environs/sync" 42 envtesting "github.com/juju/juju/environs/testing" 43 envtools "github.com/juju/juju/environs/tools" 44 toolstesting "github.com/juju/juju/environs/tools/testing" 45 "github.com/juju/juju/instance" 46 "github.com/juju/juju/juju" 47 "github.com/juju/juju/juju/osenv" 48 "github.com/juju/juju/jujuclient" 49 "github.com/juju/juju/jujuclient/jujuclienttesting" 50 "github.com/juju/juju/network" 51 "github.com/juju/juju/provider/dummy" 52 "github.com/juju/juju/rpc" 53 coretesting "github.com/juju/juju/testing" 54 coretools "github.com/juju/juju/tools" 55 jujuversion "github.com/juju/juju/version" 56 ) 57 58 type BootstrapSuite struct { 59 coretesting.FakeJujuXDGDataHomeSuite 60 testing.MgoSuite 61 envtesting.ToolsFixture 62 mockBlockClient *mockBlockClient 63 store *jujuclienttesting.MemStore 64 } 65 66 var _ = gc.Suite(&BootstrapSuite{}) 67 68 func init() { 69 dummyProvider, err := environs.Provider("dummy") 70 if err != nil { 71 panic(err) 72 } 73 environs.RegisterProvider("no-cloud-region-detection", noCloudRegionDetectionProvider{}) 74 environs.RegisterProvider("no-cloud-regions", noCloudRegionsProvider{dummyProvider}) 75 environs.RegisterProvider("no-credentials", noCredentialsProvider{}) 76 environs.RegisterProvider("many-credentials", manyCredentialsProvider{}) 77 } 78 79 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 80 s.FakeJujuXDGDataHomeSuite.SetUpSuite(c) 81 s.MgoSuite.SetUpSuite(c) 82 s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) 83 } 84 85 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 86 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 87 s.MgoSuite.SetUpTest(c) 88 s.ToolsFixture.SetUpTest(c) 89 90 // Set jujuversion.Current to a known value, for which we 91 // will make tools available. Individual tests may 92 // override this. 93 s.PatchValue(&jujuversion.Current, v100p64.Number) 94 s.PatchValue(&arch.HostArch, func() string { return v100p64.Arch }) 95 s.PatchValue(&series.HostSeries, func() string { return v100p64.Series }) 96 s.PatchValue(&jujuos.HostOS, func() jujuos.OSType { return jujuos.Ubuntu }) 97 98 // Set up a local source with tools. 99 sourceDir := createToolsSource(c, vAll) 100 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 101 102 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 103 104 s.mockBlockClient = &mockBlockClient{} 105 s.PatchValue(&blockAPI, func(c *modelcmd.ModelCommandBase) (block.BlockListAPI, error) { 106 err := s.mockBlockClient.loginError 107 if err != nil { 108 s.mockBlockClient.loginError = nil 109 return nil, err 110 } 111 if s.mockBlockClient.discoveringSpacesError > 0 { 112 s.mockBlockClient.discoveringSpacesError -= 1 113 return nil, errors.New("spaces are still being discovered") 114 } 115 return s.mockBlockClient, nil 116 }) 117 118 // TODO(wallyworld) - add test data when tests are improved 119 s.store = jujuclienttesting.NewMemStore() 120 } 121 122 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 123 s.MgoSuite.TearDownSuite(c) 124 s.FakeJujuXDGDataHomeSuite.TearDownSuite(c) 125 } 126 127 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 128 s.ToolsFixture.TearDownTest(c) 129 s.MgoSuite.TearDownTest(c) 130 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 131 dummy.Reset(c) 132 } 133 134 func (s *BootstrapSuite) newBootstrapCommand() cmd.Command { 135 c := &bootstrapCommand{} 136 c.SetClientStore(s.store) 137 return modelcmd.Wrap(c) 138 } 139 140 type mockBlockClient struct { 141 retryCount int 142 numRetries int 143 discoveringSpacesError int 144 loginError error 145 } 146 147 var errOther = errors.New("other error") 148 149 func (c *mockBlockClient) List() ([]params.Block, error) { 150 c.retryCount += 1 151 if c.retryCount == 5 { 152 return nil, &rpc.RequestError{Message: params.CodeUpgradeInProgress, Code: params.CodeUpgradeInProgress} 153 } 154 if c.numRetries < 0 { 155 return nil, errOther 156 } 157 if c.retryCount < c.numRetries { 158 return nil, &rpc.RequestError{Message: params.CodeUpgradeInProgress, Code: params.CodeUpgradeInProgress} 159 } 160 return []params.Block{}, nil 161 } 162 163 func (c *mockBlockClient) Close() error { 164 return nil 165 } 166 167 func (s *BootstrapSuite) TestBootstrapAPIReadyRetries(c *gc.C) { 168 s.PatchValue(&bootstrapReadyPollDelay, 1*time.Millisecond) 169 s.PatchValue(&bootstrapReadyPollCount, 5) 170 defaultSeriesVersion := jujuversion.Current 171 // Force a dev version by having a non zero build number. 172 // This is because we have not uploaded any tools and auto 173 // upload is only enabled for dev versions. 174 defaultSeriesVersion.Build = 1234 175 s.PatchValue(&jujuversion.Current, defaultSeriesVersion) 176 for _, t := range []struct { 177 numRetries int 178 err error 179 }{ 180 {0, nil}, // agent ready immediately 181 {2, nil}, // agent ready after 2 polls 182 {6, &rpc.RequestError{ 183 Message: params.CodeUpgradeInProgress, 184 Code: params.CodeUpgradeInProgress, 185 }}, // agent ready after 6 polls but that's too long 186 {-1, errOther}, // another error is returned 187 } { 188 resetJujuXDGDataHome(c) 189 dummy.Reset(c) 190 s.store = jujuclienttesting.NewMemStore() 191 192 s.mockBlockClient.numRetries = t.numRetries 193 s.mockBlockClient.retryCount = 0 194 _, err := coretesting.RunCommand( 195 c, s.newBootstrapCommand(), 196 "devcontroller", "dummy", "--auto-upgrade", 197 ) 198 c.Check(errors.Cause(err), gc.DeepEquals, t.err) 199 expectedRetries := t.numRetries 200 if t.numRetries <= 0 { 201 expectedRetries = 1 202 } 203 // Only retry maximum of bootstrapReadyPollCount times. 204 if expectedRetries > 5 { 205 expectedRetries = 5 206 } 207 c.Check(s.mockBlockClient.retryCount, gc.Equals, expectedRetries) 208 } 209 } 210 211 func (s *BootstrapSuite) TestBootstrapAPIReadyWaitsForSpaceDiscovery(c *gc.C) { 212 s.PatchValue(&bootstrapReadyPollDelay, 1*time.Millisecond) 213 s.PatchValue(&bootstrapReadyPollCount, 5) 214 defaultSeriesVersion := jujuversion.Current 215 // Force a dev version by having a non zero build number. 216 // This is because we have not uploaded any tools and auto 217 // upload is only enabled for dev versions. 218 defaultSeriesVersion.Build = 1234 219 s.PatchValue(&jujuversion.Current, defaultSeriesVersion) 220 resetJujuXDGDataHome(c) 221 222 s.mockBlockClient.discoveringSpacesError = 2 223 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 224 c.Assert(err, jc.ErrorIsNil) 225 c.Assert(s.mockBlockClient.discoveringSpacesError, gc.Equals, 0) 226 } 227 228 func (s *BootstrapSuite) TestBootstrapAPIReadyRetriesWithOpenEOFErr(c *gc.C) { 229 s.PatchValue(&bootstrapReadyPollDelay, 1*time.Millisecond) 230 s.PatchValue(&bootstrapReadyPollCount, 5) 231 defaultSeriesVersion := jujuversion.Current 232 // Force a dev version by having a non zero build number. 233 // This is because we have not uploaded any tools and auto 234 // upload is only enabled for dev versions. 235 defaultSeriesVersion.Build = 1234 236 s.PatchValue(&jujuversion.Current, defaultSeriesVersion) 237 resetJujuXDGDataHome(c) 238 239 s.mockBlockClient.numRetries = 0 240 s.mockBlockClient.retryCount = 0 241 s.mockBlockClient.loginError = io.EOF 242 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 243 c.Check(err, jc.ErrorIsNil) 244 245 c.Check(s.mockBlockClient.retryCount, gc.Equals, 1) 246 } 247 248 func (s *BootstrapSuite) TestBootstrapAPIReadyStopsRetriesWithOpenErr(c *gc.C) { 249 s.PatchValue(&bootstrapReadyPollDelay, 1*time.Millisecond) 250 s.PatchValue(&bootstrapReadyPollCount, 5) 251 defaultSeriesVersion := jujuversion.Current 252 // Force a dev version by having a non zero build number. 253 // This is because we have not uploaded any tools and auto 254 // upload is only enabled for dev versions. 255 defaultSeriesVersion.Build = 1234 256 s.PatchValue(&jujuversion.Current, defaultSeriesVersion) 257 258 resetJujuXDGDataHome(c) 259 260 s.mockBlockClient.numRetries = 0 261 s.mockBlockClient.retryCount = 0 262 s.mockBlockClient.loginError = errors.NewUnauthorized(nil, "") 263 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 264 c.Check(err, jc.Satisfies, errors.IsUnauthorized) 265 266 c.Check(s.mockBlockClient.retryCount, gc.Equals, 0) 267 } 268 269 func (s *BootstrapSuite) TestRunTests(c *gc.C) { 270 for i, test := range bootstrapTests { 271 c.Logf("\ntest %d: %s", i, test.info) 272 restore := s.run(c, test) 273 restore() 274 } 275 } 276 277 type bootstrapTest struct { 278 info string 279 // binary version string used to set jujuversion.Current 280 version string 281 sync bool 282 args []string 283 err string 284 // binary version string for expected tools; if set, no default tools 285 // will be uploaded before running the test. 286 upload string 287 constraints constraints.Value 288 bootstrapConstraints constraints.Value 289 placement string 290 hostArch string 291 keepBroken bool 292 } 293 294 func (s *BootstrapSuite) patchVersionAndSeries(c *gc.C, hostSeries string) { 295 resetJujuXDGDataHome(c) 296 s.PatchValue(&series.HostSeries, func() string { return hostSeries }) 297 s.patchVersion(c) 298 } 299 300 func (s *BootstrapSuite) patchVersion(c *gc.C) { 301 // Force a dev version by having a non zero build number. 302 // This is because we have not uploaded any tools and auto 303 // upload is only enabled for dev versions. 304 num := jujuversion.Current 305 num.Build = 1234 306 s.PatchValue(&jujuversion.Current, num) 307 } 308 309 func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) testing.Restorer { 310 // Create home with dummy provider and remove all 311 // of its envtools. 312 resetJujuXDGDataHome(c) 313 dummy.Reset(c) 314 315 var restore testing.Restorer = func() { 316 s.store = jujuclienttesting.NewMemStore() 317 } 318 if test.version != "" { 319 useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1) 320 v := version.MustParseBinary(useVersion) 321 restore = restore.Add(testing.PatchValue(&jujuversion.Current, v.Number)) 322 restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return v.Arch })) 323 restore = restore.Add(testing.PatchValue(&series.HostSeries, func() string { return v.Series })) 324 } 325 326 if test.hostArch != "" { 327 restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return test.hostArch })) 328 } 329 330 controllerName := "peckham-controller" 331 cloudName := "dummy" 332 333 // Run command and check for uploads. 334 args := append([]string{ 335 controllerName, cloudName, 336 "--config", "default-series=raring", 337 }, test.args...) 338 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), s.newBootstrapCommand(), args...) 339 // Check for remaining operations/errors. 340 if test.err != "" { 341 err := <-errc 342 c.Assert(err, gc.NotNil) 343 stripped := strings.Replace(err.Error(), "\n", "", -1) 344 c.Check(stripped, gc.Matches, test.err) 345 return restore 346 } 347 if !c.Check(<-errc, gc.IsNil) { 348 return restore 349 } 350 351 opBootstrap := (<-opc).(dummy.OpBootstrap) 352 c.Check(opBootstrap.Env, gc.Equals, "admin") 353 c.Check(opBootstrap.Args.ModelConstraints, gc.DeepEquals, test.constraints) 354 if test.bootstrapConstraints == (constraints.Value{}) { 355 test.bootstrapConstraints = test.constraints 356 } 357 c.Check(opBootstrap.Args.BootstrapConstraints, gc.DeepEquals, test.bootstrapConstraints) 358 c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement) 359 360 opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap) 361 c.Check(opFinalizeBootstrap.Env, gc.Equals, "admin") 362 c.Check(opFinalizeBootstrap.InstanceConfig.ToolsList(), gc.Not(gc.HasLen), 0) 363 if test.upload != "" { 364 c.Check(opFinalizeBootstrap.InstanceConfig.AgentVersion().String(), gc.Equals, test.upload) 365 } 366 367 expectedBootstrappedControllerName := bootstrappedControllerName(controllerName) 368 369 // Check controllers.yaml controller details. 370 addrConnectedTo := []string{"localhost:17070"} 371 372 controller, err := s.store.ControllerByName(expectedBootstrappedControllerName) 373 c.Assert(err, jc.ErrorIsNil) 374 c.Assert(controller.CACert, gc.Not(gc.Equals), "") 375 c.Assert(controller.UnresolvedAPIEndpoints, gc.DeepEquals, addrConnectedTo) 376 c.Assert(controller.APIEndpoints, gc.DeepEquals, addrConnectedTo) 377 c.Assert(utils.IsValidUUIDString(controller.ControllerUUID), jc.IsTrue) 378 379 // Controller model should be called "admin". 380 controllerModel, err := s.store.ModelByName(expectedBootstrappedControllerName, "admin@local", "admin") 381 c.Assert(controllerModel.ModelUUID, gc.Equals, controller.ControllerUUID) 382 c.Assert(err, jc.ErrorIsNil) 383 384 // Bootstrap config should have been saved, and should only contain 385 // the type, name, and any user-supplied configuration. 386 bootstrapConfig, err := s.store.BootstrapConfigForController(expectedBootstrappedControllerName) 387 c.Assert(err, jc.ErrorIsNil) 388 c.Assert(bootstrapConfig.Cloud, gc.Equals, "dummy") 389 c.Assert(bootstrapConfig.Credential, gc.Equals, "") 390 c.Assert(bootstrapConfig.Config, jc.DeepEquals, map[string]interface{}{ 391 "name": "admin", 392 "type": "dummy", 393 "default-series": "raring", 394 }) 395 396 return restore 397 } 398 399 var bootstrapTests = []bootstrapTest{{ 400 info: "no args, no error, no upload, no constraints", 401 }, { 402 info: "bad --constraints", 403 args: []string{"--constraints", "bad=wrong"}, 404 err: `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`, 405 }, { 406 info: "conflicting --constraints", 407 args: []string{"--constraints", "instance-type=foo mem=4G"}, 408 err: `ambiguous constraints: "instance-type" overlaps with "mem"`, 409 }, { 410 info: "bad model", 411 version: "1.2.3-%LTS%-amd64", 412 args: []string{"--config", "broken=Bootstrap Destroy", "--auto-upgrade"}, 413 err: `failed to bootstrap model: dummy.Bootstrap is broken`, 414 }, { 415 info: "constraints", 416 args: []string{"--constraints", "mem=4G cpu-cores=4"}, 417 constraints: constraints.MustParse("mem=4G cpu-cores=4"), 418 }, { 419 info: "bootstrap and environ constraints", 420 args: []string{"--constraints", "mem=4G cpu-cores=4", "--bootstrap-constraints", "mem=8G"}, 421 constraints: constraints.MustParse("mem=4G cpu-cores=4"), 422 bootstrapConstraints: constraints.MustParse("mem=8G cpu-cores=4"), 423 }, { 424 info: "unsupported constraint passed through but no error", 425 args: []string{"--constraints", "mem=4G cpu-cores=4 cpu-power=10"}, 426 constraints: constraints.MustParse("mem=4G cpu-cores=4 cpu-power=10"), 427 }, { 428 info: "--upload-tools uses arch from constraint if it matches current version", 429 version: "1.3.3-saucy-ppc64el", 430 hostArch: "ppc64el", 431 args: []string{"--upload-tools", "--constraints", "arch=ppc64el"}, 432 upload: "1.3.3.1-raring-ppc64el", // from jujuversion.Current 433 constraints: constraints.MustParse("arch=ppc64el"), 434 }, { 435 info: "--upload-tools rejects mismatched arch", 436 version: "1.3.3-saucy-amd64", 437 hostArch: "amd64", 438 args: []string{"--upload-tools", "--constraints", "arch=ppc64el"}, 439 err: `failed to bootstrap model: cannot build tools for "ppc64el" using a machine running on "amd64"`, 440 }, { 441 info: "--upload-tools rejects non-supported arch", 442 version: "1.3.3-saucy-mips64", 443 hostArch: "mips64", 444 args: []string{"--upload-tools"}, 445 err: `failed to bootstrap model: model "admin" of type dummy does not support instances running on "mips64"`, 446 }, { 447 info: "--upload-tools always bumps build number", 448 version: "1.2.3.4-raring-amd64", 449 hostArch: "amd64", 450 args: []string{"--upload-tools"}, 451 upload: "1.2.3.5-raring-amd64", 452 }, { 453 info: "placement", 454 args: []string{"--to", "something"}, 455 placement: "something", 456 }, { 457 info: "keep broken", 458 args: []string{"--keep-broken"}, 459 keepBroken: true, 460 }, { 461 info: "additional args", 462 args: []string{"anything", "else"}, 463 err: `unrecognized args: \["anything" "else"\]`, 464 }, { 465 info: "--agent-version with --upload-tools", 466 args: []string{"--agent-version", "1.1.0", "--upload-tools"}, 467 err: `--agent-version and --upload-tools can't be used together`, 468 }, { 469 info: "invalid --agent-version value", 470 args: []string{"--agent-version", "foo"}, 471 err: `invalid version "foo"`, 472 }, { 473 info: "agent-version doesn't match client version major", 474 version: "1.3.3-saucy-ppc64el", 475 args: []string{"--agent-version", "2.3.0"}, 476 err: `requested agent version major.minor mismatch`, 477 }, { 478 info: "agent-version doesn't match client version minor", 479 version: "1.3.3-saucy-ppc64el", 480 args: []string{"--agent-version", "1.4.0"}, 481 err: `requested agent version major.minor mismatch`, 482 }} 483 484 func (s *BootstrapSuite) TestRunControllerNameMissing(c *gc.C) { 485 _, err := coretesting.RunCommand(c, s.newBootstrapCommand()) 486 c.Check(err, gc.ErrorMatches, "controller name and cloud name are required") 487 } 488 489 func (s *BootstrapSuite) TestRunCloudNameMissing(c *gc.C) { 490 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "my-controller") 491 c.Check(err, gc.ErrorMatches, "controller name and cloud name are required") 492 } 493 494 func (s *BootstrapSuite) TestCheckProviderProvisional(c *gc.C) { 495 err := checkProviderType("devcontroller") 496 c.Assert(err, jc.ErrorIsNil) 497 498 for name, flag := range provisionalProviders { 499 // vsphere is disabled for gccgo. See lp:1440940. 500 if name == "vsphere" && runtime.Compiler == "gccgo" { 501 continue 502 } 503 c.Logf(" - trying %q -", name) 504 err := checkProviderType(name) 505 c.Check(err, gc.ErrorMatches, ".* provider is provisional .* set JUJU_DEV_FEATURE_FLAGS=.*") 506 507 err = os.Setenv(osenv.JujuFeatureFlagEnvKey, flag) 508 c.Assert(err, jc.ErrorIsNil) 509 err = checkProviderType(name) 510 c.Check(err, jc.ErrorIsNil) 511 } 512 } 513 514 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 515 const controllerName = "dev" 516 s.patchVersionAndSeries(c, "raring") 517 518 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade") 519 c.Assert(err, jc.ErrorIsNil) 520 521 _, err = coretesting.RunCommand(c, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade") 522 c.Assert(err, gc.ErrorMatches, `controller "local.dev" already exists`) 523 } 524 525 func (s *BootstrapSuite) TestBootstrapSetsCurrentModel(c *gc.C) { 526 s.patchVersionAndSeries(c, "raring") 527 528 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 529 c.Assert(err, jc.ErrorIsNil) 530 currentController, err := modelcmd.ReadCurrentController() 531 c.Assert(err, jc.ErrorIsNil) 532 c.Assert(currentController, gc.Equals, bootstrappedControllerName("devcontroller")) 533 modelName, err := s.store.CurrentModel(currentController, "admin@local") 534 c.Assert(err, jc.ErrorIsNil) 535 c.Assert(modelName, gc.Equals, "default") 536 } 537 538 func (s *BootstrapSuite) TestBootstrapDefaultModel(c *gc.C) { 539 s.patchVersionAndSeries(c, "raring") 540 541 var bootstrap fakeBootstrapFuncs 542 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 543 return &bootstrap 544 }) 545 546 coretesting.RunCommand( 547 c, s.newBootstrapCommand(), 548 "devcontroller", "dummy", 549 "--auto-upgrade", 550 "--default-model", "mymodel", 551 "--config", "foo=bar", 552 ) 553 c.Assert(bootstrap.args.HostedModelConfig["name"], gc.Equals, "mymodel") 554 c.Assert(bootstrap.args.HostedModelConfig["foo"], gc.Equals, "bar") 555 } 556 557 func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsProcessedAttributes(c *gc.C) { 558 s.patchVersionAndSeries(c, "raring") 559 560 var bootstrap fakeBootstrapFuncs 561 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 562 return &bootstrap 563 }) 564 565 fakeSSHFile := filepath.Join(c.MkDir(), "ssh") 566 err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600) 567 c.Assert(err, jc.ErrorIsNil) 568 coretesting.RunCommand( 569 c, s.newBootstrapCommand(), 570 "devcontroller", "dummy", 571 "--auto-upgrade", 572 "--config", "authorized-keys-path="+fakeSSHFile, 573 ) 574 _, ok := bootstrap.args.HostedModelConfig["authorized-keys-path"] 575 c.Assert(ok, jc.IsFalse) 576 } 577 578 func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsInheritedAttributes(c *gc.C) { 579 s.patchVersionAndSeries(c, "raring") 580 581 var bootstrap fakeBootstrapFuncs 582 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 583 return &bootstrap 584 }) 585 586 fakeSSHFile := filepath.Join(c.MkDir(), "ssh") 587 err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600) 588 c.Assert(err, jc.ErrorIsNil) 589 coretesting.RunCommand( 590 c, s.newBootstrapCommand(), 591 "devcontroller", "dummy", 592 "--auto-upgrade", 593 "--config", "authorized-keys=ssh-key", 594 "--config", "agent-version=1.19.0", 595 ) 596 _, ok := bootstrap.args.HostedModelConfig["authorized-keys"] 597 c.Assert(ok, jc.IsFalse) 598 _, ok = bootstrap.args.HostedModelConfig["agent-version"] 599 c.Assert(ok, jc.IsFalse) 600 } 601 602 func (s *BootstrapSuite) TestBootstrapWithGUI(c *gc.C) { 603 s.patchVersionAndSeries(c, "raring") 604 var bootstrap fakeBootstrapFuncs 605 606 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 607 return &bootstrap 608 }) 609 coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy") 610 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, gui.DefaultBaseURL) 611 } 612 613 func (s *BootstrapSuite) TestBootstrapWithCustomizedGUI(c *gc.C) { 614 s.patchVersionAndSeries(c, "raring") 615 s.PatchEnvironment("JUJU_GUI_SIMPLESTREAMS_URL", "https://1.2.3.4/gui/streams") 616 617 var bootstrap fakeBootstrapFuncs 618 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 619 return &bootstrap 620 }) 621 622 coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy") 623 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "https://1.2.3.4/gui/streams") 624 } 625 626 func (s *BootstrapSuite) TestBootstrapWithoutGUI(c *gc.C) { 627 s.patchVersionAndSeries(c, "raring") 628 var bootstrap fakeBootstrapFuncs 629 630 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 631 return &bootstrap 632 }) 633 coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--no-gui") 634 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "") 635 } 636 637 type mockBootstrapInstance struct { 638 instance.Instance 639 } 640 641 func (*mockBootstrapInstance) Addresses() ([]network.Address, error) { 642 return []network.Address{{Value: "localhost"}}, nil 643 } 644 645 // In the case where we cannot examine the client store, we want the 646 // error to propagate back up to the user. 647 func (s *BootstrapSuite) TestBootstrapPropagatesStoreErrors(c *gc.C) { 648 const controllerName = "devcontroller" 649 bootstrappedControllerName(controllerName) 650 s.patchVersionAndSeries(c, "raring") 651 652 store := jujuclienttesting.NewStubStore() 653 store.SetErrors(errors.New("oh noes")) 654 cmd := &bootstrapCommand{} 655 cmd.SetClientStore(store) 656 _, err := coretesting.RunCommand(c, modelcmd.Wrap(cmd), controllerName, "dummy", "--auto-upgrade") 657 c.Assert(err, gc.ErrorMatches, `loading credentials: oh noes`) 658 } 659 660 // When attempting to bootstrap, check that when prepare errors out, 661 // bootstrap will stop immediately. Nothing will be destroyed. 662 func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) { 663 664 destroyed := false 665 s.PatchValue(&environsDestroy, func(string, environs.Environ, jujuclient.ControllerRemover) error { 666 destroyed = true 667 return nil 668 }) 669 670 s.PatchValue(&environsPrepare, func( 671 environs.BootstrapContext, 672 jujuclient.ClientStore, 673 environs.PrepareParams, 674 ) (environs.Environ, error) { 675 return nil, fmt.Errorf("mock-prepare") 676 }) 677 678 ctx := coretesting.Context(c) 679 _, errc := cmdtesting.RunCommand( 680 ctx, s.newBootstrapCommand(), 681 "devcontroller", "dummy", 682 ) 683 c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$") 684 c.Check(destroyed, jc.IsFalse) 685 } 686 687 func (s *BootstrapSuite) writeControllerModelAccountInfo(c *gc.C, controller, model, account string) { 688 err := s.store.UpdateController(controller, jujuclient.ControllerDetails{ 689 CACert: "x", 690 ControllerUUID: "y", 691 }) 692 c.Assert(err, jc.ErrorIsNil) 693 err = modelcmd.WriteCurrentController(controller) 694 c.Assert(err, jc.ErrorIsNil) 695 err = s.store.UpdateAccount(controller, account, jujuclient.AccountDetails{ 696 User: account, 697 Password: "secret", 698 }) 699 c.Assert(err, jc.ErrorIsNil) 700 err = s.store.SetCurrentAccount(controller, account) 701 c.Assert(err, jc.ErrorIsNil) 702 err = s.store.UpdateModel(controller, account, model, jujuclient.ModelDetails{ 703 ModelUUID: "model-uuid", 704 }) 705 c.Assert(err, jc.ErrorIsNil) 706 err = s.store.SetCurrentModel(controller, account, model) 707 c.Assert(err, jc.ErrorIsNil) 708 } 709 710 func (s *BootstrapSuite) TestBootstrapErrorRestoresOldMetadata(c *gc.C) { 711 s.patchVersionAndSeries(c, "raring") 712 s.PatchValue(&environsPrepare, func( 713 environs.BootstrapContext, 714 jujuclient.ClientStore, 715 environs.PrepareParams, 716 ) (environs.Environ, error) { 717 s.writeControllerModelAccountInfo(c, "foo", "bar", "foobar@local") 718 return nil, fmt.Errorf("mock-prepare") 719 }) 720 721 s.writeControllerModelAccountInfo(c, "local.olddevcontroller", "fredmodel", "fred@local") 722 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") 723 c.Assert(err, gc.ErrorMatches, "mock-prepare") 724 725 oldCurrentController, err := modelcmd.ReadCurrentController() 726 c.Assert(err, jc.ErrorIsNil) 727 c.Assert(oldCurrentController, gc.Equals, bootstrappedControllerName("olddevcontroller")) 728 oldCurrentAccount, err := s.store.CurrentAccount(oldCurrentController) 729 c.Assert(err, jc.ErrorIsNil) 730 c.Assert(oldCurrentAccount, gc.Equals, "fred@local") 731 oldCurrentModel, err := s.store.CurrentModel(oldCurrentController, oldCurrentAccount) 732 c.Assert(err, jc.ErrorIsNil) 733 c.Assert(oldCurrentModel, gc.Equals, "fredmodel") 734 } 735 736 func (s *BootstrapSuite) TestBootstrapAlreadyExists(c *gc.C) { 737 const controllerName = "devcontroller" 738 expectedBootstrappedName := bootstrappedControllerName(controllerName) 739 s.patchVersionAndSeries(c, "raring") 740 741 s.writeControllerModelAccountInfo(c, "local.devcontroller", "fredmodel", "fred@local") 742 743 ctx := coretesting.Context(c) 744 _, errc := cmdtesting.RunCommand(ctx, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade") 745 err := <-errc 746 c.Assert(err, jc.Satisfies, errors.IsAlreadyExists) 747 c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`controller %q already exists`, expectedBootstrappedName)) 748 currentController, err := modelcmd.ReadCurrentController() 749 c.Assert(err, jc.ErrorIsNil) 750 c.Assert(currentController, gc.Equals, "local.devcontroller") 751 currentAccount, err := s.store.CurrentAccount(currentController) 752 c.Assert(err, jc.ErrorIsNil) 753 c.Assert(currentAccount, gc.Equals, "fred@local") 754 currentModel, err := s.store.CurrentModel(currentController, currentAccount) 755 c.Assert(err, jc.ErrorIsNil) 756 c.Assert(currentModel, gc.Equals, "fredmodel") 757 } 758 759 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 760 s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0")) 761 resetJujuXDGDataHome(c) 762 763 // Bootstrap the controller with an invalid source. 764 // The command returns with an error. 765 _, err := coretesting.RunCommand( 766 c, s.newBootstrapCommand(), "--metadata-source", c.MkDir(), 767 "devcontroller", "dummy", 768 ) 769 c.Check(err, gc.ErrorMatches, `failed to bootstrap model: Juju cannot bootstrap because no tools are available for your model(.|\n)*`) 770 } 771 772 // createImageMetadata creates some image metadata in a local directory. 773 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 774 // Generate some image metadata. 775 im := []*imagemetadata.ImageMetadata{ 776 { 777 Id: "1234", 778 Arch: "amd64", 779 Version: "13.04", 780 RegionName: "region", 781 Endpoint: "endpoint", 782 }, 783 } 784 cloudSpec := &simplestreams.CloudSpec{ 785 Region: "region", 786 Endpoint: "endpoint", 787 } 788 sourceDir := c.MkDir() 789 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 790 c.Assert(err, jc.ErrorIsNil) 791 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 792 c.Assert(err, jc.ErrorIsNil) 793 return sourceDir, im 794 } 795 796 func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) { 797 sourceDir, _ := createImageMetadata(c) 798 resetJujuXDGDataHome(c) 799 800 var bootstrap fakeBootstrapFuncs 801 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 802 return &bootstrap 803 }) 804 805 coretesting.RunCommand( 806 c, s.newBootstrapCommand(), 807 "--metadata-source", sourceDir, "--constraints", "mem=4G", 808 "devcontroller", "dummy-cloud/region-1", 809 "--config", "default-series=raring", 810 ) 811 c.Assert(bootstrap.args.MetadataDir, gc.Equals, sourceDir) 812 } 813 814 func (s *BootstrapSuite) checkBootstrapWithVersion(c *gc.C, vers, expect string) { 815 resetJujuXDGDataHome(c) 816 817 var bootstrap fakeBootstrapFuncs 818 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 819 return &bootstrap 820 }) 821 822 num := jujuversion.Current 823 num.Major = 2 824 num.Minor = 3 825 s.PatchValue(&jujuversion.Current, num) 826 coretesting.RunCommand( 827 c, s.newBootstrapCommand(), 828 "--agent-version", vers, 829 "devcontroller", "dummy-cloud/region-1", 830 "--config", "default-series=raring", 831 ) 832 c.Assert(bootstrap.args.AgentVersion, gc.NotNil) 833 c.Assert(*bootstrap.args.AgentVersion, gc.Equals, version.MustParse(expect)) 834 } 835 836 func (s *BootstrapSuite) TestBootstrapWithVersionNumber(c *gc.C) { 837 s.checkBootstrapWithVersion(c, "2.3.4", "2.3.4") 838 } 839 840 func (s *BootstrapSuite) TestBootstrapWithBinaryVersionNumber(c *gc.C) { 841 s.checkBootstrapWithVersion(c, "2.3.4-trusty-ppc64", "2.3.4") 842 } 843 844 func (s *BootstrapSuite) TestBootstrapWithAutoUpgrade(c *gc.C) { 845 resetJujuXDGDataHome(c) 846 847 var bootstrap fakeBootstrapFuncs 848 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 849 return &bootstrap 850 }) 851 coretesting.RunCommand( 852 c, s.newBootstrapCommand(), 853 "--auto-upgrade", 854 "devcontroller", "dummy-cloud/region-1", 855 ) 856 c.Assert(bootstrap.args.AgentVersion, gc.IsNil) 857 } 858 859 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 860 sourceDir := createToolsSource(c, vAll) 861 s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0")) 862 resetJujuXDGDataHome(c) 863 864 // Bootstrap the controller with the valid source. 865 // The bootstrapping has to show no error, because the tools 866 // are automatically synchronized. 867 _, err := coretesting.RunCommand( 868 c, s.newBootstrapCommand(), "--metadata-source", sourceDir, 869 "devcontroller", "dummy-cloud/region-1", 870 ) 871 c.Assert(err, jc.ErrorIsNil) 872 873 p, err := environs.Provider("dummy") 874 c.Assert(err, jc.ErrorIsNil) 875 cfg, err := modelcmd.NewGetBootstrapConfigFunc(s.store)("devcontroller") 876 c.Assert(err, jc.ErrorIsNil) 877 env, err := p.PrepareForBootstrap(envtesting.BootstrapContext(c), cfg) 878 c.Assert(err, jc.ErrorIsNil) 879 880 // Now check the available tools which are the 1.2.0 envtools. 881 checkTools(c, env, v120All) 882 } 883 884 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, ser string) { 885 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c)) 886 sourceDir := createToolsSource(c, vAll) 887 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 888 889 // Change the tools location to be the test location and also 890 // the version and ensure their later restoring. 891 // Set the current version to be something for which there are no tools 892 // so we can test that an upload is forced. 893 s.PatchValue(&jujuversion.Current, version.MustParse(vers)) 894 s.PatchValue(&series.HostSeries, func() string { return ser }) 895 896 // Create home with dummy provider and remove all 897 // of its envtools. 898 resetJujuXDGDataHome(c) 899 } 900 901 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 902 s.PatchValue(&series.HostSeries, func() string { return config.LatestLtsSeries() }) 903 s.setupAutoUploadTest(c, "1.7.3", "quantal") 904 // Run command and check for that upload has been run for tools matching 905 // the current juju version. 906 opc, errc := cmdtesting.RunCommand( 907 cmdtesting.NullContext(c), s.newBootstrapCommand(), 908 "devcontroller", "dummy-cloud/region-1", 909 "--config", "default-series=raring", 910 "--auto-upgrade", 911 ) 912 c.Assert(<-errc, gc.IsNil) 913 c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, "admin") 914 icfg := (<-opc).(dummy.OpFinalizeBootstrap).InstanceConfig 915 c.Assert(icfg, gc.NotNil) 916 c.Assert(icfg.AgentVersion().String(), gc.Equals, "1.7.3.1-raring-"+arch.HostArch()) 917 } 918 919 func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) { 920 s.setupAutoUploadTest(c, "1.8.3", "precise") 921 _, errc := cmdtesting.RunCommand( 922 cmdtesting.NullContext(c), s.newBootstrapCommand(), 923 "devcontroller", "dummy-cloud/region-1", 924 ) 925 err := <-errc 926 c.Assert(err, gc.ErrorMatches, 927 "failed to bootstrap model: Juju cannot bootstrap because no tools are available for your model(.|\n)*") 928 } 929 930 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 931 s.setupAutoUploadTest(c, "1.8.3", "precise") 932 933 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), 934 "devcontroller", "dummy-cloud/region-1", 935 "--config", "default-series=raring", 936 ) 937 c.Assert(err, gc.ErrorMatches, 938 "failed to bootstrap model: Juju cannot bootstrap because no tools are available for your model(.|\n)*") 939 } 940 941 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 942 943 buildToolsTarballAlwaysFails := func(forceVersion *version.Number, stream string) (*sync.BuiltTools, error) { 944 return nil, fmt.Errorf("an error") 945 } 946 947 s.setupAutoUploadTest(c, "1.7.3", "precise") 948 s.PatchValue(&sync.BuildToolsTarball, buildToolsTarballAlwaysFails) 949 950 ctx, err := coretesting.RunCommand( 951 c, s.newBootstrapCommand(), 952 "devcontroller", "dummy-cloud/region-1", 953 "--config", "default-series=raring", 954 "--config", "agent-stream=proposed", 955 "--auto-upgrade", 956 ) 957 958 c.Check(coretesting.Stderr(ctx), gc.Equals, fmt.Sprintf(` 959 Creating Juju controller "local.devcontroller" on dummy-cloud/region-1 960 Bootstrapping model "admin" 961 Starting new instance for initial controller 962 Building tools to upload (1.7.3.1-raring-%s) 963 `[1:], arch.HostArch())) 964 c.Check(err, gc.ErrorMatches, "failed to bootstrap model: cannot upload bootstrap tools: an error") 965 } 966 967 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 968 resetJujuXDGDataHome(c) 969 s.patchVersion(c) 970 971 opc, errc := cmdtesting.RunCommand( 972 cmdtesting.NullContext(c), s.newBootstrapCommand(), 973 "devcontroller", "dummy-cloud/region-1", 974 "--config", "broken=Bootstrap Destroy", 975 "--auto-upgrade", 976 ) 977 err := <-errc 978 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 979 var opDestroy *dummy.OpDestroy 980 for opDestroy == nil { 981 select { 982 case op := <-opc: 983 switch op := op.(type) { 984 case dummy.OpDestroy: 985 opDestroy = &op 986 } 987 default: 988 c.Error("expected call to env.Destroy") 989 return 990 } 991 } 992 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 993 } 994 995 func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) { 996 resetJujuXDGDataHome(c) 997 s.patchVersion(c) 998 999 opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), s.newBootstrapCommand(), 1000 "--keep-broken", 1001 "devcontroller", "dummy-cloud/region-1", 1002 "--config", "broken=Bootstrap Destroy", 1003 "--auto-upgrade", 1004 ) 1005 err := <-errc 1006 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 1007 done := false 1008 for !done { 1009 select { 1010 case op, ok := <-opc: 1011 if !ok { 1012 done = true 1013 break 1014 } 1015 switch op.(type) { 1016 case dummy.OpDestroy: 1017 c.Error("unexpected call to env.Destroy") 1018 break 1019 } 1020 default: 1021 break 1022 } 1023 } 1024 } 1025 1026 func (s *BootstrapSuite) TestBootstrapUnknownCloudOrProvider(c *gc.C) { 1027 s.patchVersionAndSeries(c, "raring") 1028 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-such-provider") 1029 c.Assert(err, gc.ErrorMatches, `unknown cloud "no-such-provider", please try "juju update-clouds"`) 1030 } 1031 1032 func (s *BootstrapSuite) TestBootstrapProviderNoRegionDetection(c *gc.C) { 1033 s.patchVersionAndSeries(c, "raring") 1034 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-cloud-region-detection") 1035 c.Assert(err, gc.ErrorMatches, `unknown cloud "no-cloud-region-detection", please try "juju update-clouds"`) 1036 } 1037 1038 func (s *BootstrapSuite) TestBootstrapProviderNoRegions(c *gc.C) { 1039 ctx, err := coretesting.RunCommand( 1040 c, s.newBootstrapCommand(), "ctrl", "no-cloud-regions", 1041 "--config", "default-series=precise", 1042 ) 1043 c.Check(coretesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"local.ctrl\" on no-cloud-regions(.|\n)*") 1044 c.Assert(err, jc.ErrorIsNil) 1045 } 1046 1047 func (s *BootstrapSuite) TestBootstrapCloudNoRegions(c *gc.C) { 1048 resetJujuXDGDataHome(c) 1049 ctx, err := coretesting.RunCommand( 1050 c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-without-regions", 1051 "--config", "default-series=precise", 1052 ) 1053 c.Check(coretesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"local.ctrl\" on dummy-cloud-without-regions(.|\n)*") 1054 c.Assert(err, jc.ErrorIsNil) 1055 } 1056 1057 func (s *BootstrapSuite) TestBootstrapCloudNoRegionsOneSpecified(c *gc.C) { 1058 resetJujuXDGDataHome(c) 1059 ctx, err := coretesting.RunCommand( 1060 c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-without-regions/my-region", 1061 "--config", "default-series=precise", 1062 ) 1063 // If the cloud doesn't have any regions defined, we still allow the 1064 // user to pass a region through. This enables the manual provider to 1065 // take the bootstrap-host from the region name, and later, will 1066 // enable the lxd provider to take the lxd remote from the region 1067 // name. 1068 c.Check(coretesting.Stderr(ctx), gc.Matches, 1069 "Creating Juju controller \"local.ctrl\" on dummy-cloud-without-regions/my-region(.|\n)*") 1070 c.Assert(err, jc.ErrorIsNil) 1071 } 1072 1073 func (s *BootstrapSuite) TestBootstrapProviderNoCredentials(c *gc.C) { 1074 s.patchVersionAndSeries(c, "raring") 1075 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-credentials") 1076 c.Assert(err, gc.ErrorMatches, `detecting credentials for "no-credentials" cloud provider: credentials not found`) 1077 } 1078 1079 func (s *BootstrapSuite) TestBootstrapProviderManyCredentials(c *gc.C) { 1080 s.patchVersionAndSeries(c, "raring") 1081 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "many-credentials") 1082 c.Assert(err, gc.ErrorMatches, ambiguousCredentialError.Error()) 1083 } 1084 1085 func (s *BootstrapSuite) TestBootstrapProviderDetectRegions(c *gc.C) { 1086 s.patchVersionAndSeries(c, "raring") 1087 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy/not-dummy") 1088 c.Assert(err, gc.NotNil) 1089 errMsg := strings.Replace(err.Error(), "\n", "", -1) 1090 c.Assert(errMsg, gc.Matches, `region "not-dummy" in cloud "dummy" not found \(expected one of \["dummy"\]\)alternatively, try "juju update-clouds"`) 1091 } 1092 1093 func (s *BootstrapSuite) TestBootstrapProviderCaseInsensitiveRegionCheck(c *gc.C) { 1094 s.patchVersionAndSeries(c, "raring") 1095 1096 var prepareParams environs.PrepareParams 1097 s.PatchValue(&environsPrepare, func( 1098 ctx environs.BootstrapContext, 1099 stor jujuclient.ClientStore, 1100 params environs.PrepareParams, 1101 ) (environs.Environ, error) { 1102 prepareParams = params 1103 return nil, fmt.Errorf("mock-prepare") 1104 }) 1105 1106 _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy/DUMMY") 1107 c.Assert(err, gc.ErrorMatches, "mock-prepare") 1108 c.Assert(prepareParams.CloudRegion, gc.Equals, "dummy") 1109 } 1110 1111 func (s *BootstrapSuite) TestBootstrapConfigFile(c *gc.C) { 1112 tmpdir := c.MkDir() 1113 configFile := filepath.Join(tmpdir, "config.yaml") 1114 err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644) 1115 c.Assert(err, jc.ErrorIsNil) 1116 1117 s.patchVersionAndSeries(c, "raring") 1118 _, err = coretesting.RunCommand( 1119 c, s.newBootstrapCommand(), "ctrl", "dummy", 1120 "--config", configFile, 1121 ) 1122 c.Assert(err, gc.ErrorMatches, `controller: expected bool, got string.*`) 1123 } 1124 1125 func (s *BootstrapSuite) TestBootstrapMultipleConfigFiles(c *gc.C) { 1126 tmpdir := c.MkDir() 1127 configFile1 := filepath.Join(tmpdir, "config-1.yaml") 1128 err := ioutil.WriteFile(configFile1, []byte( 1129 "controller: not-a-bool\nbroken: Bootstrap\n", 1130 ), 0644) 1131 c.Assert(err, jc.ErrorIsNil) 1132 configFile2 := filepath.Join(tmpdir, "config-2.yaml") 1133 err = ioutil.WriteFile(configFile2, []byte( 1134 "controller: false\n", 1135 ), 0644) 1136 1137 s.patchVersionAndSeries(c, "raring") 1138 _, err = coretesting.RunCommand( 1139 c, s.newBootstrapCommand(), "ctrl", "dummy", 1140 "--auto-upgrade", 1141 // the second config file should replace attributes 1142 // with the same name from the first, but leave the 1143 // others alone. 1144 "--config", configFile1, 1145 "--config", configFile2, 1146 ) 1147 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 1148 } 1149 1150 func (s *BootstrapSuite) TestBootstrapConfigFileAndAdHoc(c *gc.C) { 1151 tmpdir := c.MkDir() 1152 configFile := filepath.Join(tmpdir, "config.yaml") 1153 err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644) 1154 c.Assert(err, jc.ErrorIsNil) 1155 1156 s.patchVersionAndSeries(c, "raring") 1157 _, err = coretesting.RunCommand( 1158 c, s.newBootstrapCommand(), "ctrl", "dummy", 1159 "--auto-upgrade", 1160 // Configuration specified on the command line overrides 1161 // anything specified in files, no matter what the order. 1162 "--config", "controller=false", 1163 "--config", configFile, 1164 ) 1165 c.Assert(err, jc.ErrorIsNil) 1166 } 1167 1168 // createToolsSource writes the mock tools and metadata into a temporary 1169 // directory and returns it. 1170 func createToolsSource(c *gc.C, versions []version.Binary) string { 1171 versionStrings := make([]string, len(versions)) 1172 for i, vers := range versions { 1173 versionStrings[i] = vers.String() 1174 } 1175 source := c.MkDir() 1176 toolstesting.MakeTools(c, source, "released", versionStrings) 1177 return source 1178 } 1179 1180 // resetJujuXDGDataHome restores an new, clean Juju home environment without tools. 1181 func resetJujuXDGDataHome(c *gc.C) { 1182 jenvDir := testing.JujuXDGDataHomePath("models") 1183 err := os.RemoveAll(jenvDir) 1184 c.Assert(err, jc.ErrorIsNil) 1185 1186 cloudsPath := cloud.JujuPersonalCloudsPath() 1187 err = ioutil.WriteFile(cloudsPath, []byte(` 1188 clouds: 1189 dummy-cloud: 1190 type: dummy 1191 regions: 1192 region-1: 1193 region-2: 1194 dummy-cloud-without-regions: 1195 type: dummy 1196 `[1:]), 0644) 1197 c.Assert(err, jc.ErrorIsNil) 1198 } 1199 1200 // checkTools check if the environment contains the passed envtools. 1201 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 1202 list, err := envtools.FindTools( 1203 env, jujuversion.Current.Major, jujuversion.Current.Minor, "released", coretools.Filter{}) 1204 c.Check(err, jc.ErrorIsNil) 1205 c.Logf("found: " + list.String()) 1206 urls := list.URLs() 1207 c.Check(urls, gc.HasLen, len(expected)) 1208 } 1209 1210 var ( 1211 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 1212 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 1213 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 1214 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 1215 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 1216 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 1217 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 1218 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 1219 v120t32 = version.MustParseBinary("1.2.0-trusty-i386") 1220 v120t64 = version.MustParseBinary("1.2.0-trusty-amd64") 1221 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 1222 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 1223 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 1224 v100All = []version.Binary{ 1225 v100d64, v100p64, v100q64, v100q32, 1226 } 1227 v120All = []version.Binary{ 1228 v120d64, v120p64, v120q64, v120q32, v120t32, v120t64, 1229 } 1230 v190All = []version.Binary{ 1231 v190p32, v190q64, 1232 } 1233 v200All = []version.Binary{ 1234 v200p64, 1235 } 1236 vAll = joinBinaryVersions(v100All, v120All, v190All, v200All) 1237 ) 1238 1239 func joinBinaryVersions(versions ...[]version.Binary) []version.Binary { 1240 var all []version.Binary 1241 for _, versions := range versions { 1242 all = append(all, versions...) 1243 } 1244 return all 1245 } 1246 1247 // TODO(menn0): This fake BootstrapInterface implementation is 1248 // currently quite minimal but could be easily extended to cover more 1249 // test scenarios. This could help improve some of the tests in this 1250 // file which execute large amounts of external functionality. 1251 type fakeBootstrapFuncs struct { 1252 args bootstrap.BootstrapParams 1253 } 1254 1255 func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error { 1256 fake.args = args 1257 return nil 1258 } 1259 1260 type noCloudRegionDetectionProvider struct { 1261 environs.EnvironProvider 1262 } 1263 1264 type noCloudRegionsProvider struct { 1265 environs.EnvironProvider 1266 } 1267 1268 func (noCloudRegionsProvider) DetectRegions() ([]cloud.Region, error) { 1269 return nil, errors.NotFoundf("regions") 1270 } 1271 1272 type noCredentialsProvider struct { 1273 environs.EnvironProvider 1274 } 1275 1276 func (noCredentialsProvider) DetectRegions() ([]cloud.Region, error) { 1277 return []cloud.Region{{Name: "region"}}, nil 1278 } 1279 1280 func (noCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) { 1281 return nil, errors.NotFoundf("credentials") 1282 } 1283 1284 type manyCredentialsProvider struct { 1285 environs.EnvironProvider 1286 } 1287 1288 func (manyCredentialsProvider) DetectRegions() ([]cloud.Region, error) { 1289 return []cloud.Region{{Name: "region"}}, nil 1290 } 1291 1292 func (manyCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) { 1293 return &cloud.CloudCredential{ 1294 AuthCredentials: map[string]cloud.Credential{ 1295 "one": {}, "two": {}, 1296 }, 1297 }, nil 1298 }