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  }