github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/bootstrap_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	gitjujutesting "github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/cmd/envcmd"
    19  	cmdtesting "github.com/juju/juju/cmd/testing"
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/bootstrap"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/environs/configstore"
    25  	"github.com/juju/juju/environs/filestorage"
    26  	"github.com/juju/juju/environs/imagemetadata"
    27  	"github.com/juju/juju/environs/simplestreams"
    28  	"github.com/juju/juju/environs/sync"
    29  	envtesting "github.com/juju/juju/environs/testing"
    30  	envtools "github.com/juju/juju/environs/tools"
    31  	toolstesting "github.com/juju/juju/environs/tools/testing"
    32  	"github.com/juju/juju/instance"
    33  	"github.com/juju/juju/juju"
    34  	"github.com/juju/juju/juju/arch"
    35  	"github.com/juju/juju/network"
    36  	"github.com/juju/juju/provider/dummy"
    37  	coretesting "github.com/juju/juju/testing"
    38  	coretools "github.com/juju/juju/tools"
    39  	"github.com/juju/juju/version"
    40  )
    41  
    42  type BootstrapSuite struct {
    43  	coretesting.FakeJujuHomeSuite
    44  	gitjujutesting.MgoSuite
    45  	envtesting.ToolsFixture
    46  }
    47  
    48  var _ = gc.Suite(&BootstrapSuite{})
    49  
    50  func (s *BootstrapSuite) SetUpSuite(c *gc.C) {
    51  	s.FakeJujuHomeSuite.SetUpSuite(c)
    52  	s.MgoSuite.SetUpSuite(c)
    53  }
    54  
    55  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    56  	s.FakeJujuHomeSuite.SetUpTest(c)
    57  	s.MgoSuite.SetUpTest(c)
    58  	s.ToolsFixture.SetUpTest(c)
    59  
    60  	// Set version.Current to a known value, for which we
    61  	// will make tools available. Individual tests may
    62  	// override this.
    63  	s.PatchValue(&version.Current, v100p64)
    64  
    65  	// Set up a local source with tools.
    66  	sourceDir := createToolsSource(c, vAll)
    67  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
    68  
    69  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
    70  }
    71  
    72  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
    73  	s.MgoSuite.TearDownSuite(c)
    74  	s.FakeJujuHomeSuite.TearDownSuite(c)
    75  }
    76  
    77  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
    78  	s.ToolsFixture.TearDownTest(c)
    79  	s.MgoSuite.TearDownTest(c)
    80  	s.FakeJujuHomeSuite.TearDownTest(c)
    81  	dummy.Reset()
    82  }
    83  
    84  func (s *BootstrapSuite) TestRunTests(c *gc.C) {
    85  	for i, test := range bootstrapTests {
    86  		c.Logf("\ntest %d: %s", i, test.info)
    87  		restore := s.run(c, test)
    88  		restore()
    89  	}
    90  }
    91  
    92  type bootstrapTest struct {
    93  	info string
    94  	// binary version string used to set version.Current
    95  	version string
    96  	sync    bool
    97  	args    []string
    98  	err     string
    99  	// binary version string for expected tools; if set, no default tools
   100  	// will be uploaded before running the test.
   101  	upload      string
   102  	constraints constraints.Value
   103  	placement   string
   104  	hostArch    string
   105  	keepBroken  bool
   106  }
   107  
   108  func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) (restore gitjujutesting.Restorer) {
   109  	// Create home with dummy provider and remove all
   110  	// of its envtools.
   111  	env := resetJujuHome(c, "peckham")
   112  
   113  	// Although we're testing PrepareEndpointsForCaching interactions
   114  	// separately in the juju package, here we just ensure it gets
   115  	// called with the right arguments.
   116  	prepareCalled := false
   117  	addrConnectedTo := "localhost:17070"
   118  	restore = gitjujutesting.PatchValue(
   119  		&prepareEndpointsForCaching,
   120  		func(info configstore.EnvironInfo, hps [][]network.HostPort, addr network.HostPort) (_, _ []string, _ bool) {
   121  			prepareCalled = true
   122  			addrs, hosts, changed := juju.PrepareEndpointsForCaching(info, hps, addr)
   123  			// Because we're bootstrapping the addresses will always
   124  			// change, as there's no .jenv file saved yet.
   125  			c.Assert(changed, jc.IsTrue)
   126  			return addrs, hosts, changed
   127  		},
   128  	)
   129  
   130  	if test.version != "" {
   131  		useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1)
   132  		origVersion := version.Current
   133  		version.Current = version.MustParseBinary(useVersion)
   134  		restore = restore.Add(func() {
   135  			version.Current = origVersion
   136  		})
   137  	}
   138  
   139  	if test.hostArch != "" {
   140  		origArch := arch.HostArch
   141  		arch.HostArch = func() string {
   142  			return test.hostArch
   143  		}
   144  		restore = restore.Add(func() {
   145  			arch.HostArch = origArch
   146  		})
   147  	}
   148  
   149  	// Run command and check for uploads.
   150  	opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), test.args...)
   151  	// Check for remaining operations/errors.
   152  	if test.err != "" {
   153  		err := <-errc
   154  		stripped := strings.Replace(err.Error(), "\n", "", -1)
   155  		c.Check(stripped, gc.Matches, test.err)
   156  		return restore
   157  	}
   158  	if !c.Check(<-errc, gc.IsNil) {
   159  		return restore
   160  	}
   161  
   162  	opBootstrap := (<-opc).(dummy.OpBootstrap)
   163  	c.Check(opBootstrap.Env, gc.Equals, "peckham")
   164  	c.Check(opBootstrap.Args.Constraints, gc.DeepEquals, test.constraints)
   165  	c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement)
   166  
   167  	opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap)
   168  	c.Check(opFinalizeBootstrap.Env, gc.Equals, "peckham")
   169  	c.Check(opFinalizeBootstrap.MachineConfig.Tools, gc.NotNil)
   170  	if test.upload != "" {
   171  		c.Check(opFinalizeBootstrap.MachineConfig.Tools.Version.String(), gc.Equals, test.upload)
   172  	}
   173  
   174  	store, err := configstore.Default()
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	// Check a CA cert/key was generated by reloading the environment.
   177  	env, err = environs.NewFromName("peckham", store)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	_, hasCert := env.Config().CACert()
   180  	c.Check(hasCert, jc.IsTrue)
   181  	_, hasKey := env.Config().CAPrivateKey()
   182  	c.Check(hasKey, jc.IsTrue)
   183  	info, err := store.ReadInfo("peckham")
   184  	c.Assert(err, jc.ErrorIsNil)
   185  	c.Assert(info, gc.NotNil)
   186  	c.Assert(prepareCalled, jc.IsTrue)
   187  	c.Assert(info.APIEndpoint().Addresses, gc.DeepEquals, []string{addrConnectedTo})
   188  	return restore
   189  }
   190  
   191  var bootstrapTests = []bootstrapTest{{
   192  	info: "no args, no error, no upload, no constraints",
   193  }, {
   194  	info: "bad --constraints",
   195  	args: []string{"--constraints", "bad=wrong"},
   196  	err:  `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`,
   197  }, {
   198  	info: "conflicting --constraints",
   199  	args: []string{"--constraints", "instance-type=foo mem=4G"},
   200  	err:  `failed to bootstrap environment: ambiguous constraints: "instance-type" overlaps with "mem"`,
   201  }, {
   202  	info: "bad --series",
   203  	args: []string{"--series", "1bad1"},
   204  	err:  `invalid value "1bad1" for flag --series: invalid series name "1bad1"`,
   205  }, {
   206  	info: "lonely --series",
   207  	args: []string{"--series", "fine"},
   208  	err:  `--series requires --upload-tools`,
   209  }, {
   210  	info: "lonely --upload-series",
   211  	args: []string{"--upload-series", "fine"},
   212  	err:  `--upload-series requires --upload-tools`,
   213  }, {
   214  	info: "--upload-series with --series",
   215  	args: []string{"--upload-tools", "--upload-series", "foo", "--series", "bar"},
   216  	err:  `--upload-series and --series can't be used together`,
   217  }, {
   218  	info:    "bad environment",
   219  	version: "1.2.3-%LTS%-amd64",
   220  	args:    []string{"-e", "brokenenv"},
   221  	err:     `failed to bootstrap environment: dummy.Bootstrap is broken`,
   222  }, {
   223  	info:        "constraints",
   224  	args:        []string{"--constraints", "mem=4G cpu-cores=4"},
   225  	constraints: constraints.MustParse("mem=4G cpu-cores=4"),
   226  }, {
   227  	info:        "unsupported constraint passed through but no error",
   228  	args:        []string{"--constraints", "mem=4G cpu-cores=4 cpu-power=10"},
   229  	constraints: constraints.MustParse("mem=4G cpu-cores=4 cpu-power=10"),
   230  }, {
   231  	info:        "--upload-tools uses arch from constraint if it matches current version",
   232  	version:     "1.3.3-saucy-ppc64el",
   233  	hostArch:    "ppc64el",
   234  	args:        []string{"--upload-tools", "--constraints", "arch=ppc64el"},
   235  	upload:      "1.3.3.1-raring-ppc64el", // from version.Current
   236  	constraints: constraints.MustParse("arch=ppc64el"),
   237  }, {
   238  	info:     "--upload-tools rejects mismatched arch",
   239  	version:  "1.3.3-saucy-amd64",
   240  	hostArch: "amd64",
   241  	args:     []string{"--upload-tools", "--constraints", "arch=ppc64el"},
   242  	err:      `failed to bootstrap environment: cannot build tools for "ppc64el" using a machine running on "amd64"`,
   243  }, {
   244  	info:     "--upload-tools rejects non-supported arch",
   245  	version:  "1.3.3-saucy-arm64",
   246  	hostArch: "arm64",
   247  	args:     []string{"--upload-tools"},
   248  	err:      `failed to bootstrap environment: environment "peckham" of type dummy does not support instances running on "arm64"`,
   249  }, {
   250  	info:    "--upload-tools always bumps build number",
   251  	version: "1.2.3.4-raring-amd64",
   252  	args:    []string{"--upload-tools"},
   253  	upload:  "1.2.3.5-raring-amd64",
   254  }, {
   255  	info:      "placement",
   256  	args:      []string{"--to", "something"},
   257  	placement: "something",
   258  }, {
   259  	info:       "keep broken",
   260  	args:       []string{"--keep-broken"},
   261  	keepBroken: true,
   262  }, {
   263  	info: "additional args",
   264  	args: []string{"anything", "else"},
   265  	err:  `unrecognized args: \["anything" "else"\]`,
   266  }}
   267  
   268  func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) {
   269  	env := resetJujuHome(c, "devenv")
   270  	defaultSeriesVersion := version.Current
   271  	defaultSeriesVersion.Series = config.PreferredSeries(env.Config())
   272  	// Force a dev version by having a non zero build number.
   273  	// This is because we have not uploaded any tools and auto
   274  	// upload is only enabled for dev versions.
   275  	defaultSeriesVersion.Build = 1234
   276  	s.PatchValue(&version.Current, defaultSeriesVersion)
   277  
   278  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv")
   279  	c.Assert(err, jc.ErrorIsNil)
   280  
   281  	_, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv")
   282  	c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped")
   283  }
   284  
   285  type mockBootstrapInstance struct {
   286  	instance.Instance
   287  }
   288  
   289  func (*mockBootstrapInstance) Addresses() ([]network.Address, error) {
   290  	return []network.Address{{Value: "localhost"}}, nil
   291  }
   292  
   293  func (s *BootstrapSuite) TestSeriesDeprecation(c *gc.C) {
   294  	ctx := s.checkSeriesArg(c, "--series")
   295  	c.Check(coretesting.Stderr(ctx), gc.Equals,
   296  		"Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.\n")
   297  }
   298  
   299  func (s *BootstrapSuite) TestUploadSeriesDeprecation(c *gc.C) {
   300  	ctx := s.checkSeriesArg(c, "--upload-series")
   301  	c.Check(coretesting.Stderr(ctx), gc.Equals,
   302  		"Use of --upload-series is obsolete. --upload-tools now expands to all supported series of the same operating system.\n")
   303  }
   304  
   305  func (s *BootstrapSuite) checkSeriesArg(c *gc.C, argVariant string) *cmd.Context {
   306  	_bootstrap := &fakeBootstrapFuncs{}
   307  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   308  		return _bootstrap
   309  	})
   310  	resetJujuHome(c, "devenv")
   311  	s.PatchValue(&allInstances, func(environ environs.Environ) ([]instance.Instance, error) {
   312  		return []instance.Instance{&mockBootstrapInstance{}}, nil
   313  	})
   314  
   315  	ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--upload-tools", argVariant, "foo,bar")
   316  
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	return ctx
   319  }
   320  
   321  // In the case where we cannot examine an environment, we want the
   322  // error to propagate back up to the user.
   323  func (s *BootstrapSuite) TestBootstrapPropagatesEnvErrors(c *gc.C) {
   324  
   325  	const envName = "devenv"
   326  	env := resetJujuHome(c, envName)
   327  	defaultSeriesVersion := version.Current
   328  	defaultSeriesVersion.Series = config.PreferredSeries(env.Config())
   329  	// Force a dev version by having a non zero build number.
   330  	// This is because we have not uploaded any tools and auto
   331  	// upload is only enabled for dev versions.
   332  	defaultSeriesVersion.Build = 1234
   333  	s.PatchValue(&version.Current, defaultSeriesVersion)
   334  
   335  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", envName)
   336  	c.Assert(err, jc.ErrorIsNil)
   337  
   338  	// Change permissions on the jenv file to simulate some kind of
   339  	// unexpected error when trying to read info from the environment
   340  	jenvFile := gitjujutesting.HomePath(".juju", "environments", envName+".jenv")
   341  	err = os.Chmod(jenvFile, os.FileMode(0200))
   342  	c.Assert(err, jc.ErrorIsNil)
   343  
   344  	// The second bootstrap should fail b/c of the propogated error
   345  	_, err = coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", envName)
   346  	c.Assert(err, gc.ErrorMatches, "there was an issue examining the environment: .*")
   347  }
   348  
   349  func (s *BootstrapSuite) TestBootstrapCleansUpIfEnvironPrepFails(c *gc.C) {
   350  
   351  	cleanupRan := false
   352  
   353  	s.PatchValue(
   354  		&environFromName,
   355  		func(
   356  			*cmd.Context,
   357  			string,
   358  			string,
   359  			func(environs.Environ) error,
   360  		) (environs.Environ, func(), error) {
   361  			return nil, func() { cleanupRan = true }, fmt.Errorf("mock")
   362  		},
   363  	)
   364  
   365  	ctx := coretesting.Context(c)
   366  	_, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham")
   367  	c.Check(<-errc, gc.Not(gc.IsNil))
   368  	c.Check(cleanupRan, jc.IsTrue)
   369  }
   370  
   371  // When attempting to bootstrap, check that when prepare errors out,
   372  // the code cleans up the created jenv file, but *not* any existing
   373  // environment that may have previously been bootstrapped.
   374  func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) {
   375  
   376  	destroyedEnvRan := false
   377  	destroyedInfoRan := false
   378  
   379  	// Mock functions
   380  	mockDestroyPreparedEnviron := func(
   381  		*cmd.Context,
   382  		environs.Environ,
   383  		configstore.Storage,
   384  		string,
   385  	) {
   386  		destroyedEnvRan = true
   387  	}
   388  
   389  	mockDestroyEnvInfo := func(
   390  		ctx *cmd.Context,
   391  		cfgName string,
   392  		store configstore.Storage,
   393  		action string,
   394  	) {
   395  		destroyedInfoRan = true
   396  	}
   397  
   398  	mockEnvironFromName := func(
   399  		ctx *cmd.Context,
   400  		envName string,
   401  		action string,
   402  		_ func(environs.Environ) error,
   403  	) (environs.Environ, func(), error) {
   404  		// Always show that the environment is bootstrapped.
   405  		return environFromNameProductionFunc(
   406  			ctx,
   407  			envName,
   408  			action,
   409  			func(env environs.Environ) error {
   410  				return environs.ErrAlreadyBootstrapped
   411  			})
   412  	}
   413  
   414  	mockPrepare := func(
   415  		string,
   416  		environs.BootstrapContext,
   417  		configstore.Storage,
   418  	) (environs.Environ, error) {
   419  		return nil, fmt.Errorf("mock-prepare")
   420  	}
   421  
   422  	// Simulation: prepare should fail and we should only clean up the
   423  	// jenv file. Any existing environment should not be destroyed.
   424  	s.PatchValue(&destroyPreparedEnviron, mockDestroyPreparedEnviron)
   425  	s.PatchValue(&environFromName, mockEnvironFromName)
   426  	s.PatchValue(&environs.PrepareFromName, mockPrepare)
   427  	s.PatchValue(&destroyEnvInfo, mockDestroyEnvInfo)
   428  
   429  	ctx := coretesting.Context(c)
   430  	_, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "peckham")
   431  	c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$")
   432  	c.Check(destroyedEnvRan, jc.IsFalse)
   433  	c.Check(destroyedInfoRan, jc.IsTrue)
   434  }
   435  
   436  func (s *BootstrapSuite) TestBootstrapJenvWarning(c *gc.C) {
   437  	env := resetJujuHome(c, "devenv")
   438  	defaultSeriesVersion := version.Current
   439  	defaultSeriesVersion.Series = config.PreferredSeries(env.Config())
   440  	// Force a dev version by having a non zero build number.
   441  	// This is because we have not uploaded any tools and auto
   442  	// upload is only enabled for dev versions.
   443  	defaultSeriesVersion.Build = 1234
   444  	s.PatchValue(&version.Current, defaultSeriesVersion)
   445  
   446  	store, err := configstore.Default()
   447  	c.Assert(err, jc.ErrorIsNil)
   448  	ctx := coretesting.Context(c)
   449  	environs.PrepareFromName("devenv", envcmd.BootstrapContext(ctx), store)
   450  
   451  	logger := "jenv.warning.test"
   452  	var testWriter loggo.TestWriter
   453  	loggo.RegisterWriter(logger, &testWriter, loggo.WARNING)
   454  	defer loggo.RemoveWriter(logger)
   455  
   456  	_, errc := cmdtesting.RunCommand(ctx, envcmd.Wrap(new(BootstrapCommand)), "-e", "devenv")
   457  	c.Assert(<-errc, gc.IsNil)
   458  	c.Assert(testWriter.Log(), jc.LogMatches, []string{"ignoring environments.yaml: using bootstrap config in .*"})
   459  }
   460  
   461  func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) {
   462  	s.PatchValue(&version.Current.Number, version.MustParse("1.2.0"))
   463  	env := resetJujuHome(c, "devenv")
   464  
   465  	// Bootstrap the environment with an invalid source.
   466  	// The command returns with an error.
   467  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", c.MkDir())
   468  	c.Check(err, gc.ErrorMatches, `failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*`)
   469  
   470  	// Now check that there are no tools available.
   471  	_, err = envtools.FindTools(
   472  		env, version.Current.Major, version.Current.Minor, coretools.Filter{})
   473  	c.Assert(err, gc.FitsTypeOf, errors.NotFoundf(""))
   474  }
   475  
   476  // createImageMetadata creates some image metadata in a local directory.
   477  func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) {
   478  	// Generate some image metadata.
   479  	im := []*imagemetadata.ImageMetadata{
   480  		{
   481  			Id:         "1234",
   482  			Arch:       "amd64",
   483  			Version:    "13.04",
   484  			RegionName: "region",
   485  			Endpoint:   "endpoint",
   486  		},
   487  	}
   488  	cloudSpec := &simplestreams.CloudSpec{
   489  		Region:   "region",
   490  		Endpoint: "endpoint",
   491  	}
   492  	sourceDir := c.MkDir()
   493  	sourceStor, err := filestorage.NewFileStorageWriter(sourceDir)
   494  	c.Assert(err, jc.ErrorIsNil)
   495  	err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor)
   496  	c.Assert(err, jc.ErrorIsNil)
   497  	return sourceDir, im
   498  }
   499  
   500  func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) {
   501  	sourceDir, _ := createImageMetadata(c)
   502  	resetJujuHome(c, "devenv")
   503  
   504  	_bootstrap := &fakeBootstrapFuncs{}
   505  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   506  		return _bootstrap
   507  	})
   508  
   509  	coretesting.RunCommand(
   510  		c, envcmd.Wrap(&BootstrapCommand{}),
   511  		"--metadata-source", sourceDir, "--constraints", "mem=4G",
   512  	)
   513  	c.Assert(_bootstrap.args.MetadataDir, gc.Equals, sourceDir)
   514  }
   515  
   516  func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) {
   517  	sourceDir := createToolsSource(c, vAll)
   518  	s.PatchValue(&version.Current.Number, version.MustParse("1.2.0"))
   519  	env := resetJujuHome(c, "peckham")
   520  
   521  	// Bootstrap the environment with the valid source.
   522  	// The bootstrapping has to show no error, because the tools
   523  	// are automatically synchronized.
   524  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "--metadata-source", sourceDir)
   525  	c.Assert(err, jc.ErrorIsNil)
   526  
   527  	// Now check the available tools which are the 1.2.0 envtools.
   528  	checkTools(c, env, v120All)
   529  }
   530  
   531  func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, series string) environs.Environ {
   532  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
   533  	sourceDir := createToolsSource(c, vAll)
   534  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
   535  
   536  	// Change the tools location to be the test location and also
   537  	// the version and ensure their later restoring.
   538  	// Set the current version to be something for which there are no tools
   539  	// so we can test that an upload is forced.
   540  	s.PatchValue(&version.Current, version.MustParseBinary(vers+"-"+series+"-"+version.Current.Arch))
   541  
   542  	// Create home with dummy provider and remove all
   543  	// of its envtools.
   544  	return resetJujuHome(c, "devenv")
   545  }
   546  
   547  func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) {
   548  	s.PatchValue(&version.Current.Series, config.LatestLtsSeries())
   549  	s.setupAutoUploadTest(c, "1.7.3", "quantal")
   550  	// Run command and check for that upload has been run for tools matching
   551  	// the current juju version.
   552  	opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "devenv")
   553  	c.Assert(<-errc, gc.IsNil)
   554  	c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, "devenv")
   555  	mcfg := (<-opc).(dummy.OpFinalizeBootstrap).MachineConfig
   556  	c.Assert(mcfg, gc.NotNil)
   557  	c.Assert(mcfg.Tools.Version.String(), gc.Equals, "1.7.3.1-raring-"+version.Current.Arch)
   558  }
   559  
   560  func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) {
   561  	s.setupAutoUploadTest(c, "1.8.3", "precise")
   562  	_, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)))
   563  	err := <-errc
   564  	c.Assert(err, gc.ErrorMatches,
   565  		"failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*")
   566  }
   567  
   568  func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) {
   569  	s.setupAutoUploadTest(c, "1.8.3", "precise")
   570  
   571  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}))
   572  	c.Assert(err, gc.ErrorMatches,
   573  		"failed to bootstrap environment: Juju cannot bootstrap because no tools are available for your environment(.|\n)*")
   574  }
   575  
   576  func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) {
   577  
   578  	buildToolsTarballAlwaysFails := func(forceVersion *version.Number, stream string) (*sync.BuiltTools, error) {
   579  		return nil, fmt.Errorf("an error")
   580  	}
   581  
   582  	s.setupAutoUploadTest(c, "1.7.3", "precise")
   583  	s.PatchValue(&sync.BuildToolsTarball, buildToolsTarballAlwaysFails)
   584  
   585  	ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&BootstrapCommand{}), "-e", "devenv")
   586  
   587  	c.Check(coretesting.Stderr(ctx), gc.Equals, fmt.Sprintf(`
   588  Bootstrapping environment "devenv"
   589  Starting new instance for initial state server
   590  Building tools to upload (1.7.3.1-raring-%s)
   591  `[1:], version.Current.Arch))
   592  	c.Check(err, gc.ErrorMatches, "failed to bootstrap environment: cannot upload bootstrap tools: an error")
   593  }
   594  
   595  func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) {
   596  	resetJujuHome(c, "devenv")
   597  	devVersion := version.Current
   598  	// Force a dev version by having a non zero build number.
   599  	// This is because we have not uploaded any tools and auto
   600  	// upload is only enabled for dev versions.
   601  	devVersion.Build = 1234
   602  	s.PatchValue(&version.Current, devVersion)
   603  	opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv")
   604  	err := <-errc
   605  	c.Assert(err, gc.ErrorMatches, "failed to bootstrap environment: dummy.Bootstrap is broken")
   606  	var opDestroy *dummy.OpDestroy
   607  	for opDestroy == nil {
   608  		select {
   609  		case op := <-opc:
   610  			switch op := op.(type) {
   611  			case dummy.OpDestroy:
   612  				opDestroy = &op
   613  			}
   614  		default:
   615  			c.Error("expected call to env.Destroy")
   616  			return
   617  		}
   618  	}
   619  	c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken")
   620  }
   621  
   622  func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) {
   623  	resetJujuHome(c, "devenv")
   624  	devVersion := version.Current
   625  	// Force a dev version by having a non zero build number.
   626  	// This is because we have not uploaded any tools and auto
   627  	// upload is only enabled for dev versions.
   628  	devVersion.Build = 1234
   629  	s.PatchValue(&version.Current, devVersion)
   630  	opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), envcmd.Wrap(new(BootstrapCommand)), "-e", "brokenenv", "--keep-broken")
   631  	err := <-errc
   632  	c.Assert(err, gc.ErrorMatches, "failed to bootstrap environment: dummy.Bootstrap is broken")
   633  	done := false
   634  	for !done {
   635  		select {
   636  		case op, ok := <-opc:
   637  			if !ok {
   638  				done = true
   639  				break
   640  			}
   641  			switch op.(type) {
   642  			case dummy.OpDestroy:
   643  				c.Error("unexpected call to env.Destroy")
   644  				break
   645  			}
   646  		default:
   647  			break
   648  		}
   649  	}
   650  }
   651  
   652  // createToolsSource writes the mock tools and metadata into a temporary
   653  // directory and returns it.
   654  func createToolsSource(c *gc.C, versions []version.Binary) string {
   655  	versionStrings := make([]string, len(versions))
   656  	for i, vers := range versions {
   657  		versionStrings[i] = vers.String()
   658  	}
   659  	source := c.MkDir()
   660  	toolstesting.MakeTools(c, source, "released", versionStrings)
   661  	return source
   662  }
   663  
   664  // resetJujuHome restores an new, clean Juju home environment without tools.
   665  func resetJujuHome(c *gc.C, envName string) environs.Environ {
   666  	jenvDir := gitjujutesting.HomePath(".juju", "environments")
   667  	err := os.RemoveAll(jenvDir)
   668  	c.Assert(err, jc.ErrorIsNil)
   669  	coretesting.WriteEnvironments(c, envConfig)
   670  	dummy.Reset()
   671  	store, err := configstore.Default()
   672  	c.Assert(err, jc.ErrorIsNil)
   673  	env, err := environs.PrepareFromName(envName, envcmd.BootstrapContext(cmdtesting.NullContext(c)), store)
   674  	c.Assert(err, jc.ErrorIsNil)
   675  	return env
   676  }
   677  
   678  // checkTools check if the environment contains the passed envtools.
   679  func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) {
   680  	list, err := envtools.FindTools(
   681  		env, version.Current.Major, version.Current.Minor, coretools.Filter{})
   682  	c.Check(err, jc.ErrorIsNil)
   683  	c.Logf("found: " + list.String())
   684  	urls := list.URLs()
   685  	c.Check(urls, gc.HasLen, len(expected))
   686  }
   687  
   688  var (
   689  	v100d64 = version.MustParseBinary("1.0.0-raring-amd64")
   690  	v100p64 = version.MustParseBinary("1.0.0-precise-amd64")
   691  	v100q32 = version.MustParseBinary("1.0.0-quantal-i386")
   692  	v100q64 = version.MustParseBinary("1.0.0-quantal-amd64")
   693  	v120d64 = version.MustParseBinary("1.2.0-raring-amd64")
   694  	v120p64 = version.MustParseBinary("1.2.0-precise-amd64")
   695  	v120q32 = version.MustParseBinary("1.2.0-quantal-i386")
   696  	v120q64 = version.MustParseBinary("1.2.0-quantal-amd64")
   697  	v120t32 = version.MustParseBinary("1.2.0-trusty-i386")
   698  	v120t64 = version.MustParseBinary("1.2.0-trusty-amd64")
   699  	v190p32 = version.MustParseBinary("1.9.0-precise-i386")
   700  	v190q64 = version.MustParseBinary("1.9.0-quantal-amd64")
   701  	v200p64 = version.MustParseBinary("2.0.0-precise-amd64")
   702  	v100All = []version.Binary{
   703  		v100d64, v100p64, v100q64, v100q32,
   704  	}
   705  	v120All = []version.Binary{
   706  		v120d64, v120p64, v120q64, v120q32, v120t32, v120t64,
   707  	}
   708  	v190All = []version.Binary{
   709  		v190p32, v190q64,
   710  	}
   711  	v200All = []version.Binary{
   712  		v200p64,
   713  	}
   714  	vAll = joinBinaryVersions(v100All, v120All, v190All, v200All)
   715  )
   716  
   717  func joinBinaryVersions(versions ...[]version.Binary) []version.Binary {
   718  	var all []version.Binary
   719  	for _, versions := range versions {
   720  		all = append(all, versions...)
   721  	}
   722  	return all
   723  }
   724  
   725  // TODO(menn0): This fake BootstrapInterface implementation is
   726  // currently quite minimal but could be easily extended to cover more
   727  // test scenarios. This could help improve some of the tests in this
   728  // file which execute large amounts of external functionality.
   729  type fakeBootstrapFuncs struct {
   730  	args bootstrap.BootstrapParams
   731  }
   732  
   733  func (fake *fakeBootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error {
   734  	return nil
   735  }
   736  
   737  func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error {
   738  	fake.args = args
   739  	return nil
   740  }