github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "regexp" 14 "runtime" 15 "sort" 16 "strings" 17 "time" 18 19 "github.com/juju/cmd" 20 "github.com/juju/cmd/cmdtesting" 21 "github.com/juju/errors" 22 "github.com/juju/loggo" 23 jujuos "github.com/juju/os" 24 "github.com/juju/os/series" 25 "github.com/juju/testing" 26 jc "github.com/juju/testing/checkers" 27 "github.com/juju/utils" 28 "github.com/juju/utils/arch" 29 "github.com/juju/version" 30 gc "gopkg.in/check.v1" 31 32 "github.com/juju/juju/cert" 33 "github.com/juju/juju/cloud" 34 "github.com/juju/juju/cmd/cmdtest" 35 "github.com/juju/juju/cmd/modelcmd" 36 "github.com/juju/juju/core/constraints" 37 "github.com/juju/juju/core/model" 38 "github.com/juju/juju/environs" 39 "github.com/juju/juju/environs/bootstrap" 40 "github.com/juju/juju/environs/config" 41 "github.com/juju/juju/environs/context" 42 "github.com/juju/juju/environs/filestorage" 43 "github.com/juju/juju/environs/gui" 44 "github.com/juju/juju/environs/imagemetadata" 45 "github.com/juju/juju/environs/instances" 46 "github.com/juju/juju/environs/simplestreams" 47 sstesting "github.com/juju/juju/environs/simplestreams/testing" 48 "github.com/juju/juju/environs/sync" 49 envtesting "github.com/juju/juju/environs/testing" 50 envtools "github.com/juju/juju/environs/tools" 51 toolstesting "github.com/juju/juju/environs/tools/testing" 52 "github.com/juju/juju/juju/keys" 53 "github.com/juju/juju/juju/osenv" 54 supportedversion "github.com/juju/juju/juju/version" 55 "github.com/juju/juju/jujuclient" 56 "github.com/juju/juju/jujuclient/jujuclienttesting" 57 "github.com/juju/juju/network" 58 "github.com/juju/juju/provider/dummy" 59 "github.com/juju/juju/provider/openstack" 60 coretesting "github.com/juju/juju/testing" 61 coretools "github.com/juju/juju/tools" 62 jujuversion "github.com/juju/juju/version" 63 ) 64 65 type BootstrapSuite struct { 66 coretesting.FakeJujuXDGDataHomeSuite 67 testing.MgoSuite 68 envtesting.ToolsFixture 69 store *jujuclient.MemStore 70 tw loggo.TestWriter 71 } 72 73 var _ = gc.Suite(&BootstrapSuite{}) 74 75 func init() { 76 dummyProvider, err := environs.Provider("dummy") 77 if err != nil { 78 panic(err) 79 } 80 environs.RegisterProvider("no-cloud-region-detection", noCloudRegionDetectionProvider{}) 81 environs.RegisterProvider("no-cloud-regions", noCloudRegionsProvider{ 82 dummyProvider.(environs.CloudEnvironProvider)}) 83 environs.RegisterProvider("no-credentials", noCredentialsProvider{}) 84 environs.RegisterProvider("many-credentials", manyCredentialsProvider{ 85 dummyProvider.(environs.CloudEnvironProvider)}) 86 } 87 88 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 89 s.FakeJujuXDGDataHomeSuite.SetUpSuite(c) 90 s.MgoSuite.SetUpSuite(c) 91 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 92 s.PatchValue(&cert.NewCA, coretesting.NewCA) 93 s.PatchValue(&cert.NewLeafKeyBits, 512) 94 } 95 96 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 97 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 98 s.MgoSuite.SetUpTest(c) 99 s.ToolsFixture.SetUpTest(c) 100 101 // Set jujuversion.Current to a known value, for which we 102 // will make tools available. Individual tests may 103 // override this. 104 s.PatchValue(&jujuversion.Current, v100p64.Number) 105 s.PatchValue(&arch.HostArch, func() string { return v100p64.Arch }) 106 s.PatchValue(&series.MustHostSeries, func() string { return v100p64.Series }) 107 s.PatchValue(&jujuos.HostOS, func() jujuos.OSType { return jujuos.Ubuntu }) 108 109 // Set up a local source with tools. 110 sourceDir := createToolsSource(c, vAll) 111 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 112 113 // NOTE(axw) we cannot patch BundleTools here, as the "gc.C" argument 114 // is invalidated once this method returns. 115 s.PatchValue(&envtools.BundleTools, func(bool, io.Writer, *version.Number) (version.Binary, bool, string, error) { 116 panic("tests must call setupAutoUploadTest or otherwise patch envtools.BundleTools") 117 }) 118 119 s.PatchValue(&waitForAgentInitialisation, func(*cmd.Context, *modelcmd.ModelCommandBase, string, string) error { 120 return nil 121 }) 122 123 // TODO(wallyworld) - add test data when tests are improved 124 s.store = jujuclienttesting.MinimalStore() 125 126 // Write bootstrap command logs to an in-memory buffer, 127 // so we can inspect the output in tests. 128 s.tw.Clear() 129 c.Assert(loggo.RegisterWriter("bootstrap-test", &s.tw), jc.ErrorIsNil) 130 s.AddCleanup(func(c *gc.C) { 131 _, err := loggo.RemoveWriter("bootstrap-test") 132 c.Assert(err, jc.ErrorIsNil) 133 }) 134 } 135 136 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 137 s.MgoSuite.TearDownSuite(c) 138 s.FakeJujuXDGDataHomeSuite.TearDownSuite(c) 139 } 140 141 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 142 s.ToolsFixture.TearDownTest(c) 143 s.MgoSuite.TearDownTest(c) 144 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 145 dummy.Reset(c) 146 } 147 148 // bootstrapCommandWrapper wraps the bootstrap command. The wrapped command has 149 // the ability to disable fetching GUI information from simplestreams, so that 150 // it is possible to test the bootstrap process without connecting to the 151 // network. This ability can be turned on by setting disableGUI to true. 152 type bootstrapCommandWrapper struct { 153 bootstrapCommand 154 disableGUI bool 155 } 156 157 func (c *bootstrapCommandWrapper) Run(ctx *cmd.Context) error { 158 if c.disableGUI { 159 c.bootstrapCommand.noGUI = true 160 } 161 return c.bootstrapCommand.Run(ctx) 162 } 163 164 func (s *BootstrapSuite) newBootstrapCommand() cmd.Command { 165 return s.newBootstrapCommandWrapper(true) 166 } 167 168 func (s *BootstrapSuite) newBootstrapCommandWrapper(disableGUI bool) cmd.Command { 169 c := &bootstrapCommandWrapper{ 170 disableGUI: disableGUI, 171 } 172 c.SetClientStore(s.store) 173 return modelcmd.Wrap(c) 174 } 175 176 func (s *BootstrapSuite) TestRunTests(c *gc.C) { 177 for i, test := range bootstrapTests { 178 c.Logf("\ntest %d: %s", i, test.info) 179 restore := s.run(c, test) 180 restore() 181 } 182 } 183 184 type bootstrapTest struct { 185 info string 186 // binary version string used to set jujuversion.Current 187 version string 188 sync bool 189 args []string 190 err string 191 silentErr bool 192 logs jc.SimpleMessages 193 // binary version string for expected tools; if set, no default tools 194 // will be uploaded before running the test. 195 upload string 196 constraints constraints.Value 197 bootstrapConstraints constraints.Value 198 placement string 199 hostArch string 200 keepBroken bool 201 } 202 203 func (s *BootstrapSuite) patchVersionAndSeries(c *gc.C, hostSeries string) { 204 resetJujuXDGDataHome(c) 205 s.PatchValue(&series.MustHostSeries, func() string { return hostSeries }) 206 s.patchVersion(c) 207 } 208 209 func (s *BootstrapSuite) patchVersion(c *gc.C) { 210 // Force a dev version by having a non zero build number. 211 // This is because we have not uploaded any tools and auto 212 // upload is only enabled for dev versions. 213 num := jujuversion.Current 214 num.Build = 1234 215 s.PatchValue(&jujuversion.Current, num) 216 } 217 218 func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) testing.Restorer { 219 // Create home with dummy provider and remove all 220 // of its envtools. 221 resetJujuXDGDataHome(c) 222 dummy.Reset(c) 223 s.tw.Clear() 224 225 var restore testing.Restorer = func() { 226 s.store = jujuclienttesting.MinimalStore() 227 } 228 bootstrapVersion := v100p64 229 if test.version != "" { 230 useVersion := strings.Replace(test.version, "%LTS%", supportedversion.SupportedLTS(), 1) 231 bootstrapVersion = version.MustParseBinary(useVersion) 232 restore = restore.Add(testing.PatchValue(&jujuversion.Current, bootstrapVersion.Number)) 233 restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return bootstrapVersion.Arch })) 234 restore = restore.Add(testing.PatchValue(&series.MustHostSeries, func() string { return bootstrapVersion.Series })) 235 bootstrapVersion.Build = 1 236 if test.upload != "" { 237 uploadVers := version.MustParseBinary(test.upload) 238 bootstrapVersion.Number = uploadVers.Number 239 } 240 restore = restore.Add(testing.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &bootstrapVersion.Number))) 241 } 242 243 if test.hostArch != "" { 244 restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return test.hostArch })) 245 } 246 247 controllerName := "peckham-controller" 248 cloudName := "dummy" 249 250 // Run command and check for uploads. 251 args := append([]string{ 252 cloudName, controllerName, 253 "--config", "default-series=raring", 254 }, test.args...) 255 opc, errc := cmdtest.RunCommandWithDummyProvider(cmdtesting.Context(c), s.newBootstrapCommand(), args...) 256 var err error 257 select { 258 case err = <-errc: 259 case <-time.After(coretesting.LongWait): 260 c.Fatal("timed out") 261 } 262 c.Check(s.tw.Log(), jc.LogMatches, test.logs) 263 // Check for remaining operations/errors. 264 if test.silentErr { 265 c.Assert(err, gc.Equals, cmd.ErrSilent) 266 return restore 267 } else if test.err != "" { 268 c.Assert(err, gc.NotNil) 269 stripped := strings.Replace(err.Error(), "\n", "", -1) 270 c.Check(stripped, gc.Matches, test.err) 271 return restore 272 } 273 if !c.Check(err, gc.IsNil) { 274 return restore 275 } 276 277 op, ok := <-opc 278 c.Assert(ok, gc.Equals, true) 279 opBootstrap := op.(dummy.OpBootstrap) 280 c.Check(opBootstrap.Env, gc.Equals, bootstrap.ControllerModelName) 281 c.Check(opBootstrap.Args.ModelConstraints, gc.DeepEquals, test.constraints) 282 if test.bootstrapConstraints == (constraints.Value{}) { 283 test.bootstrapConstraints = test.constraints 284 } 285 if test.bootstrapConstraints.Mem == nil { 286 mem := uint64(3584) 287 test.bootstrapConstraints.Mem = &mem 288 } 289 c.Check(opBootstrap.Args.BootstrapConstraints, gc.DeepEquals, test.bootstrapConstraints) 290 c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement) 291 292 opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap) 293 c.Check(opFinalizeBootstrap.Env, gc.Equals, bootstrap.ControllerModelName) 294 c.Check(opFinalizeBootstrap.InstanceConfig.ToolsList(), gc.Not(gc.HasLen), 0) 295 if test.upload != "" { 296 c.Check(opFinalizeBootstrap.InstanceConfig.AgentVersion().String(), gc.Equals, test.upload) 297 } 298 299 // Check controllers.yaml controller details. 300 addrConnectedTo := []string{"localhost:17070"} 301 302 controller, err := s.store.ControllerByName(controllerName) 303 c.Assert(err, jc.ErrorIsNil) 304 c.Assert(controller.CACert, gc.Not(gc.Equals), "") 305 c.Assert(controller.APIEndpoints, gc.DeepEquals, addrConnectedTo) 306 c.Assert(utils.IsValidUUIDString(controller.ControllerUUID), jc.IsTrue) 307 // We don't care about build numbers here. 308 bootstrapVers := bootstrapVersion.Number 309 bootstrapVers.Build = 0 310 controllerVers := version.MustParse(controller.AgentVersion) 311 controllerVers.Build = 0 312 c.Assert(controllerVers.String(), gc.Equals, bootstrapVers.String()) 313 314 controllerModel, err := s.store.ModelByName(controllerName, "admin/controller") 315 c.Assert(err, jc.ErrorIsNil) 316 c.Assert(utils.IsValidUUIDString(controllerModel.ModelUUID), jc.IsTrue) 317 318 // Bootstrap config should have been saved, and should only contain 319 // the type, name, and any user-supplied configuration. 320 bootstrapConfig, err := s.store.BootstrapConfigForController(controllerName) 321 c.Assert(err, jc.ErrorIsNil) 322 c.Assert(bootstrapConfig.Cloud, gc.Equals, "dummy") 323 c.Assert(bootstrapConfig.Credential, gc.Equals, "") 324 expected := map[string]interface{}{ 325 "name": bootstrap.ControllerModelName, 326 "type": "dummy", 327 "default-series": "raring", 328 "authorized-keys": "public auth key\n", 329 // Dummy provider defaults 330 "broken": "", 331 "secret": "pork", 332 "controller": false, 333 } 334 for k, v := range config.ConfigDefaults() { 335 if _, ok := expected[k]; !ok { 336 expected[k] = v 337 } 338 } 339 c.Assert(bootstrapConfig.Config, jc.DeepEquals, expected) 340 341 return restore 342 } 343 344 var bootstrapTests = []bootstrapTest{{ 345 info: "no args, no error, no upload, no constraints", 346 }, { 347 info: "bad --constraints", 348 args: []string{"--constraints", "bad=wrong"}, 349 err: `unknown constraint "bad"`, 350 }, { 351 info: "conflicting --constraints", 352 args: []string{"--constraints", "instance-type=foo mem=4G"}, 353 silentErr: true, 354 logs: []jc.SimpleMessage{{loggo.ERROR, `ambiguous constraints: "instance-type" overlaps with "mem"`}}, 355 }, { 356 info: "bad model", 357 version: "1.2.3-%LTS%-amd64", 358 args: []string{"--config", "broken=Bootstrap Destroy", "--auto-upgrade"}, 359 silentErr: true, 360 logs: []jc.SimpleMessage{{loggo.ERROR, `failed to bootstrap model: dummy.Bootstrap is broken`}}, 361 }, { 362 info: "constraints", 363 args: []string{"--constraints", "mem=4G cores=4"}, 364 constraints: constraints.MustParse("mem=4G cores=4"), 365 }, { 366 info: "bootstrap and environ constraints", 367 args: []string{"--constraints", "mem=4G cores=4", "--bootstrap-constraints", "mem=8G"}, 368 constraints: constraints.MustParse("mem=4G cores=4"), 369 bootstrapConstraints: constraints.MustParse("mem=8G cores=4"), 370 }, { 371 info: "unsupported constraint passed through but no error", 372 args: []string{"--constraints", "mem=4G cores=4 cpu-power=10"}, 373 constraints: constraints.MustParse("mem=4G cores=4 cpu-power=10"), 374 }, { 375 info: "--build-agent uses arch from constraint if it matches current version", 376 version: "1.3.3-saucy-ppc64el", 377 hostArch: "ppc64el", 378 args: []string{"--build-agent", "--constraints", "arch=ppc64el"}, 379 upload: "1.3.3.1-raring-ppc64el", // from jujuversion.Current 380 constraints: constraints.MustParse("arch=ppc64el"), 381 }, { 382 info: "--build-agent rejects mismatched arch", 383 version: "1.3.3-saucy-amd64", 384 hostArch: "amd64", 385 args: []string{"--build-agent", "--constraints", "arch=ppc64el"}, 386 silentErr: true, 387 logs: []jc.SimpleMessage{{ 388 loggo.ERROR, `failed to bootstrap model: cannot use agent built for "ppc64el" using a machine running on "amd64"`, 389 }}, 390 }, { 391 info: "--build-agent rejects non-supported arch", 392 version: "1.3.3-saucy-mips64", 393 hostArch: "mips64", 394 args: []string{"--build-agent"}, 395 silentErr: true, 396 logs: []jc.SimpleMessage{{ 397 loggo.ERROR, fmt.Sprintf(`failed to bootstrap model: model %q of type dummy does not support instances running on "mips64"`, bootstrap.ControllerModelName), 398 }}, 399 }, { 400 info: "--build-agent always bumps build number", 401 version: "1.2.3.4-raring-amd64", 402 hostArch: "amd64", 403 args: []string{"--build-agent"}, 404 upload: "1.2.3.5-raring-amd64", 405 }, { 406 info: "placement", 407 args: []string{"--to", "something"}, 408 placement: "something", 409 }, { 410 info: "keep broken", 411 args: []string{"--keep-broken"}, 412 keepBroken: true, 413 }, { 414 info: "additional args", 415 args: []string{"anything", "else"}, 416 err: `unrecognized args: \["anything" "else"\]`, 417 }, { 418 info: "--agent-version with --build-agent", 419 args: []string{"--agent-version", "1.1.0", "--build-agent"}, 420 err: `--agent-version and --build-agent can't be used together`, 421 }, { 422 info: "invalid --agent-version value", 423 args: []string{"--agent-version", "foo"}, 424 err: `invalid version "foo"`, 425 }, { 426 info: "agent-version doesn't match client version major", 427 version: "1.3.3-saucy-ppc64el", 428 args: []string{"--agent-version", "2.3.0"}, 429 err: regexp.QuoteMeta(`this client can only bootstrap 1.3 agents`), 430 }, { 431 info: "agent-version doesn't match client version minor", 432 version: "1.3.3-saucy-ppc64el", 433 args: []string{"--agent-version", "1.4.0"}, 434 err: regexp.QuoteMeta(`this client can only bootstrap 1.3 agents`), 435 }, { 436 info: "--clouds with --regions", 437 args: []string{"--clouds", "--regions", "aws"}, 438 err: `--clouds and --regions can't be used together`, 439 }, { 440 info: "specifying bootstrap attribute as model-default", 441 args: []string{"--model-default", "bootstrap-timeout=10"}, 442 err: `"bootstrap-timeout" is a bootstrap only attribute, and cannot be set as a model-default`, 443 }, { 444 info: "specifying controller attribute as model-default", 445 args: []string{"--model-default", "api-port=12345"}, 446 err: `"api-port" is a controller attribute, and cannot be set as a model-default`, 447 }} 448 449 func (s *BootstrapSuite) TestRunCloudNameUnknown(c *gc.C) { 450 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "unknown", "my-controller") 451 c.Check(err, gc.ErrorMatches, `unknown cloud "unknown", please try "juju update-clouds"`) 452 } 453 454 func (s *BootstrapSuite) TestRunBadCloudName(c *gc.C) { 455 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "bad^cloud", "my-controller") 456 c.Check(err, gc.ErrorMatches, `cloud name "bad\^cloud" not valid`) 457 } 458 459 func (s *BootstrapSuite) TestCheckProviderProvisional(c *gc.C) { 460 err := checkProviderType("devcontroller") 461 c.Assert(err, jc.ErrorIsNil) 462 463 for name, flag := range provisionalProviders { 464 // vsphere is disabled for gccgo. See lp:1440940. 465 if name == "vsphere" && runtime.Compiler == "gccgo" { 466 continue 467 } 468 c.Logf(" - trying %q -", name) 469 err := checkProviderType(name) 470 c.Check(err, gc.ErrorMatches, ".* provider is provisional .* set JUJU_DEV_FEATURE_FLAGS=.*") 471 472 err = os.Setenv(osenv.JujuFeatureFlagEnvKey, flag) 473 c.Assert(err, jc.ErrorIsNil) 474 err = checkProviderType(name) 475 c.Check(err, jc.ErrorIsNil) 476 } 477 } 478 479 func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { 480 const controllerName = "dev" 481 s.setupAutoUploadTest(c, "1.8.3", "raring") 482 483 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", controllerName, "--auto-upgrade") 484 c.Assert(err, jc.ErrorIsNil) 485 486 _, err = cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", controllerName, "--auto-upgrade") 487 c.Assert(err, gc.ErrorMatches, `controller "dev" already exists`) 488 } 489 490 func (s *BootstrapSuite) TestBootstrapDefaultControllerName(c *gc.C) { 491 s.setupAutoUploadTest(c, "1.8.3", "raring") 492 493 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy-cloud/region-1", "--auto-upgrade") 494 c.Assert(err, jc.ErrorIsNil) 495 currentController := s.store.CurrentControllerName 496 c.Assert(currentController, gc.Equals, "dummy-cloud-region-1") 497 details, err := s.store.ControllerByName(currentController) 498 c.Assert(err, jc.ErrorIsNil) 499 c.Assert(*details.MachineCount, gc.Equals, 1) 500 c.Assert(details.AgentVersion, gc.Equals, jujuversion.Current.String()) 501 } 502 503 func (s *BootstrapSuite) TestBootstrapDefaultControllerNameNoRegions(c *gc.C) { 504 s.setupAutoUploadTest(c, "1.8.3", "raring") 505 506 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "no-cloud-regions", "--auto-upgrade") 507 c.Assert(err, jc.ErrorIsNil) 508 currentController := s.store.CurrentControllerName 509 c.Assert(currentController, gc.Equals, "no-cloud-regions") 510 } 511 512 func (s *BootstrapSuite) TestBootstrapSetsCurrentModel(c *gc.C) { 513 s.setupAutoUploadTest(c, "1.8.3", "raring") 514 515 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "devcontroller", "--auto-upgrade") 516 c.Assert(err, jc.ErrorIsNil) 517 currentController := s.store.CurrentControllerName 518 c.Assert(currentController, gc.Equals, "devcontroller") 519 modelName, err := s.store.CurrentModel(currentController) 520 c.Assert(err, jc.ErrorIsNil) 521 c.Assert(modelName, gc.Equals, "admin/default") 522 m, err := s.store.ModelByName(currentController, modelName) 523 c.Assert(err, jc.ErrorIsNil) 524 c.Assert(m.ModelType, gc.Equals, model.IAAS) 525 } 526 527 func (s *BootstrapSuite) TestNoSwitch(c *gc.C) { 528 s.setupAutoUploadTest(c, "1.8.3", "raring") 529 530 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "devcontroller", "--no-switch") 531 c.Assert(err, jc.ErrorIsNil) 532 533 c.Assert(s.store.CurrentControllerName, gc.Equals, "arthur") 534 } 535 536 func (s *BootstrapSuite) TestBootstrapSetsControllerDetails(c *gc.C) { 537 s.setupAutoUploadTest(c, "1.8.3", "raring") 538 539 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "devcontroller", "--auto-upgrade") 540 c.Assert(err, jc.ErrorIsNil) 541 currentController := s.store.CurrentControllerName 542 c.Assert(currentController, gc.Equals, "devcontroller") 543 details, err := s.store.ControllerByName(currentController) 544 c.Assert(err, jc.ErrorIsNil) 545 c.Assert(*details.MachineCount, gc.Equals, 1) 546 c.Assert(details.AgentVersion, gc.Equals, jujuversion.Current.String()) 547 } 548 549 func (s *BootstrapSuite) TestBootstrapDefaultModel(c *gc.C) { 550 s.patchVersionAndSeries(c, "raring") 551 552 var bootstrap fakeBootstrapFuncs 553 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 554 return &bootstrap 555 }) 556 557 cmdtesting.RunCommand( 558 c, s.newBootstrapCommand(), 559 "dummy", "devcontroller", 560 "--auto-upgrade", 561 "--default-model", "mymodel", 562 "--config", "foo=bar", 563 ) 564 c.Assert(utils.IsValidUUIDString(bootstrap.args.ControllerConfig.ControllerUUID()), jc.IsTrue) 565 c.Assert(bootstrap.args.HostedModelConfig["name"], gc.Equals, "mymodel") 566 c.Assert(bootstrap.args.HostedModelConfig["foo"], gc.Equals, "bar") 567 } 568 569 func (s *BootstrapSuite) TestBootstrapTimeout(c *gc.C) { 570 s.patchVersionAndSeries(c, "raring") 571 572 var bootstrap fakeBootstrapFuncs 573 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 574 return &bootstrap 575 }) 576 cmdtesting.RunCommand( 577 c, s.newBootstrapCommand(), "dummy", "devcontroller", "--auto-upgrade", 578 "--config", "bootstrap-timeout=99", 579 ) 580 c.Assert(bootstrap.args.DialOpts.Timeout, gc.Equals, 99*time.Second) 581 } 582 583 func (s *BootstrapSuite) TestBootstrapAllSpacesAsConstraintsMerged(c *gc.C) { 584 s.patchVersionAndSeries(c, "raring") 585 586 var bootstrap fakeBootstrapFuncs 587 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 588 return &bootstrap 589 }) 590 cmdtesting.RunCommand( 591 c, s.newBootstrapCommand(), "dummy", "devcontroller", "--auto-upgrade", 592 "--config", "juju-ha-space=ha-space", "--config", "juju-mgmt-space=management-space", 593 "--constraints", "spaces=ha-space,random-space", 594 ) 595 596 got := *(bootstrap.args.BootstrapConstraints.Spaces) 597 c.Check(got, gc.DeepEquals, []string{"ha-space", "management-space", "random-space"}) 598 } 599 600 func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsProcessedAttributes(c *gc.C) { 601 s.patchVersionAndSeries(c, "raring") 602 603 var bootstrap fakeBootstrapFuncs 604 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 605 return &bootstrap 606 }) 607 608 fakeSSHFile := filepath.Join(c.MkDir(), "ssh") 609 err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600) 610 c.Assert(err, jc.ErrorIsNil) 611 cmdtesting.RunCommand( 612 c, s.newBootstrapCommand(), 613 "dummy", "devcontroller", 614 "--auto-upgrade", 615 "--config", "authorized-keys-path="+fakeSSHFile, 616 ) 617 _, ok := bootstrap.args.HostedModelConfig["authorized-keys-path"] 618 c.Assert(ok, jc.IsFalse) 619 } 620 621 func (s *BootstrapSuite) TestBootstrapModelDefaultConfig(c *gc.C) { 622 s.patchVersionAndSeries(c, "raring") 623 624 var bootstrap fakeBootstrapFuncs 625 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 626 return &bootstrap 627 }) 628 629 cmdtesting.RunCommand( 630 c, s.newBootstrapCommand(), 631 "dummy", "devcontroller", 632 "--model-default", "network=foo", 633 "--model-default", "ftp-proxy=model-proxy", 634 "--config", "ftp-proxy=controller-proxy", 635 ) 636 637 c.Check(bootstrap.args.HostedModelConfig["network"], gc.Equals, "foo") 638 c.Check(bootstrap.args.ControllerInheritedConfig["network"], gc.Equals, "foo") 639 640 c.Check(bootstrap.args.HostedModelConfig["ftp-proxy"], gc.Equals, "controller-proxy") 641 c.Check(bootstrap.args.ControllerInheritedConfig["ftp-proxy"], gc.Equals, "model-proxy") 642 } 643 644 func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsInheritedAttributes(c *gc.C) { 645 s.patchVersionAndSeries(c, "raring") 646 647 var bootstrap fakeBootstrapFuncs 648 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 649 return &bootstrap 650 }) 651 652 fakeSSHFile := filepath.Join(c.MkDir(), "ssh") 653 err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600) 654 c.Assert(err, jc.ErrorIsNil) 655 cmdtesting.RunCommand( 656 c, s.newBootstrapCommand(), 657 "dummy", "devcontroller", 658 "--auto-upgrade", 659 "--config", "authorized-keys=ssh-key", 660 "--config", "agent-version=1.19.0", 661 ) 662 _, ok := bootstrap.args.HostedModelConfig["authorized-keys"] 663 c.Assert(ok, jc.IsFalse) 664 _, ok = bootstrap.args.HostedModelConfig["agent-version"] 665 c.Assert(ok, jc.IsFalse) 666 } 667 668 // checkConfigs runs bootstrapCmd.getBootstrapConfigs and checks the returned configs match 669 // the expected values passed in the expect parameter. 670 func checkConfigs( 671 c *gc.C, 672 bootstrapCmd bootstrapCommand, 673 key string, 674 ctx *cmd.Context, cloud *cloud.Cloud, provider environs.EnvironProvider, 675 expect map[string]map[string]interface{}) { 676 677 configs, err := bootstrapCmd.bootstrapConfigs(ctx, *cloud, provider) 678 679 c.Assert(err, jc.ErrorIsNil) 680 681 checkConfigEntryMatches(c, configs.bootstrapModel, key, "bootstrapModelConfig", expect) 682 checkConfigEntryMatches(c, configs.inheritedControllerAttrs, key, "inheritedControllerAttrs", expect) 683 checkConfigEntryMatches(c, configs.userConfigAttrs, key, "userConfigAttrs", expect) 684 685 _, ok := configs.controller[key] 686 c.Check(ok, jc.IsFalse) 687 } 688 689 // checkConfigEntryMatches tests that a keys existence and indexed value in configMap 690 // matches those in expect[name]. 691 func checkConfigEntryMatches(c *gc.C, configMap map[string]interface{}, key, name string, expect map[string]map[string]interface{}) { 692 v, ok := configMap[key] 693 expected_config, expected_config_ok := expect[name] 694 c.Assert(expected_config_ok, jc.IsTrue) 695 v_expect, ok_expect := expected_config[key] 696 697 c.Logf("checkConfigEntryMatches %v %v", name, key) 698 c.Check(ok, gc.Equals, ok_expect) 699 c.Check(v, gc.Equals, v_expect) 700 } 701 702 func (s *BootstrapSuite) TestBootstrapAttributesInheritedOverDefaults(c *gc.C) { 703 /* Test that defaults are overwritten by inherited attributes by setting 704 the inherited attribute enable-os-upgrade to true in the cloud 705 config and ensure that it ends up as true in the model config. */ 706 s.patchVersionAndSeries(c, "raring") 707 708 bootstrapCmd := bootstrapCommand{} 709 ctx := cmdtesting.Context(c) 710 711 // The OpenStack provider has a default of "use-floating-ip": false, so we 712 // use that to test against. 713 env := &openstack.Environ{} 714 provider := env.Provider() 715 716 // First test that use-floating-ip defaults to false 717 testCloud, err := cloud.CloudByName("dummy-cloud") 718 c.Assert(err, jc.ErrorIsNil) 719 720 key := "use-floating-ip" 721 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 722 "bootstrapModelConfig": {key: false}, 723 "inheritedControllerAttrs": {}, 724 "userConfigAttrs": {}, 725 }) 726 727 // Second test that use-floating-ip in the cloud config overwrites the 728 // provider default of false with true 729 testCloud, err = cloud.CloudByName("dummy-cloud-with-config") 730 c.Assert(err, jc.ErrorIsNil) 731 732 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 733 "bootstrapModelConfig": {key: true}, 734 "inheritedControllerAttrs": {key: true}, 735 "userConfigAttrs": {}, 736 }) 737 } 738 739 func (s *BootstrapSuite) TestBootstrapRegionConfigNoRegionSpecified(c *gc.C) { 740 resetJujuXDGDataHome(c) 741 742 var bootstrap fakeBootstrapFuncs 743 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 744 return &bootstrap 745 }) 746 747 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy-cloud-dummy-region-config") 748 c.Assert(err, gc.Equals, cmd.ErrSilent) 749 c.Assert(bootstrap.args.ControllerInheritedConfig["secret"], gc.Equals, "region-test") 750 } 751 752 func (s *BootstrapSuite) TestBootstrapRegionConfigAttributesOverCloudConfig(c *gc.C) { 753 /* Test that cloud config attributes are overwritten by region config 754 attributes by setting both to something different in the config setup. 755 Only the region config values should be found */ 756 s.patchVersionAndSeries(c, "raring") 757 758 bootstrapCmd := bootstrapCommand{Region: "region-2"} 759 ctx := cmdtesting.Context(c) 760 761 // The OpenStack provider has a config attribute of network we can use. 762 env := &openstack.Environ{} 763 provider := env.Provider() 764 765 // First test that the network is set to the cloud config value 766 key := "network" 767 testCloud, err := cloud.CloudByName("dummy-cloud-with-region-config") 768 c.Assert(err, jc.ErrorIsNil) 769 770 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 771 "bootstrapModelConfig": {key: "cloud-network"}, 772 "inheritedControllerAttrs": {key: "cloud-network"}, 773 "userConfigAttrs": {}, 774 }) 775 776 // Second test that network in the region config overwrites the cloud config network value. 777 bootstrapCmd = bootstrapCommand{Region: "region-1"} 778 testCloud, err = cloud.CloudByName("dummy-cloud-with-region-config") 779 c.Assert(err, jc.ErrorIsNil) 780 781 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 782 "bootstrapModelConfig": {key: "region-network"}, 783 "inheritedControllerAttrs": {key: "region-network"}, 784 "userConfigAttrs": {}, 785 }) 786 } 787 788 func (s *BootstrapSuite) TestBootstrapAttributesCLIOverDefaults(c *gc.C) { 789 /* Test that defaults are overwritten by CLI passed attributes by setting 790 the inherited attribute enable-os-upgrade to true in the cloud 791 config and ensure that it ends up as true in the model config. */ 792 s.patchVersionAndSeries(c, "raring") 793 794 bootstrapCmd := bootstrapCommand{} 795 ctx := cmdtesting.Context(c) 796 797 // The OpenStack provider has a default of "use-floating-ip": false, so we 798 // use that to test against. 799 env := &openstack.Environ{} 800 provider := env.Provider() 801 802 // First test that use-floating-ip defaults to false 803 testCloud, err := cloud.CloudByName("dummy-cloud") 804 c.Assert(err, jc.ErrorIsNil) 805 806 key := "use-floating-ip" 807 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 808 "bootstrapModelConfig": {key: false}, 809 "inheritedControllerAttrs": {}, 810 "userConfigAttrs": {}, 811 }) 812 813 // Second test that use-floating-ip passed on the command line overwrites the 814 // provider default of false with true 815 bootstrapCmd.config.Set("use-floating-ip=true") 816 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 817 "bootstrapModelConfig": {key: true}, 818 "inheritedControllerAttrs": {}, 819 "userConfigAttrs": {key: true}, 820 }) 821 } 822 823 func (s *BootstrapSuite) TestBootstrapAttributesCLIOverInherited(c *gc.C) { 824 /* Test that defaults are overwritten by CLI passed attributes by setting 825 the inherited attribute enable-os-upgrade to true in the cloud 826 config and ensure that it ends up as true in the model config. */ 827 s.patchVersionAndSeries(c, "raring") 828 829 bootstrapCmd := bootstrapCommand{} 830 ctx := cmdtesting.Context(c) 831 832 // The OpenStack provider has a default of "use-floating-ip": false, so we 833 // use that to test against. 834 env := &openstack.Environ{} 835 provider := env.Provider() 836 837 // First test that use-floating-ip defaults to false 838 testCloud, err := cloud.CloudByName("dummy-cloud") 839 c.Assert(err, jc.ErrorIsNil) 840 841 key := "use-floating-ip" 842 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 843 "bootstrapModelConfig": {key: false}, 844 "inheritedControllerAttrs": {}, 845 "userConfigAttrs": {}, 846 }) 847 848 // Second test that use-floating-ip passed on the command line overwrites the 849 // inherited attribute 850 testCloud, err = cloud.CloudByName("dummy-cloud-with-config") 851 c.Assert(err, jc.ErrorIsNil) 852 bootstrapCmd.config.Set("use-floating-ip=false") 853 checkConfigs(c, bootstrapCmd, key, ctx, testCloud, provider, map[string]map[string]interface{}{ 854 "bootstrapModelConfig": {key: false}, 855 "inheritedControllerAttrs": {key: true}, 856 "userConfigAttrs": {key: false}, 857 }) 858 } 859 860 func (s *BootstrapSuite) TestBootstrapWithGUI(c *gc.C) { 861 s.patchVersionAndSeries(c, "raring") 862 var bootstrap fakeBootstrapFuncs 863 864 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 865 return &bootstrap 866 }) 867 cmdtesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "dummy", "devcontroller") 868 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, gui.DefaultBaseURL) 869 } 870 871 func (s *BootstrapSuite) TestBootstrapWithCustomizedGUI(c *gc.C) { 872 s.patchVersionAndSeries(c, "raring") 873 s.PatchEnvironment("JUJU_GUI_SIMPLESTREAMS_URL", "https://1.2.3.4/gui/streams") 874 875 var bootstrap fakeBootstrapFuncs 876 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 877 return &bootstrap 878 }) 879 880 cmdtesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "dummy", "devcontroller") 881 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "https://1.2.3.4/gui/streams") 882 } 883 884 func (s *BootstrapSuite) TestBootstrapWithoutGUI(c *gc.C) { 885 s.patchVersionAndSeries(c, "raring") 886 var bootstrap fakeBootstrapFuncs 887 888 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 889 return &bootstrap 890 }) 891 cmdtesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "dummy", "devcontroller", "--no-gui") 892 c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "") 893 } 894 895 type mockBootstrapInstance struct { 896 instances.Instance 897 } 898 899 func (*mockBootstrapInstance) Addresses() ([]network.Address, error) { 900 return []network.Address{{Value: "localhost"}}, nil 901 } 902 903 // In the case where we cannot examine the client store, we want the 904 // error to propagate back up to the user. 905 func (s *BootstrapSuite) TestBootstrapPropagatesStoreErrors(c *gc.C) { 906 const controllerName = "devcontroller" 907 s.patchVersionAndSeries(c, "raring") 908 909 store := jujuclienttesting.NewStubStore() 910 store.CurrentControllerFunc = func() (string, error) { 911 return "arthur", nil 912 } 913 store.CurrentModelFunc = func(controller string) (string, error) { 914 c.Assert(controller, gc.Equals, "arthur") 915 return "sword", nil 916 } 917 store.ModelByNameFunc = func(controller, model string) (*jujuclient.ModelDetails, error) { 918 c.Assert(controller, gc.Equals, "arthur") 919 c.Assert(model, gc.Equals, "sword") 920 return &jujuclient.ModelDetails{}, nil 921 } 922 store.SetErrors(errors.New("oh noes")) 923 cmd := &bootstrapCommand{} 924 cmd.SetClientStore(store) 925 wrapped := modelcmd.Wrap(cmd, modelcmd.WrapSkipModelFlags, modelcmd.WrapSkipDefaultModel) 926 _, err := cmdtesting.RunCommand(c, wrapped, "dummy", controllerName, "--auto-upgrade") 927 store.CheckCallNames(c, "CredentialForCloud") 928 c.Assert(err, gc.ErrorMatches, `loading credentials: oh noes`) 929 } 930 931 // When attempting to bootstrap, check that when prepare errors out, 932 // bootstrap will stop immediately. Nothing will be destroyed. 933 func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) { 934 destroyed := false 935 s.PatchValue(&environsDestroy, func(name string, _ environs.ControllerDestroyer, _ context.ProviderCallContext, _ jujuclient.ControllerStore) error { 936 c.Assert(name, gc.Equals, "decontroller") 937 destroyed = true 938 return nil 939 }) 940 941 s.PatchValue(&bootstrapPrepareController, func( 942 bool, 943 environs.BootstrapContext, 944 jujuclient.ClientStore, 945 bootstrap.PrepareParams, 946 ) (environs.BootstrapEnviron, error) { 947 return nil, errors.New("mock-prepare") 948 }) 949 950 ctx := cmdtesting.Context(c) 951 _, errc := cmdtest.RunCommandWithDummyProvider( 952 ctx, s.newBootstrapCommand(), 953 "dummy", "devcontroller", 954 ) 955 c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$") 956 c.Check(destroyed, jc.IsFalse) 957 } 958 959 // TestBootstrapInvalidCredentialMessage tests that an informative message is logged 960 // when attempting to bootstrap with an invalid credential. 961 func (s *BootstrapSuite) TestBootstrapInvalidCredentialMessage(c *gc.C) { 962 bootstrap := &fakeBootstrapFuncs{ 963 bootstrapF: func(_ environs.BootstrapContext, _ environs.BootstrapEnviron, callCtx context.ProviderCallContext, _ bootstrap.BootstrapParams) error { 964 callCtx.InvalidateCredential("considered invalid for the sake of testing") 965 return nil 966 }, 967 } 968 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 969 return bootstrap 970 }) 971 ctx, _ := cmdtesting.RunCommand(c, s.newBootstrapCommand(), 972 "dummy", "devcontroller", 973 "--auto-upgrade", 974 ) 975 c.Assert(cmdtesting.Stderr(ctx), jc.Contains, 976 `Cloud credential "default" is not accepted by cloud provider: considered invalid for the sake of testing`) 977 } 978 979 type controllerModelAccountParams struct { 980 controller string 981 controllerUUID string 982 model string 983 user string 984 } 985 986 func (s *BootstrapSuite) writeControllerModelAccountInfo(c *gc.C, context *controllerModelAccountParams) { 987 controller := context.controller 988 bootstrapModel := context.model 989 user := context.user 990 controllerUUID := "a-uuid" 991 if context.controllerUUID != "" { 992 controllerUUID = context.controllerUUID 993 } 994 err := s.store.AddController(controller, jujuclient.ControllerDetails{ 995 CACert: "a-cert", 996 ControllerUUID: controllerUUID, 997 }) 998 c.Assert(err, jc.ErrorIsNil) 999 err = s.store.SetCurrentController(controller) 1000 c.Assert(err, jc.ErrorIsNil) 1001 err = s.store.UpdateAccount(controller, jujuclient.AccountDetails{ 1002 User: user, 1003 Password: "secret", 1004 }) 1005 c.Assert(err, jc.ErrorIsNil) 1006 err = s.store.UpdateModel(controller, bootstrapModel, jujuclient.ModelDetails{ 1007 ModelUUID: "model-uuid", 1008 ModelType: model.IAAS, 1009 }) 1010 c.Assert(err, jc.ErrorIsNil) 1011 err = s.store.SetCurrentModel(controller, bootstrapModel) 1012 c.Assert(err, jc.ErrorIsNil) 1013 } 1014 1015 func (s *BootstrapSuite) TestBootstrapErrorRestoresOldMetadata(c *gc.C) { 1016 s.patchVersionAndSeries(c, "raring") 1017 s.PatchValue(&bootstrapPrepareController, func( 1018 bool, 1019 environs.BootstrapContext, 1020 jujuclient.ClientStore, 1021 bootstrap.PrepareParams, 1022 ) (environs.BootstrapEnviron, error) { 1023 ctx := controllerModelAccountParams{ 1024 controller: "foo", 1025 model: "foobar/bar", 1026 user: "foobar", 1027 } 1028 s.writeControllerModelAccountInfo(c, &ctx) 1029 return nil, errors.New("mock-prepare") 1030 }) 1031 1032 ctx := controllerModelAccountParams{ 1033 controller: "olddevcontroller", 1034 controllerUUID: "another-uuid", 1035 model: "fred/fredmodel", 1036 user: "fred", 1037 } 1038 s.writeControllerModelAccountInfo(c, &ctx) 1039 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "devcontroller", "--auto-upgrade") 1040 c.Assert(err, gc.ErrorMatches, "mock-prepare") 1041 1042 currentController := s.store.CurrentControllerName 1043 c.Assert(currentController, gc.Equals, "olddevcontroller") 1044 accountDetails, err := s.store.AccountDetails(currentController) 1045 c.Assert(err, jc.ErrorIsNil) 1046 c.Assert(accountDetails.User, gc.Equals, "fred") 1047 currentModel, err := s.store.CurrentModel(currentController) 1048 c.Assert(err, jc.ErrorIsNil) 1049 c.Assert(currentModel, gc.Equals, "fred/fredmodel") 1050 } 1051 1052 func (s *BootstrapSuite) TestBootstrapAlreadyExists(c *gc.C) { 1053 const controllerName = "devcontroller" 1054 s.patchVersionAndSeries(c, "raring") 1055 1056 cmaCtx := controllerModelAccountParams{ 1057 controller: "devcontroller", 1058 model: "fred/fredmodel", 1059 user: "fred", 1060 } 1061 s.writeControllerModelAccountInfo(c, &cmaCtx) 1062 1063 ctx := cmdtesting.Context(c) 1064 _, errc := cmdtest.RunCommandWithDummyProvider(ctx, s.newBootstrapCommand(), "dummy", controllerName, "--auto-upgrade") 1065 err := <-errc 1066 c.Assert(err, jc.Satisfies, errors.IsAlreadyExists) 1067 c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`controller %q already exists`, controllerName)) 1068 currentController := s.store.CurrentControllerName 1069 c.Assert(currentController, gc.Equals, "devcontroller") 1070 accountDetails, err := s.store.AccountDetails(currentController) 1071 c.Assert(err, jc.ErrorIsNil) 1072 c.Assert(accountDetails.User, gc.Equals, "fred") 1073 currentModel, err := s.store.CurrentModel(currentController) 1074 c.Assert(err, jc.ErrorIsNil) 1075 c.Assert(currentModel, gc.Equals, "fred/fredmodel") 1076 } 1077 1078 func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) { 1079 s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0")) 1080 s.PatchValue(&envtools.BundleTools, func(bool, io.Writer, *version.Number) (version.Binary, bool, string, error) { 1081 return version.Binary{}, false, "", errors.New("no agent binaries for you") 1082 }) 1083 resetJujuXDGDataHome(c) 1084 1085 // Bootstrap the controller with an invalid source. 1086 // The command will look for prepackaged agent binaries 1087 // in the source, and then fall back to building. 1088 ctx, err := cmdtesting.RunCommand( 1089 c, s.newBootstrapCommand(), "--metadata-source", c.MkDir(), 1090 "dummy", "devcontroller", 1091 ) 1092 c.Check(err, gc.Equals, cmd.ErrSilent) 1093 1094 stderr := cmdtesting.Stderr(ctx) 1095 c.Check(stderr, gc.Matches, 1096 "Creating Juju controller \"devcontroller\" on dummy/dummy\n"+ 1097 "Looking for packaged Juju agent version 1.2.0 for amd64\n", 1098 ) 1099 c.Check(s.tw.Log(), jc.LogMatches, []jc.SimpleMessage{ 1100 {loggo.ERROR, "failed to bootstrap model: no matching agent binaries available"}, 1101 }) 1102 } 1103 1104 // createImageMetadata creates some image metadata in a local directory. 1105 func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) { 1106 // Generate some image metadata. 1107 im := []*imagemetadata.ImageMetadata{ 1108 { 1109 Id: "1234", 1110 Arch: "amd64", 1111 Version: "13.04", 1112 RegionName: "region", 1113 Endpoint: "endpoint", 1114 }, 1115 } 1116 cloudSpec := &simplestreams.CloudSpec{ 1117 Region: "region", 1118 Endpoint: "endpoint", 1119 } 1120 sourceDir := c.MkDir() 1121 sourceStor, err := filestorage.NewFileStorageWriter(sourceDir) 1122 c.Assert(err, jc.ErrorIsNil) 1123 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor) 1124 c.Assert(err, jc.ErrorIsNil) 1125 return sourceDir, im 1126 } 1127 1128 func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) { 1129 sourceDir, _ := createImageMetadata(c) 1130 resetJujuXDGDataHome(c) 1131 1132 var bootstrap fakeBootstrapFuncs 1133 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1134 return &bootstrap 1135 }) 1136 1137 cmdtesting.RunCommand( 1138 c, s.newBootstrapCommand(), 1139 "--metadata-source", sourceDir, "--constraints", "mem=4G", 1140 "dummy-cloud/region-1", "devcontroller", 1141 "--config", "default-series=raring", 1142 ) 1143 c.Assert(bootstrap.args.MetadataDir, gc.Equals, sourceDir) 1144 } 1145 1146 func (s *BootstrapSuite) checkBootstrapWithVersion(c *gc.C, vers, expect string) { 1147 resetJujuXDGDataHome(c) 1148 1149 var bootstrap fakeBootstrapFuncs 1150 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1151 return &bootstrap 1152 }) 1153 1154 num := jujuversion.Current 1155 num.Major = 2 1156 num.Minor = 3 1157 s.PatchValue(&jujuversion.Current, num) 1158 cmdtesting.RunCommand( 1159 c, s.newBootstrapCommand(), 1160 "--agent-version", vers, 1161 "dummy-cloud/region-1", "devcontroller", 1162 "--config", "default-series=raring", 1163 ) 1164 c.Assert(bootstrap.args.AgentVersion, gc.NotNil) 1165 c.Assert(*bootstrap.args.AgentVersion, gc.Equals, version.MustParse(expect)) 1166 } 1167 1168 func (s *BootstrapSuite) TestBootstrapWithVersionNumber(c *gc.C) { 1169 s.checkBootstrapWithVersion(c, "2.3.4", "2.3.4") 1170 } 1171 1172 func (s *BootstrapSuite) TestBootstrapWithBinaryVersionNumber(c *gc.C) { 1173 s.checkBootstrapWithVersion(c, "2.3.4-trusty-ppc64", "2.3.4") 1174 } 1175 1176 func (s *BootstrapSuite) TestBootstrapWithAutoUpgrade(c *gc.C) { 1177 resetJujuXDGDataHome(c) 1178 1179 var bootstrap fakeBootstrapFuncs 1180 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1181 return &bootstrap 1182 }) 1183 cmdtesting.RunCommand( 1184 c, s.newBootstrapCommand(), 1185 "--auto-upgrade", 1186 "dummy-cloud/region-1", "devcontroller", 1187 ) 1188 c.Assert(bootstrap.args.AgentVersion, gc.IsNil) 1189 } 1190 1191 func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) { 1192 sourceDir := createToolsSource(c, vAll) 1193 s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0")) 1194 series.SetLatestLtsForTesting("trusty") 1195 resetJujuXDGDataHome(c) 1196 1197 // Bootstrap the controller with the valid source. 1198 // The bootstrapping has to show no error, because the tools 1199 // are automatically synchronized. 1200 _, err := cmdtesting.RunCommand( 1201 c, s.newBootstrapCommand(), "--metadata-source", sourceDir, 1202 "dummy-cloud/region-1", "devcontroller", "--config", "default-series=trusty", 1203 ) 1204 c.Assert(err, jc.ErrorIsNil) 1205 1206 bootstrapConfig, params, err := modelcmd.NewGetBootstrapConfigParamsFunc( 1207 cmdtesting.Context(c), s.store, environs.GlobalProviderRegistry(), 1208 )("devcontroller") 1209 c.Assert(err, jc.ErrorIsNil) 1210 provider, err := environs.Provider(bootstrapConfig.CloudType) 1211 c.Assert(err, jc.ErrorIsNil) 1212 cfg, err := provider.PrepareConfig(*params) 1213 c.Assert(err, jc.ErrorIsNil) 1214 1215 env, err := environs.New(environs.OpenParams{ 1216 Cloud: params.Cloud, 1217 Config: cfg, 1218 }) 1219 c.Assert(err, jc.ErrorIsNil) 1220 err = env.PrepareForBootstrap(envtesting.BootstrapContext(c)) 1221 c.Assert(err, jc.ErrorIsNil) 1222 1223 // Now check the available tools which are the 1.2.0 envtools. 1224 checkTools(c, env, v120All) 1225 } 1226 1227 func (s *BootstrapSuite) TestInteractiveBootstrap(c *gc.C) { 1228 s.setupAutoUploadTest(c, "1.8.3", "precise") 1229 //s.patchVersionAndSeries(c, "raring") 1230 1231 cmd := s.newBootstrapCommand() 1232 err := cmdtesting.InitCommand(cmd, nil) 1233 c.Assert(err, jc.ErrorIsNil) 1234 ctx := cmdtesting.Context(c) 1235 out := bytes.Buffer{} 1236 ctx.Stdin = strings.NewReader(` 1237 dummy-cloud 1238 region-1 1239 my-dummy-cloud 1240 `[1:]) 1241 ctx.Stdout = &out 1242 err = cmd.Run(ctx) 1243 if err != nil { 1244 c.Logf(out.String()) 1245 } 1246 c.Assert(err, jc.ErrorIsNil) 1247 1248 name := s.store.CurrentControllerName 1249 c.Assert(name, gc.Equals, "my-dummy-cloud") 1250 controller := s.store.Controllers[name] 1251 c.Assert(controller.Cloud, gc.Equals, "dummy-cloud") 1252 c.Assert(controller.CloudRegion, gc.Equals, "region-1") 1253 } 1254 1255 func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, ser string) { 1256 patchedVersion := version.MustParse(vers) 1257 patchedVersion.Build = 1 1258 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &patchedVersion)) 1259 sourceDir := createToolsSource(c, vAll) 1260 s.PatchValue(&envtools.DefaultBaseURL, sourceDir) 1261 1262 // Change the tools location to be the test location and also 1263 // the version and ensure their later restoring. 1264 // Set the current version to be something for which there are no tools 1265 // so we can test that an upload is forced. 1266 s.PatchValue(&jujuversion.Current, version.MustParse(vers)) 1267 s.PatchValue(&series.MustHostSeries, func() string { return ser }) 1268 1269 // Create home with dummy provider and remove all 1270 // of its envtools. 1271 resetJujuXDGDataHome(c) 1272 } 1273 1274 func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) { 1275 s.PatchValue(&series.MustHostSeries, func() string { return supportedversion.SupportedLTS() }) 1276 s.setupAutoUploadTest(c, "1.7.3", "quantal") 1277 // Run command and check for that upload has been run for tools matching 1278 // the current juju version. 1279 opc, errc := cmdtest.RunCommandWithDummyProvider( 1280 cmdtesting.Context(c), s.newBootstrapCommand(), 1281 "dummy-cloud/region-1", "devcontroller", 1282 "--config", "default-series=raring", 1283 "--auto-upgrade", 1284 ) 1285 select { 1286 case err := <-errc: 1287 c.Assert(err, jc.ErrorIsNil) 1288 case <-time.After(coretesting.LongWait): 1289 c.Fatal("timed out") 1290 } 1291 c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, bootstrap.ControllerModelName) 1292 icfg := (<-opc).(dummy.OpFinalizeBootstrap).InstanceConfig 1293 c.Assert(icfg, gc.NotNil) 1294 c.Assert(icfg.AgentVersion().String(), gc.Equals, "1.7.3.1-raring-"+arch.HostArch()) 1295 } 1296 1297 func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) { 1298 s.setupAutoUploadTest(c, "1.8.3", "precise") 1299 1300 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), 1301 "dummy-cloud/region-1", "devcontroller", 1302 "--config", "default-series=raring", "--agent-version=1.8.4", 1303 ) 1304 c.Assert(err, gc.Equals, cmd.ErrSilent) 1305 c.Check(s.tw.Log(), jc.LogMatches, []jc.SimpleMessage{{ 1306 loggo.ERROR, 1307 "failed to bootstrap model: Juju cannot bootstrap because no agent binaries are available for your model", 1308 }}) 1309 } 1310 1311 func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) { 1312 1313 BuildAgentTarballAlwaysFails := func(build bool, forceVersion *version.Number, stream string) (*sync.BuiltAgent, error) { 1314 return nil, errors.New("an error") 1315 } 1316 1317 s.setupAutoUploadTest(c, "1.7.3", "precise") 1318 s.PatchValue(&sync.BuildAgentTarball, BuildAgentTarballAlwaysFails) 1319 1320 ctx, err := cmdtesting.RunCommand( 1321 c, s.newBootstrapCommand(), 1322 "dummy-cloud/region-1", "devcontroller", 1323 "--config", "default-series=raring", 1324 "--config", "agent-stream=proposed", 1325 "--auto-upgrade", "--agent-version=1.7.3", 1326 ) 1327 1328 c.Check(cmdtesting.Stderr(ctx), gc.Equals, ` 1329 Creating Juju controller "devcontroller" on dummy-cloud/region-1 1330 Looking for packaged Juju agent version 1.7.3 for amd64 1331 No packaged binary found, preparing local Juju agent binary 1332 `[1:]) 1333 c.Assert(err, gc.Equals, cmd.ErrSilent) 1334 c.Check(s.tw.Log(), jc.LogMatches, []jc.SimpleMessage{{ 1335 loggo.ERROR, 1336 "failed to bootstrap model: cannot package bootstrap agent binary: an error", 1337 }}) 1338 } 1339 1340 func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { 1341 s.setupAutoUploadTest(c, "1.7.3", "quantal") 1342 1343 opc, errc := cmdtest.RunCommandWithDummyProvider( 1344 cmdtesting.Context(c), s.newBootstrapCommand(), 1345 "dummy-cloud/region-1", "devcontroller", 1346 "--config", "broken=Bootstrap Destroy", 1347 "--auto-upgrade", 1348 ) 1349 select { 1350 case err := <-errc: 1351 c.Assert(err, gc.Equals, cmd.ErrSilent) 1352 case <-time.After(coretesting.LongWait): 1353 c.Fatal("timed out") 1354 } 1355 1356 var opDestroy *dummy.OpDestroy 1357 for opDestroy == nil { 1358 select { 1359 case op := <-opc: 1360 switch op := op.(type) { 1361 case dummy.OpDestroy: 1362 opDestroy = &op 1363 } 1364 default: 1365 c.Error("expected call to env.Destroy") 1366 return 1367 } 1368 } 1369 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") 1370 1371 c.Check(s.tw.Log(), jc.LogMatches, []jc.SimpleMessage{ 1372 {loggo.ERROR, "failed to bootstrap model: dummy.Bootstrap is broken"}, 1373 {loggo.DEBUG, "(error details.*)"}, 1374 {loggo.DEBUG, "cleaning up after failed bootstrap"}, 1375 {loggo.ERROR, "error cleaning up: dummy.Destroy is broken"}, 1376 }) 1377 } 1378 1379 func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) { 1380 s.setupAutoUploadTest(c, "1.7.3", "quantal") 1381 1382 ctx := cmdtesting.Context(c) 1383 opc, errc := cmdtest.RunCommandWithDummyProvider(ctx, s.newBootstrapCommand(), 1384 "--keep-broken", 1385 "dummy-cloud/region-1", "devcontroller", 1386 "--config", "broken=Bootstrap Destroy", 1387 "--auto-upgrade", 1388 ) 1389 select { 1390 case err := <-errc: 1391 c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") 1392 case <-time.After(coretesting.LongWait): 1393 c.Fatal("timed out") 1394 } 1395 done := false 1396 for !done { 1397 select { 1398 case op, ok := <-opc: 1399 if !ok { 1400 done = true 1401 break 1402 } 1403 switch op.(type) { 1404 case dummy.OpDestroy: 1405 c.Error("unexpected call to env.Destroy") 1406 break 1407 } 1408 default: 1409 break 1410 } 1411 } 1412 stderr := strings.Replace(cmdtesting.Stderr(ctx), "\n", " ", -1) 1413 c.Assert(stderr, gc.Matches, `.*See .*juju kill\-controller.*`) 1414 } 1415 1416 func (s *BootstrapSuite) TestBootstrapUnknownCloudOrProvider(c *gc.C) { 1417 s.patchVersionAndSeries(c, "raring") 1418 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "no-such-provider", "ctrl") 1419 c.Assert(err, gc.ErrorMatches, `unknown cloud "no-such-provider", please try "juju update-clouds"`) 1420 } 1421 1422 func (s *BootstrapSuite) TestBootstrapProviderNoRegionDetection(c *gc.C) { 1423 s.patchVersionAndSeries(c, "raring") 1424 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "no-cloud-region-detection", "ctrl") 1425 c.Assert(err, gc.ErrorMatches, `unknown cloud "no-cloud-region-detection", please try "juju update-clouds"`) 1426 } 1427 1428 func (s *BootstrapSuite) TestBootstrapProviderNoRegions(c *gc.C) { 1429 ctx, err := cmdtesting.RunCommand( 1430 c, s.newBootstrapCommand(), "no-cloud-regions", "ctrl", 1431 "--config", "default-series=precise", 1432 ) 1433 c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"ctrl\" on no-cloud-regions(.|\n)*") 1434 c.Assert(err, jc.ErrorIsNil) 1435 } 1436 1437 func (s *BootstrapSuite) TestBootstrapCloudNoRegions(c *gc.C) { 1438 resetJujuXDGDataHome(c) 1439 ctx, err := cmdtesting.RunCommand( 1440 c, s.newBootstrapCommand(), "dummy-cloud-without-regions", "ctrl", 1441 "--config", "default-series=precise", 1442 ) 1443 c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"ctrl\" on dummy-cloud-without-regions(.|\n)*") 1444 c.Assert(err, jc.ErrorIsNil) 1445 } 1446 1447 func (s *BootstrapSuite) TestBootstrapCloudNoRegionsOneSpecified(c *gc.C) { 1448 resetJujuXDGDataHome(c) 1449 ctx, err := cmdtesting.RunCommand( 1450 c, s.newBootstrapCommand(), "dummy-cloud-without-regions/my-region", "ctrl", 1451 "--config", "default-series=precise", 1452 ) 1453 c.Check(cmdtesting.Stderr(ctx), gc.Matches, 1454 "region \"my-region\" not found \\(expected one of \\[\\]\\)\n\n.*\n") 1455 c.Assert(err, gc.Equals, cmd.ErrSilent) 1456 } 1457 1458 func (s *BootstrapSuite) TestBootstrapProviderNoCredentials(c *gc.C) { 1459 s.patchVersionAndSeries(c, "raring") 1460 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "no-credentials", "ctrl") 1461 c.Assert(err, gc.ErrorMatches, "detecting credentials for \"no-credentials\" cloud provider: credentials not found\nSee `juju add-credential no-credentials --help` for instructions") 1462 } 1463 1464 func (s *BootstrapSuite) TestBootstrapProviderManyDetectedCredentials(c *gc.C) { 1465 s.patchVersionAndSeries(c, "raring") 1466 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "many-credentials", "ctrl") 1467 c.Assert(err, gc.ErrorMatches, ambiguousDetectedCredentialError.Error()) 1468 } 1469 1470 func (s *BootstrapSuite) TestBootstrapProviderFileCredential(c *gc.C) { 1471 dummyProvider, err := environs.Provider("dummy") 1472 c.Assert(err, jc.ErrorIsNil) 1473 1474 tmpFile, err := ioutil.TempFile("", "juju-bootstrap-test") 1475 c.Assert(err, jc.ErrorIsNil) 1476 defer func() { 1477 tmpFile.Close() 1478 err := os.Remove(tmpFile.Name()) 1479 c.Assert(err, jc.ErrorIsNil) 1480 }() 1481 1482 contents := []byte("{something: special}\n") 1483 err = ioutil.WriteFile(tmpFile.Name(), contents, 0644) 1484 1485 unfinalizedCredential := cloud.NewEmptyCredential() 1486 finalizedCredential := cloud.NewEmptyCredential() 1487 fp := fileCredentialProvider{ 1488 dummyProvider.(environs.CloudEnvironProvider), 1489 tmpFile.Name(), 1490 &unfinalizedCredential, 1491 &finalizedCredential} 1492 environs.RegisterProvider("file-credentials", fp) 1493 1494 resetJujuXDGDataHome(c) 1495 _, err = cmdtesting.RunCommand( 1496 c, s.newBootstrapCommand(), "file-credentials", "ctrl", 1497 "--config", "default-series=precise", 1498 ) 1499 c.Assert(err, jc.ErrorIsNil) 1500 1501 // When credentials are "finalized" any credential attribute indicated 1502 // to be a file path is replaced by that file's contents. Here we check to see 1503 // that the state of the credential under test before finalization is 1504 // indeed the file path itself and that the state of the credential 1505 // after finalization is the contents of that file. 1506 c.Assert(unfinalizedCredential.Attributes()["file"], gc.Equals, tmpFile.Name()) 1507 c.Assert(finalizedCredential.Attributes()["file"], gc.Equals, string(contents)) 1508 } 1509 1510 func (s *BootstrapSuite) TestBootstrapProviderDetectRegionsInvalid(c *gc.C) { 1511 s.patchVersionAndSeries(c, "raring") 1512 ctx, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy/not-dummy", "ctrl") 1513 c.Assert(err, gc.Equals, cmd.ErrSilent) 1514 stderr := strings.Replace(cmdtesting.Stderr(ctx), "\n", "", -1) 1515 c.Assert(stderr, gc.Matches, `region "not-dummy" not found \(expected one of \["dummy"\]\)Specify an alternative region, or try "juju update-clouds".`) 1516 } 1517 1518 func (s *BootstrapSuite) TestBootstrapProviderManyCredentialsCloudNoAuthTypes(c *gc.C) { 1519 var bootstrap fakeBootstrapFuncs 1520 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1521 return &bootstrap 1522 }) 1523 1524 s.patchVersionAndSeries(c, "raring") 1525 s.store.Credentials = map[string]cloud.CloudCredential{ 1526 "many-credentials-no-auth-types": { 1527 AuthCredentials: map[string]cloud.Credential{"one": cloud.NewCredential("one", nil)}, 1528 }, 1529 } 1530 cmdtesting.RunCommand(c, s.newBootstrapCommand(), 1531 "many-credentials-no-auth-types", "ctrl", 1532 "--credential", "one", 1533 ) 1534 c.Assert(bootstrap.args.Cloud.AuthTypes, jc.SameContents, cloud.AuthTypes{"one", "two"}) 1535 } 1536 1537 func (s *BootstrapSuite) TestManyAvailableCredentialsNoneSpecified(c *gc.C) { 1538 var bootstrap fakeBootstrapFuncs 1539 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1540 return &bootstrap 1541 }) 1542 1543 s.patchVersionAndSeries(c, "raring") 1544 s.store.Credentials = map[string]cloud.CloudCredential{ 1545 "dummy": { 1546 AuthCredentials: map[string]cloud.Credential{ 1547 "one": cloud.NewCredential("one", nil), 1548 "two": cloud.NewCredential("two", nil), 1549 }, 1550 }, 1551 } 1552 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "ctrl") 1553 msg := strings.Replace(err.Error(), "\n", "", -1) 1554 c.Assert(msg, gc.Matches, "more than one credential is available.*") 1555 } 1556 1557 func (s *BootstrapSuite) TestBootstrapProviderDetectCloud(c *gc.C) { 1558 resetJujuXDGDataHome(c) 1559 1560 dummyProvider, err := environs.Provider("dummy") 1561 c.Assert(err, jc.ErrorIsNil) 1562 1563 var bootstrap fakeBootstrapFuncs 1564 bootstrap.newCloudDetector = func(p environs.EnvironProvider) (environs.CloudDetector, bool) { 1565 if p != dummyProvider { 1566 return nil, false 1567 } 1568 return cloudDetectorFunc(func() ([]cloud.Cloud, error) { 1569 return []cloud.Cloud{{ 1570 Name: "bruce", 1571 Type: "dummy", 1572 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 1573 Regions: []cloud.Region{{Name: "gazza", Endpoint: "endpoint"}}, 1574 }}, nil 1575 }), true 1576 } 1577 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1578 return &bootstrap 1579 }) 1580 1581 s.patchVersionAndSeries(c, "raring") 1582 cmdtesting.RunCommand(c, s.newBootstrapCommand(), "bruce", "ctrl") 1583 c.Assert(err, jc.ErrorIsNil) 1584 c.Assert(bootstrap.args.CloudRegion, gc.Equals, "gazza") 1585 c.Assert(bootstrap.args.CloudCredentialName, gc.Equals, "default") 1586 c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{ 1587 Name: "bruce", 1588 Type: "dummy", 1589 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 1590 Regions: []cloud.Region{{Name: "gazza", Endpoint: "endpoint"}}, 1591 }) 1592 } 1593 1594 func (s *BootstrapSuite) TestBootstrapProviderDetectRegions(c *gc.C) { 1595 resetJujuXDGDataHome(c) 1596 1597 var bootstrap fakeBootstrapFuncs 1598 bootstrap.cloudRegionDetector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) { 1599 return []cloud.Region{{Name: "bruce", Endpoint: "endpoint"}}, nil 1600 }) 1601 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1602 return &bootstrap 1603 }) 1604 1605 s.patchVersionAndSeries(c, "raring") 1606 cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "ctrl") 1607 c.Assert(bootstrap.args.CloudRegion, gc.Equals, "bruce") 1608 c.Assert(bootstrap.args.CloudCredentialName, gc.Equals, "default") 1609 sort.Sort(bootstrap.args.Cloud.AuthTypes) 1610 c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{ 1611 Name: "dummy", 1612 Type: "dummy", 1613 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 1614 Regions: []cloud.Region{{Name: "bruce", Endpoint: "endpoint"}}, 1615 }) 1616 } 1617 1618 func (s *BootstrapSuite) TestBootstrapProviderDetectNoRegions(c *gc.C) { 1619 resetJujuXDGDataHome(c) 1620 1621 var bootstrap fakeBootstrapFuncs 1622 bootstrap.cloudRegionDetector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) { 1623 return nil, errors.NotFoundf("regions") 1624 }) 1625 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1626 return &bootstrap 1627 }) 1628 1629 s.patchVersionAndSeries(c, "raring") 1630 cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "ctrl") 1631 c.Assert(bootstrap.args.CloudRegion, gc.Equals, "") 1632 sort.Sort(bootstrap.args.Cloud.AuthTypes) 1633 c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{ 1634 Name: "dummy", 1635 Type: "dummy", 1636 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 1637 }) 1638 } 1639 1640 func (s *BootstrapSuite) TestBootstrapProviderFinalizeCloud(c *gc.C) { 1641 resetJujuXDGDataHome(c) 1642 1643 var bootstrap fakeBootstrapFuncs 1644 bootstrap.cloudFinalizer = cloudFinalizerFunc(func(ctx environs.FinalizeCloudContext, in cloud.Cloud) (cloud.Cloud, error) { 1645 c.Assert(in, jc.DeepEquals, cloud.Cloud{ 1646 Name: "dummy", 1647 Type: "dummy", 1648 AuthTypes: []cloud.AuthType{"empty", "userpass"}, 1649 }) 1650 in.Name = "override" 1651 return in, nil 1652 }) 1653 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1654 return &bootstrap 1655 }) 1656 1657 s.patchVersionAndSeries(c, "raring") 1658 cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy", "ctrl") 1659 c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{ 1660 Name: "override", 1661 Type: "dummy", 1662 AuthTypes: []cloud.AuthType{"empty", "userpass"}, 1663 }) 1664 } 1665 1666 func (s *BootstrapSuite) TestBootstrapProviderCaseInsensitiveRegionCheck(c *gc.C) { 1667 s.patchVersionAndSeries(c, "raring") 1668 1669 var prepareParams bootstrap.PrepareParams 1670 s.PatchValue(&bootstrapPrepareController, func( 1671 _ bool, 1672 ctx environs.BootstrapContext, 1673 stor jujuclient.ClientStore, 1674 params bootstrap.PrepareParams, 1675 ) (environs.BootstrapEnviron, error) { 1676 prepareParams = params 1677 return nil, errors.New("mock-prepare") 1678 }) 1679 1680 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "dummy/DUMMY", "ctrl") 1681 c.Assert(err, gc.ErrorMatches, "mock-prepare") 1682 c.Assert(prepareParams.Cloud.Region, gc.Equals, "dummy") 1683 } 1684 1685 func (s *BootstrapSuite) TestBootstrapConfigFile(c *gc.C) { 1686 tmpdir := c.MkDir() 1687 configFile := filepath.Join(tmpdir, "config.yaml") 1688 err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644) 1689 c.Assert(err, jc.ErrorIsNil) 1690 1691 s.patchVersionAndSeries(c, "raring") 1692 _, err = cmdtesting.RunCommand( 1693 c, s.newBootstrapCommand(), "dummy", "ctrl", 1694 "--config", configFile, 1695 ) 1696 c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got string.*`) 1697 } 1698 1699 func (s *BootstrapSuite) TestBootstrapMultipleConfigFiles(c *gc.C) { 1700 tmpdir := c.MkDir() 1701 configFile1 := filepath.Join(tmpdir, "config-1.yaml") 1702 err := ioutil.WriteFile(configFile1, []byte( 1703 "controller: not-a-bool\nbroken: Bootstrap\n", 1704 ), 0644) 1705 c.Assert(err, jc.ErrorIsNil) 1706 configFile2 := filepath.Join(tmpdir, "config-2.yaml") 1707 err = ioutil.WriteFile(configFile2, []byte( 1708 "controller: false\n", 1709 ), 0644) 1710 1711 s.setupAutoUploadTest(c, "1.8.3", "raring") 1712 _, err = cmdtesting.RunCommand( 1713 c, s.newBootstrapCommand(), "dummy", "ctrl", 1714 "--auto-upgrade", 1715 // the second config file should replace attributes 1716 // with the same name from the first, but leave the 1717 // others alone. 1718 "--config", configFile1, 1719 "--config", configFile2, 1720 ) 1721 c.Assert(err, gc.Equals, cmd.ErrSilent) 1722 c.Check(s.tw.Log(), jc.LogMatches, []jc.SimpleMessage{ 1723 {loggo.ERROR, "failed to bootstrap model: dummy.Bootstrap is broken"}, 1724 }) 1725 } 1726 1727 func (s *BootstrapSuite) TestBootstrapConfigFileAndAdHoc(c *gc.C) { 1728 tmpdir := c.MkDir() 1729 configFile := filepath.Join(tmpdir, "config.yaml") 1730 err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644) 1731 c.Assert(err, jc.ErrorIsNil) 1732 1733 s.setupAutoUploadTest(c, "1.8.3", "raring") 1734 _, err = cmdtesting.RunCommand( 1735 c, s.newBootstrapCommand(), "dummy", "ctrl", 1736 "--auto-upgrade", 1737 // Configuration specified on the command line overrides 1738 // anything specified in files, no matter what the order. 1739 "--config", "controller=false", 1740 "--config", configFile, 1741 ) 1742 c.Assert(err, jc.ErrorIsNil) 1743 } 1744 1745 func (s *BootstrapSuite) TestBootstrapAutocertDNSNameDefaultPort(c *gc.C) { 1746 s.patchVersionAndSeries(c, "raring") 1747 var bootstrap fakeBootstrapFuncs 1748 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1749 return &bootstrap 1750 }) 1751 cmdtesting.RunCommand( 1752 c, s.newBootstrapCommand(), "dummy", "ctrl", 1753 "--config", "autocert-dns-name=foo.example", 1754 ) 1755 c.Assert(bootstrap.args.ControllerConfig.APIPort(), gc.Equals, 443) 1756 } 1757 1758 func (s *BootstrapSuite) TestBootstrapAutocertDNSNameExplicitAPIPort(c *gc.C) { 1759 s.patchVersionAndSeries(c, "raring") 1760 var bootstrap fakeBootstrapFuncs 1761 s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface { 1762 return &bootstrap 1763 }) 1764 cmdtesting.RunCommand( 1765 c, s.newBootstrapCommand(), "dummy", "ctrl", 1766 "--config", "autocert-dns-name=foo.example", 1767 "--config", "api-port=12345", 1768 ) 1769 c.Assert(bootstrap.args.ControllerConfig.APIPort(), gc.Equals, 12345) 1770 } 1771 1772 func (s *BootstrapSuite) TestBootstrapCloudConfigAndAdHoc(c *gc.C) { 1773 s.patchVersionAndSeries(c, "raring") 1774 _, err := cmdtesting.RunCommand( 1775 c, s.newBootstrapCommand(), "dummy-cloud-with-config", "ctrl", 1776 "--auto-upgrade", 1777 // Configuration specified on the command line overrides 1778 // anything specified in files, no matter what the order. 1779 "--config", "controller=not-a-bool", 1780 ) 1781 c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got .*`) 1782 } 1783 1784 func (s *BootstrapSuite) TestBootstrapPrintClouds(c *gc.C) { 1785 resetJujuXDGDataHome(c) 1786 s.store.Credentials = map[string]cloud.CloudCredential{ 1787 "aws": { 1788 DefaultRegion: "us-west-1", 1789 AuthCredentials: map[string]cloud.Credential{ 1790 "fred": {}, 1791 "mary": {}, 1792 }, 1793 }, 1794 "dummy-cloud": { 1795 DefaultRegion: "home", 1796 AuthCredentials: map[string]cloud.Credential{ 1797 "joe": {}, 1798 }, 1799 }, 1800 } 1801 defer func() { 1802 s.store = jujuclient.NewMemStore() 1803 }() 1804 1805 ctx, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "--clouds") 1806 c.Assert(err, jc.ErrorIsNil) 1807 c.Assert(cmdtesting.Stdout(ctx), jc.DeepEquals, ` 1808 You can bootstrap on these clouds. See ‘--regions <cloud>’ for all regions. 1809 Cloud Credentials Default Region 1810 aws fred us-west-1 1811 mary 1812 aws-china 1813 aws-gov 1814 azure 1815 azure-china 1816 cloudsigma 1817 google 1818 joyent 1819 oracle 1820 oracle-classic 1821 rackspace 1822 localhost 1823 dummy-cloud joe home 1824 dummy-cloud-dummy-region-config 1825 dummy-cloud-with-config 1826 dummy-cloud-with-region-config 1827 dummy-cloud-without-regions 1828 many-credentials-no-auth-types 1829 1830 You will need to have a credential if you want to bootstrap on a cloud, see 1831 ‘juju autoload-credentials’ and ‘juju add-credential’. The first credential 1832 listed is the default. Add more clouds with ‘juju add-cloud’. 1833 `[1:]) 1834 } 1835 1836 func (s *BootstrapSuite) TestBootstrapPrintCloudRegions(c *gc.C) { 1837 resetJujuXDGDataHome(c) 1838 ctx, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "--regions", "aws") 1839 c.Assert(err, jc.ErrorIsNil) 1840 c.Assert(cmdtesting.Stdout(ctx), jc.DeepEquals, ` 1841 Showing regions for aws: 1842 us-east-1 1843 us-east-2 1844 us-west-1 1845 us-west-2 1846 ca-central-1 1847 eu-west-1 1848 eu-west-2 1849 eu-west-3 1850 eu-central-1 1851 ap-south-1 1852 ap-southeast-1 1853 ap-southeast-2 1854 ap-northeast-1 1855 ap-northeast-2 1856 sa-east-1 1857 `[1:]) 1858 } 1859 1860 func (s *BootstrapSuite) TestBootstrapPrintCloudRegionsNoSuchCloud(c *gc.C) { 1861 resetJujuXDGDataHome(c) 1862 _, err := cmdtesting.RunCommand(c, s.newBootstrapCommand(), "--regions", "foo") 1863 c.Assert(err, gc.ErrorMatches, "cloud foo not found") 1864 } 1865 1866 func (s *BootstrapSuite) TestBootstrapSetsControllerOnBase(c *gc.C) { 1867 // This test ensures that the controller name is correctly set on 1868 // on the bootstrap commands embedded ModelCommandBase. Without 1869 // this, the concurrent bootstraps fail. 1870 // See https://pad.lv/1604223 1871 1872 s.setupAutoUploadTest(c, "1.8.3", "precise") 1873 1874 const controllerName = "dev" 1875 1876 // Record the controller name seen by ModelCommandBase at the end of bootstrap. 1877 var seenControllerName string 1878 s.PatchValue(&waitForAgentInitialisation, func(_ *cmd.Context, base *modelcmd.ModelCommandBase, controllerName, _ string) error { 1879 seenControllerName = controllerName 1880 return nil 1881 }) 1882 1883 // Run the bootstrap command in another goroutine, sending the 1884 // dummy provider ops to opc. 1885 errc := make(chan error, 1) 1886 opc := make(chan dummy.Operation) 1887 dummy.Listen(opc) 1888 go func() { 1889 defer func() { 1890 dummy.Listen(nil) 1891 close(opc) 1892 }() 1893 com := s.newBootstrapCommand() 1894 args := []string{"dummy", controllerName, "--auto-upgrade"} 1895 if err := cmdtesting.InitCommand(com, args); err != nil { 1896 errc <- err 1897 return 1898 } 1899 errc <- com.Run(cmdtesting.Context(c)) 1900 }() 1901 1902 // Wait for bootstrap to start. 1903 select { 1904 case op := <-opc: 1905 _, ok := op.(dummy.OpBootstrap) 1906 c.Assert(ok, jc.IsTrue) 1907 case <-time.After(coretesting.LongWait): 1908 c.Fatal("timed out") 1909 } 1910 1911 // Simulate another controller being bootstrapped during the 1912 // bootstrap. Changing the current controller shouldn't affect the 1913 // bootstrap process. 1914 c.Assert(s.store.AddController("another", jujuclient.ControllerDetails{ 1915 ControllerUUID: "uuid", 1916 CACert: "cert", 1917 }), jc.ErrorIsNil) 1918 c.Assert(s.store.SetCurrentController("another"), jc.ErrorIsNil) 1919 1920 // Let bootstrap finish. 1921 select { 1922 case op := <-opc: 1923 _, ok := op.(dummy.OpFinalizeBootstrap) 1924 c.Assert(ok, jc.IsTrue) 1925 case <-time.After(coretesting.LongWait): 1926 c.Fatal("timed out") 1927 } 1928 1929 // Ensure there were no errors reported. 1930 select { 1931 case err := <-errc: 1932 c.Assert(err, jc.ErrorIsNil) 1933 case <-time.After(coretesting.LongWait): 1934 c.Fatal("timed out") 1935 } 1936 1937 // Wait for the ops channel to close. 1938 select { 1939 case _, ok := <-opc: 1940 c.Assert(ok, jc.IsFalse) 1941 case <-time.After(coretesting.LongWait): 1942 c.Fatal("timed out") 1943 } 1944 1945 // Expect to see that the correct controller was in use at the end 1946 // of bootstrap. 1947 c.Assert(seenControllerName, gc.Equals, controllerName) 1948 } 1949 1950 // createToolsSource writes the mock tools and metadata into a temporary 1951 // directory and returns it. 1952 func createToolsSource(c *gc.C, versions []version.Binary) string { 1953 versionStrings := make([]string, len(versions)) 1954 for i, vers := range versions { 1955 versionStrings[i] = vers.String() 1956 } 1957 source := c.MkDir() 1958 toolstesting.MakeTools(c, source, "released", versionStrings) 1959 return source 1960 } 1961 1962 // resetJujuXDGDataHome restores an new, clean Juju home environment without tools. 1963 func resetJujuXDGDataHome(c *gc.C) { 1964 cloudsPath := cloud.JujuPersonalCloudsPath() 1965 err := ioutil.WriteFile(cloudsPath, []byte(` 1966 clouds: 1967 dummy-cloud: 1968 type: dummy 1969 regions: 1970 region-1: 1971 region-2: 1972 dummy-cloud-without-regions: 1973 type: dummy 1974 dummy-cloud-dummy-region-config: 1975 type: dummy 1976 regions: 1977 region-1: 1978 region-2: 1979 region-config: 1980 region-1: 1981 secret: region-test 1982 dummy-cloud-with-region-config: 1983 type: dummy 1984 regions: 1985 region-1: 1986 region-2: 1987 config: 1988 network: cloud-network 1989 region-config: 1990 region-1: 1991 network: region-network 1992 dummy-cloud-with-config: 1993 type: dummy 1994 config: 1995 broken: Bootstrap 1996 controller: not-a-bool 1997 use-floating-ip: true 1998 many-credentials-no-auth-types: 1999 type: many-credentials 2000 `[1:]), 0644) 2001 c.Assert(err, jc.ErrorIsNil) 2002 } 2003 2004 // checkTools check if the environment contains the passed envtools. 2005 func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) { 2006 list, err := envtools.FindTools( 2007 env, jujuversion.Current.Major, jujuversion.Current.Minor, []string{"released"}, coretools.Filter{}) 2008 c.Check(err, jc.ErrorIsNil) 2009 c.Logf("found: " + list.String()) 2010 urls := list.URLs() 2011 c.Check(urls, gc.HasLen, len(expected)) 2012 } 2013 2014 var ( 2015 v100d64 = version.MustParseBinary("1.0.0-raring-amd64") 2016 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 2017 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 2018 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 2019 v120d64 = version.MustParseBinary("1.2.0-raring-amd64") 2020 v120p64 = version.MustParseBinary("1.2.0-precise-amd64") 2021 v120q32 = version.MustParseBinary("1.2.0-quantal-i386") 2022 v120q64 = version.MustParseBinary("1.2.0-quantal-amd64") 2023 v120t32 = version.MustParseBinary("1.2.0-trusty-i386") 2024 v120t64 = version.MustParseBinary("1.2.0-trusty-amd64") 2025 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 2026 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 2027 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 2028 v100All = []version.Binary{ 2029 v100d64, v100p64, v100q64, v100q32, 2030 } 2031 v120All = []version.Binary{ 2032 v120d64, v120p64, v120q64, v120q32, v120t32, v120t64, 2033 } 2034 v190All = []version.Binary{ 2035 v190p32, v190q64, 2036 } 2037 v200All = []version.Binary{ 2038 v200p64, 2039 } 2040 vAll = joinBinaryVersions(v100All, v120All, v190All, v200All) 2041 ) 2042 2043 func joinBinaryVersions(versions ...[]version.Binary) []version.Binary { 2044 var all []version.Binary 2045 for _, versions := range versions { 2046 all = append(all, versions...) 2047 } 2048 return all 2049 } 2050 2051 // TODO(menn0): This fake BootstrapInterface implementation is 2052 // currently quite minimal but could be easily extended to cover more 2053 // test scenarios. This could help improve some of the tests in this 2054 // file which execute large amounts of external functionality. 2055 type fakeBootstrapFuncs struct { 2056 args bootstrap.BootstrapParams 2057 newCloudDetector func(environs.EnvironProvider) (environs.CloudDetector, bool) 2058 cloudRegionDetector environs.CloudRegionDetector 2059 cloudFinalizer environs.CloudFinalizer 2060 bootstrapF func(environs.BootstrapContext, environs.BootstrapEnviron, context.ProviderCallContext, bootstrap.BootstrapParams) error 2061 } 2062 2063 func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.BootstrapEnviron, callCtx context.ProviderCallContext, args bootstrap.BootstrapParams) error { 2064 if fake.bootstrapF != nil { 2065 return fake.bootstrapF(ctx, env, callCtx, args) 2066 } 2067 fake.args = args 2068 return nil 2069 } 2070 2071 func (fake *fakeBootstrapFuncs) CloudDetector(p environs.EnvironProvider) (environs.CloudDetector, bool) { 2072 if fake.newCloudDetector != nil { 2073 return fake.newCloudDetector(p) 2074 } 2075 return nil, false 2076 } 2077 2078 func (fake *fakeBootstrapFuncs) CloudRegionDetector(environs.EnvironProvider) (environs.CloudRegionDetector, bool) { 2079 detector := fake.cloudRegionDetector 2080 if detector == nil { 2081 detector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) { 2082 return nil, errors.NotFoundf("regions") 2083 }) 2084 } 2085 return detector, true 2086 } 2087 2088 func (fake *fakeBootstrapFuncs) CloudFinalizer(environs.EnvironProvider) (environs.CloudFinalizer, bool) { 2089 finalizer := fake.cloudFinalizer 2090 return finalizer, finalizer != nil 2091 } 2092 2093 type noCloudRegionDetectionProvider struct { 2094 environs.CloudEnvironProvider 2095 } 2096 2097 type noCloudRegionsProvider struct { 2098 environs.CloudEnvironProvider 2099 } 2100 2101 func (noCloudRegionsProvider) DetectRegions() ([]cloud.Region, error) { 2102 return nil, errors.NotFoundf("regions") 2103 } 2104 2105 func (noCloudRegionsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 2106 return map[cloud.AuthType]cloud.CredentialSchema{cloud.EmptyAuthType: {}} 2107 } 2108 2109 type noCredentialsProvider struct { 2110 environs.CloudEnvironProvider 2111 } 2112 2113 func (noCredentialsProvider) DetectRegions() ([]cloud.Region, error) { 2114 return []cloud.Region{{Name: "region"}}, nil 2115 } 2116 2117 func (noCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) { 2118 return nil, errors.NotFoundf("credentials") 2119 } 2120 2121 func (noCredentialsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 2122 return nil 2123 } 2124 2125 type manyCredentialsProvider struct { 2126 environs.CloudEnvironProvider 2127 } 2128 2129 func (manyCredentialsProvider) DetectRegions() ([]cloud.Region, error) { 2130 return []cloud.Region{{Name: "region"}}, nil 2131 } 2132 2133 func (manyCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) { 2134 return &cloud.CloudCredential{ 2135 AuthCredentials: map[string]cloud.Credential{ 2136 "one": cloud.NewCredential("one", nil), 2137 "two": {}, 2138 }, 2139 }, nil 2140 } 2141 2142 func (manyCredentialsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 2143 return map[cloud.AuthType]cloud.CredentialSchema{"one": {}, "two": {}} 2144 } 2145 2146 type cloudDetectorFunc func() ([]cloud.Cloud, error) 2147 2148 type fileCredentialProvider struct { 2149 environs.CloudEnvironProvider 2150 testFileName string 2151 unFinalizedCredential *cloud.Credential 2152 finalizedCredential *cloud.Credential 2153 } 2154 2155 func (f fileCredentialProvider) DetectRegions() ([]cloud.Region, error) { 2156 return []cloud.Region{{Name: "region"}}, nil 2157 } 2158 2159 func (f fileCredentialProvider) DetectCredentials() (*cloud.CloudCredential, error) { 2160 credential := cloud.NewCredential(cloud.JSONFileAuthType, 2161 map[string]string{"file": f.testFileName}) 2162 cc := &cloud.CloudCredential{AuthCredentials: map[string]cloud.Credential{ 2163 "cred": credential, 2164 }} 2165 *f.unFinalizedCredential = credential 2166 return cc, nil 2167 } 2168 2169 func (fileCredentialProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { 2170 return map[cloud.AuthType]cloud.CredentialSchema{cloud.JSONFileAuthType: {cloud.NamedCredentialAttr{ 2171 Name: "file", 2172 CredentialAttr: cloud.CredentialAttr{ 2173 FilePath: true, 2174 }}, 2175 }} 2176 } 2177 2178 func (f fileCredentialProvider) FinalizeCredential(_ environs.FinalizeCredentialContext, fp environs.FinalizeCredentialParams) (*cloud.Credential, error) { 2179 *f.finalizedCredential = fp.Credential 2180 return &fp.Credential, nil 2181 } 2182 2183 func (c cloudDetectorFunc) DetectCloud(name string) (cloud.Cloud, error) { 2184 clouds, err := c.DetectClouds() 2185 if err != nil { 2186 return cloud.Cloud{}, err 2187 } 2188 for _, cloud := range clouds { 2189 if cloud.Name == name { 2190 return cloud, nil 2191 } 2192 } 2193 return cloud.Cloud{}, errors.NotFoundf("cloud %s", name) 2194 } 2195 2196 func (c cloudDetectorFunc) DetectClouds() ([]cloud.Cloud, error) { 2197 return c() 2198 } 2199 2200 type cloudRegionDetectorFunc func() ([]cloud.Region, error) 2201 2202 func (c cloudRegionDetectorFunc) DetectRegions() ([]cloud.Region, error) { 2203 return c() 2204 } 2205 2206 type cloudFinalizerFunc func(environs.FinalizeCloudContext, cloud.Cloud) (cloud.Cloud, error) 2207 2208 func (c cloudFinalizerFunc) FinalizeCloud(ctx environs.FinalizeCloudContext, in cloud.Cloud) (cloud.Cloud, error) { 2209 return c(ctx, in) 2210 }