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