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