github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/commands/bootstrap_test.go (about)

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