github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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  	"bytes"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/juju/loggo"
    12  	gc "launchpad.net/gocheck"
    13  
    14  	"launchpad.net/juju-core/cmd"
    15  	"launchpad.net/juju-core/constraints"
    16  	"launchpad.net/juju-core/environs"
    17  	"launchpad.net/juju-core/environs/configstore"
    18  	"launchpad.net/juju-core/environs/filestorage"
    19  	"launchpad.net/juju-core/environs/imagemetadata"
    20  	imtesting "launchpad.net/juju-core/environs/imagemetadata/testing"
    21  	"launchpad.net/juju-core/environs/simplestreams"
    22  	"launchpad.net/juju-core/environs/storage"
    23  	"launchpad.net/juju-core/environs/sync"
    24  	envtesting "launchpad.net/juju-core/environs/testing"
    25  	envtools "launchpad.net/juju-core/environs/tools"
    26  	ttesting "launchpad.net/juju-core/environs/tools/testing"
    27  	"launchpad.net/juju-core/errors"
    28  	"launchpad.net/juju-core/provider/dummy"
    29  	coretesting "launchpad.net/juju-core/testing"
    30  	jc "launchpad.net/juju-core/testing/checkers"
    31  	"launchpad.net/juju-core/testing/testbase"
    32  	coretools "launchpad.net/juju-core/tools"
    33  	"launchpad.net/juju-core/version"
    34  )
    35  
    36  type BootstrapSuite struct {
    37  	testbase.LoggingSuite
    38  	coretesting.MgoSuite
    39  	envtesting.ToolsFixture
    40  }
    41  
    42  var _ = gc.Suite(&BootstrapSuite{})
    43  
    44  func (s *BootstrapSuite) SetUpSuite(c *gc.C) {
    45  	s.LoggingSuite.SetUpSuite(c)
    46  	s.MgoSuite.SetUpSuite(c)
    47  }
    48  
    49  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    50  	s.LoggingSuite.SetUpTest(c)
    51  	s.MgoSuite.SetUpTest(c)
    52  	s.ToolsFixture.SetUpTest(c)
    53  
    54  	// Set up a local source with tools.
    55  	sourceDir := createToolsSource(c, vAll)
    56  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
    57  }
    58  
    59  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
    60  	s.MgoSuite.TearDownSuite(c)
    61  	s.LoggingSuite.TearDownSuite(c)
    62  }
    63  
    64  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
    65  	s.ToolsFixture.TearDownTest(c)
    66  	s.MgoSuite.TearDownTest(c)
    67  	s.LoggingSuite.TearDownTest(c)
    68  	dummy.Reset()
    69  }
    70  
    71  type bootstrapRetryTest struct {
    72  	info               string
    73  	args               []string
    74  	expectedAllowRetry []bool
    75  	err                string
    76  	// If version != "", version.Current will be
    77  	// set to it for the duration of the test.
    78  	version string
    79  	// If addVersionToSource is true, then "version"
    80  	// above will be populated in the tools source.
    81  	addVersionToSource bool
    82  }
    83  
    84  var bootstrapRetryTests = []bootstrapRetryTest{{
    85  	info:               "no tools uploaded, first check has no retries; no matching binary in source; sync fails with no second attempt",
    86  	expectedAllowRetry: []bool{false},
    87  	err:                "cannot find bootstrap tools: no matching tools available",
    88  	version:            "1.16.0-precise-amd64",
    89  }, {
    90  	info:               "no tools uploaded, first check has no retries; matching binary in source; check after sync has retries",
    91  	expectedAllowRetry: []bool{false, true},
    92  	err:                "cannot find bootstrap tools: tools not found",
    93  	version:            "1.16.0-precise-amd64",
    94  	addVersionToSource: true,
    95  }, {
    96  	info:               "no tools uploaded, first check has no retries; no matching binary in source; check after upload has retries",
    97  	expectedAllowRetry: []bool{false, true},
    98  	err:                "cannot find bootstrap tools: tools not found",
    99  	version:            "1.15.1-precise-amd64", // dev version to force upload
   100  }, {
   101  	info:               "new tools uploaded, so we want to allow retries to give them a chance at showing up",
   102  	args:               []string{"--upload-tools"},
   103  	expectedAllowRetry: []bool{true},
   104  	err:                "cannot find bootstrap tools: no matching tools available",
   105  }}
   106  
   107  // Test test checks that bootstrap calls FindTools with the expected allowRetry flag.
   108  func (s *BootstrapSuite) TestAllowRetries(c *gc.C) {
   109  	for i, test := range bootstrapRetryTests {
   110  		c.Logf("test %d: %s\n", i, test.info)
   111  		s.runAllowRetriesTest(c, test)
   112  	}
   113  }
   114  
   115  func (s *BootstrapSuite) runAllowRetriesTest(c *gc.C, test bootstrapRetryTest) {
   116  	toolsVersions := envtesting.VAll
   117  	if test.version != "" {
   118  		testVersion := version.MustParseBinary(test.version)
   119  		restore := testbase.PatchValue(&version.Current, testVersion)
   120  		defer restore()
   121  		if test.addVersionToSource {
   122  			toolsVersions = append([]version.Binary{}, toolsVersions...)
   123  			toolsVersions = append(toolsVersions, testVersion)
   124  		}
   125  	}
   126  	_, fake := makeEmptyFakeHome(c)
   127  	defer fake.Restore()
   128  	sourceDir := createToolsSource(c, toolsVersions)
   129  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
   130  
   131  	var findToolsRetryValues []bool
   132  	mockFindTools := func(cloudInst environs.ConfigGetter, majorVersion, minorVersion int,
   133  		filter coretools.Filter, allowRetry bool) (list coretools.List, err error) {
   134  		findToolsRetryValues = append(findToolsRetryValues, allowRetry)
   135  		return nil, errors.NotFoundf("tools")
   136  	}
   137  
   138  	restore := envtools.TestingPatchBootstrapFindTools(mockFindTools)
   139  	defer restore()
   140  
   141  	_, errc := runCommand(nullContext(), new(BootstrapCommand), test.args...)
   142  	err := <-errc
   143  	c.Check(findToolsRetryValues, gc.DeepEquals, test.expectedAllowRetry)
   144  	c.Check(err, gc.ErrorMatches, test.err)
   145  }
   146  
   147  func (s *BootstrapSuite) TestTest(c *gc.C) {
   148  	s.PatchValue(&sync.Upload, mockUploadTools)
   149  	for i, test := range bootstrapTests {
   150  		c.Logf("\ntest %d: %s", i, test.info)
   151  		test.run(c)
   152  	}
   153  }
   154  
   155  type bootstrapTest struct {
   156  	info string
   157  	// binary version string used to set version.Current
   158  	version string
   159  	sync    bool
   160  	args    []string
   161  	err     string
   162  	// binary version strings for expected tools; if set, no default tools
   163  	// will be uploaded before running the test.
   164  	uploads     []string
   165  	constraints constraints.Value
   166  }
   167  
   168  func (test bootstrapTest) run(c *gc.C) {
   169  	// Create home with dummy provider and remove all
   170  	// of its envtools.
   171  	env, fake := makeEmptyFakeHome(c)
   172  	defer fake.Restore()
   173  
   174  	if test.version != "" {
   175  		origVersion := version.Current
   176  		version.Current = version.MustParseBinary(test.version)
   177  		defer func() { version.Current = origVersion }()
   178  	}
   179  
   180  	uploadCount := len(test.uploads)
   181  	if uploadCount == 0 {
   182  		usefulVersion := version.Current
   183  		usefulVersion.Series = env.Config().DefaultSeries()
   184  		envtesting.AssertUploadFakeToolsVersions(c, env.Storage(), usefulVersion)
   185  	}
   186  
   187  	// Run command and check for uploads.
   188  	opc, errc := runCommand(nullContext(), new(BootstrapCommand), test.args...)
   189  	if uploadCount > 0 {
   190  		for i := 0; i < uploadCount; i++ {
   191  			c.Check((<-opc).(dummy.OpPutFile).Env, gc.Equals, "peckham")
   192  		}
   193  		list, err := envtools.FindTools(
   194  			env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry)
   195  		c.Check(err, gc.IsNil)
   196  		c.Logf("found: " + list.String())
   197  		urls := list.URLs()
   198  		c.Check(urls, gc.HasLen, len(test.uploads))
   199  		for _, v := range test.uploads {
   200  			c.Logf("seeking: " + v)
   201  			vers := version.MustParseBinary(v)
   202  			_, found := urls[vers]
   203  			c.Check(found, gc.Equals, true)
   204  		}
   205  	}
   206  
   207  	// Check for remaining operations/errors.
   208  	if test.err != "" {
   209  		c.Check(<-errc, gc.ErrorMatches, test.err)
   210  		return
   211  	}
   212  	if !c.Check(<-errc, gc.IsNil) {
   213  		return
   214  	}
   215  	if len(test.uploads) > 0 {
   216  		indexFile := (<-opc).(dummy.OpPutFile)
   217  		c.Check(indexFile.FileName, gc.Equals, "tools/streams/v1/index.json")
   218  		productFile := (<-opc).(dummy.OpPutFile)
   219  		c.Check(productFile.FileName, gc.Equals, "tools/streams/v1/com.ubuntu.juju:released:tools.json")
   220  	}
   221  	opPutBootstrapVerifyFile := (<-opc).(dummy.OpPutFile)
   222  	c.Check(opPutBootstrapVerifyFile.Env, gc.Equals, "peckham")
   223  	c.Check(opPutBootstrapVerifyFile.FileName, gc.Equals, environs.VerificationFilename)
   224  
   225  	opPutBootstrapInitFile := (<-opc).(dummy.OpPutFile)
   226  	c.Check(opPutBootstrapInitFile.Env, gc.Equals, "peckham")
   227  	c.Check(opPutBootstrapInitFile.FileName, gc.Equals, "provider-state")
   228  
   229  	opBootstrap := (<-opc).(dummy.OpBootstrap)
   230  	c.Check(opBootstrap.Env, gc.Equals, "peckham")
   231  	c.Check(opBootstrap.Constraints, gc.DeepEquals, test.constraints)
   232  
   233  	store, err := configstore.Default()
   234  	c.Assert(err, gc.IsNil)
   235  	// Check a CA cert/key was generated by reloading the environment.
   236  	env, err = environs.NewFromName("peckham", store)
   237  	c.Assert(err, gc.IsNil)
   238  	_, hasCert := env.Config().CACert()
   239  	c.Check(hasCert, gc.Equals, true)
   240  	_, hasKey := env.Config().CAPrivateKey()
   241  	c.Check(hasKey, gc.Equals, true)
   242  }
   243  
   244  var bootstrapTests = []bootstrapTest{{
   245  	info: "no args, no error, no uploads, no constraints",
   246  }, {
   247  	info: "bad arg",
   248  	args: []string{"twiddle"},
   249  	err:  `unrecognized args: \["twiddle"\]`,
   250  }, {
   251  	info: "bad --constraints",
   252  	args: []string{"--constraints", "bad=wrong"},
   253  	err:  `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`,
   254  }, {
   255  	info: "bad --series",
   256  	args: []string{"--series", "bad1"},
   257  	err:  `invalid value "bad1" for flag --series: invalid series name "bad1"`,
   258  }, {
   259  	info: "lonely --series",
   260  	args: []string{"--series", "fine"},
   261  	err:  `--series requires --upload-tools`,
   262  }, {
   263  	info:    "bad environment",
   264  	version: "1.2.3-precise-amd64",
   265  	args:    []string{"-e", "brokenenv"},
   266  	err:     `dummy.Bootstrap is broken`,
   267  }, {
   268  	info:        "constraints",
   269  	args:        []string{"--constraints", "mem=4G cpu-cores=4"},
   270  	constraints: constraints.MustParse("mem=4G cpu-cores=4"),
   271  }, {
   272  	info:    "--upload-tools picks all reasonable series",
   273  	version: "1.2.3-saucy-amd64",
   274  	args:    []string{"--upload-tools"},
   275  	uploads: []string{
   276  		"1.2.3.1-saucy-amd64",   // from version.Current
   277  		"1.2.3.1-raring-amd64",  // from env.Config().DefaultSeries()
   278  		"1.2.3.1-precise-amd64", // from environs/config.DefaultSeries
   279  	},
   280  }, {
   281  	info:    "--upload-tools only uploads each file once",
   282  	version: "1.2.3-precise-amd64",
   283  	args:    []string{"--upload-tools"},
   284  	uploads: []string{
   285  		"1.2.3.1-raring-amd64",
   286  		"1.2.3.1-precise-amd64",
   287  	},
   288  }, {
   289  	info:    "--upload-tools rejects invalid series",
   290  	version: "1.2.3-saucy-amd64",
   291  	args:    []string{"--upload-tools", "--series", "ping,ping,pong"},
   292  	err:     `invalid series "ping"`,
   293  }, {
   294  	info:    "--upload-tools always bumps build number",
   295  	version: "1.2.3.4-raring-amd64",
   296  	args:    []string{"--upload-tools"},
   297  	uploads: []string{
   298  		"1.2.3.5-raring-amd64",
   299  		"1.2.3.5-precise-amd64",
   300  	},
   301  }}
   302  
   303  func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) {
   304  	env, fake := makeEmptyFakeHome(c)
   305  	defer fake.Restore()
   306  	defaultSeriesVersion := version.Current
   307  	defaultSeriesVersion.Series = env.Config().DefaultSeries()
   308  
   309  	ctx := coretesting.Context(c)
   310  	code := cmd.Main(&BootstrapCommand{}, ctx, nil)
   311  	c.Check(code, gc.Equals, 0)
   312  
   313  	ctx2 := coretesting.Context(c)
   314  	code2 := cmd.Main(&BootstrapCommand{}, ctx2, nil)
   315  	c.Check(code2, gc.Equals, 1)
   316  	c.Check(coretesting.Stderr(ctx2), gc.Equals, "error: environment is already bootstrapped\n")
   317  	c.Check(coretesting.Stdout(ctx2), gc.Equals, "")
   318  }
   319  
   320  func (s *BootstrapSuite) TestBootstrapJenvWarning(c *gc.C) {
   321  	env, fake := makeEmptyFakeHome(c)
   322  	defer fake.Restore()
   323  	defaultSeriesVersion := version.Current
   324  	defaultSeriesVersion.Series = env.Config().DefaultSeries()
   325  
   326  	store, err := configstore.Default()
   327  	c.Assert(err, gc.IsNil)
   328  	ctx := coretesting.Context(c)
   329  	environs.PrepareFromName("peckham", ctx, store)
   330  
   331  	logger := "jenv.warning.test"
   332  	testWriter := &loggo.TestWriter{}
   333  	loggo.RegisterWriter(logger, testWriter, loggo.WARNING)
   334  	defer loggo.RemoveWriter(logger)
   335  
   336  	_, errc := runCommand(ctx, new(BootstrapCommand), "-e", "peckham")
   337  	c.Assert(<-errc, gc.IsNil)
   338  	c.Assert(testWriter.Log, jc.LogMatches, []string{"ignoring environments.yaml: using bootstrap config in .*"})
   339  }
   340  
   341  func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) {
   342  	s.PatchValue(&version.Current.Number, version.MustParse("1.2.0"))
   343  	env, fake := makeEmptyFakeHome(c)
   344  	defer fake.Restore()
   345  
   346  	// Bootstrap the environment with an invalid source.
   347  	// The command returns with an error.
   348  	ctx := coretesting.Context(c)
   349  	code := cmd.Main(&BootstrapCommand{}, ctx, []string{"--metadata-source", c.MkDir()})
   350  	c.Check(code, gc.Equals, 1)
   351  
   352  	// Now check that there are no tools available.
   353  	_, err := envtools.FindTools(
   354  		env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry)
   355  	c.Assert(err, gc.FitsTypeOf, errors.NotFoundf(""))
   356  }
   357  
   358  // createImageMetadata creates some image metadata in a local directory.
   359  func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) {
   360  	// Generate some image metadata.
   361  	im := []*imagemetadata.ImageMetadata{
   362  		{
   363  			Id:         "1234",
   364  			Arch:       "amd64",
   365  			Version:    "13.04",
   366  			RegionName: "region",
   367  			Endpoint:   "endpoint",
   368  		},
   369  	}
   370  	cloudSpec := &simplestreams.CloudSpec{
   371  		Region:   "region",
   372  		Endpoint: "endpoint",
   373  	}
   374  	sourceDir := c.MkDir()
   375  	sourceStor, err := filestorage.NewFileStorageWriter(sourceDir)
   376  	c.Assert(err, gc.IsNil)
   377  	err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor)
   378  	c.Assert(err, gc.IsNil)
   379  	return sourceDir, im
   380  }
   381  
   382  // checkImageMetadata checks that the environment contains the expected image metadata.
   383  func checkImageMetadata(c *gc.C, stor storage.StorageReader, expected []*imagemetadata.ImageMetadata) {
   384  	metadata := imtesting.ParseMetadataFromStorage(c, stor)
   385  	c.Assert(metadata, gc.HasLen, 1)
   386  	c.Assert(expected[0], gc.DeepEquals, metadata[0])
   387  }
   388  
   389  func (s *BootstrapSuite) TestUploadLocalImageMetadata(c *gc.C) {
   390  	sourceDir, expected := createImageMetadata(c)
   391  	env, fake := makeEmptyFakeHome(c)
   392  	defer fake.Restore()
   393  
   394  	// Bootstrap the environment with the valid source.
   395  	ctx := coretesting.Context(c)
   396  	code := cmd.Main(&BootstrapCommand{}, ctx, []string{"--metadata-source", sourceDir})
   397  	c.Check(code, gc.Equals, 0)
   398  	c.Assert(imagemetadata.DefaultBaseURL, gc.Equals, imagemetadata.UbuntuCloudImagesURL)
   399  
   400  	// Now check the image metadata has been uploaded.
   401  	checkImageMetadata(c, env.Storage(), expected)
   402  }
   403  
   404  func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) {
   405  	sourceDir := createToolsSource(c, vAll)
   406  	s.PatchValue(&version.Current.Number, version.MustParse("1.2.0"))
   407  	env, fake := makeEmptyFakeHome(c)
   408  	defer fake.Restore()
   409  
   410  	// Bootstrap the environment with the valid source.
   411  	// The bootstrapping has to show no error, because the tools
   412  	// are automatically synchronized.
   413  	ctx := coretesting.Context(c)
   414  	code := cmd.Main(&BootstrapCommand{}, ctx, []string{"--metadata-source", sourceDir})
   415  	c.Check(code, gc.Equals, 0)
   416  
   417  	// Now check the available tools which are the 1.2.0 envtools.
   418  	checkTools(c, env, v120All)
   419  }
   420  
   421  func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, series string) environs.Environ {
   422  	s.PatchValue(&sync.Upload, mockUploadTools)
   423  	sourceDir := createToolsSource(c, vAll)
   424  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
   425  
   426  	// Change the tools location to be the test location and also
   427  	// the version and ensure their later restoring.
   428  	// Set the current version to be something for which there are no tools
   429  	// so we can test that an upload is forced.
   430  	origVersion := version.Current
   431  	version.Current.Number = version.MustParse(vers)
   432  	version.Current.Series = series
   433  	s.AddCleanup(func(*gc.C) { version.Current = origVersion })
   434  
   435  	// Create home with dummy provider and remove all
   436  	// of its envtools.
   437  	env, fake := makeEmptyFakeHome(c)
   438  	s.AddCleanup(func(*gc.C) { fake.Restore() })
   439  	return env
   440  }
   441  
   442  func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) {
   443  	otherSeries := "precise"
   444  	if otherSeries == version.Current.Series {
   445  		otherSeries = "raring"
   446  	}
   447  	env := s.setupAutoUploadTest(c, "1.7.3", otherSeries)
   448  	// Run command and check for that upload has been run for tools matching the current juju version.
   449  	opc, errc := runCommand(nullContext(), new(BootstrapCommand))
   450  	c.Assert(<-errc, gc.IsNil)
   451  	c.Assert((<-opc).(dummy.OpPutFile).Env, gc.Equals, "peckham")
   452  	list, err := envtools.FindTools(env, version.Current.Major, version.Current.Minor, coretools.Filter{}, false)
   453  	c.Assert(err, gc.IsNil)
   454  	c.Logf("found: " + list.String())
   455  	urls := list.URLs()
   456  	c.Assert(urls, gc.HasLen, 2)
   457  	expectedVers := []version.Binary{
   458  		version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", otherSeries, version.Current.Arch)),
   459  		version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", version.Current.Series, version.Current.Arch)),
   460  	}
   461  	for _, vers := range expectedVers {
   462  		c.Logf("seeking: " + vers.String())
   463  		_, found := urls[vers]
   464  		c.Check(found, gc.Equals, true)
   465  	}
   466  }
   467  
   468  func (s *BootstrapSuite) TestAutoUploadOnlyForDev(c *gc.C) {
   469  	s.setupAutoUploadTest(c, "1.8.3", "precise")
   470  	_, errc := runCommand(nullContext(), new(BootstrapCommand))
   471  	err := <-errc
   472  	c.Assert(err, gc.ErrorMatches, "cannot find bootstrap tools: no matching tools available")
   473  }
   474  
   475  func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) {
   476  	s.setupAutoUploadTest(c, "1.8.3", "precise")
   477  	context := coretesting.Context(c)
   478  	code := cmd.Main(&BootstrapCommand{}, context, nil)
   479  	c.Assert(code, gc.Equals, 1)
   480  	errText := context.Stderr.(*bytes.Buffer).String()
   481  	errText = strings.Replace(errText, "\n", "", -1)
   482  	expectedErrText := "error: cannot find bootstrap tools: no matching tools available"
   483  	c.Assert(errText, gc.Matches, expectedErrText)
   484  }
   485  
   486  func uploadToolsAlwaysFails(stor storage.Storage, forceVersion *version.Number, series ...string) (*coretools.Tools, error) {
   487  	return nil, fmt.Errorf("an error")
   488  }
   489  
   490  func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) {
   491  	s.setupAutoUploadTest(c, "1.7.3", "precise")
   492  	s.PatchValue(&sync.Upload, uploadToolsAlwaysFails)
   493  	context := coretesting.Context(c)
   494  	code := cmd.Main(&BootstrapCommand{}, context, nil)
   495  	c.Assert(code, gc.Equals, 1)
   496  	errText := context.Stderr.(*bytes.Buffer).String()
   497  	errText = strings.Replace(errText, "\n", "", -1)
   498  	expectedErrText := "error: cannot find bootstrap tools: an error"
   499  	c.Assert(errText, gc.Matches, expectedErrText)
   500  }
   501  
   502  func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) {
   503  	_, fake := makeEmptyFakeHome(c)
   504  	defer fake.Restore()
   505  	opc, errc := runCommand(nullContext(), new(BootstrapCommand), "-e", "brokenenv")
   506  	err := <-errc
   507  	c.Assert(err, gc.ErrorMatches, "dummy.Bootstrap is broken")
   508  	var opDestroy *dummy.OpDestroy
   509  	for opDestroy == nil {
   510  		select {
   511  		case op := <-opc:
   512  			switch op := op.(type) {
   513  			case dummy.OpDestroy:
   514  				opDestroy = &op
   515  			}
   516  		default:
   517  			c.Error("expected call to env.Destroy")
   518  			return
   519  		}
   520  	}
   521  	c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken")
   522  }
   523  
   524  // createToolsSource writes the mock tools and metadata into a temporary
   525  // directory and returns it.
   526  func createToolsSource(c *gc.C, versions []version.Binary) string {
   527  	versionStrings := make([]string, len(versions))
   528  	for i, vers := range versions {
   529  		versionStrings[i] = vers.String()
   530  	}
   531  	source := c.MkDir()
   532  	ttesting.MakeTools(c, source, "releases", versionStrings)
   533  	return source
   534  }
   535  
   536  // makeEmptyFakeHome creates a faked home without envtools.
   537  func makeEmptyFakeHome(c *gc.C) (environs.Environ, *coretesting.FakeHome) {
   538  	fake := coretesting.MakeFakeHome(c, envConfig)
   539  	dummy.Reset()
   540  	store, err := configstore.Default()
   541  	c.Assert(err, gc.IsNil)
   542  	env, err := environs.PrepareFromName("peckham", nullContext(), store)
   543  	c.Assert(err, gc.IsNil)
   544  	envtesting.RemoveAllTools(c, env)
   545  	return env, fake
   546  }
   547  
   548  // checkTools check if the environment contains the passed envtools.
   549  func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) {
   550  	list, err := envtools.FindTools(
   551  		env, version.Current.Major, version.Current.Minor, coretools.Filter{}, envtools.DoNotAllowRetry)
   552  	c.Check(err, gc.IsNil)
   553  	c.Logf("found: " + list.String())
   554  	urls := list.URLs()
   555  	c.Check(urls, gc.HasLen, len(expected))
   556  }
   557  
   558  var (
   559  	v100d64 = version.MustParseBinary("1.0.0-raring-amd64")
   560  	v100p64 = version.MustParseBinary("1.0.0-precise-amd64")
   561  	v100q32 = version.MustParseBinary("1.0.0-quantal-i386")
   562  	v100q64 = version.MustParseBinary("1.0.0-quantal-amd64")
   563  	v120d64 = version.MustParseBinary("1.2.0-raring-amd64")
   564  	v120p64 = version.MustParseBinary("1.2.0-precise-amd64")
   565  	v120q32 = version.MustParseBinary("1.2.0-quantal-i386")
   566  	v120q64 = version.MustParseBinary("1.2.0-quantal-amd64")
   567  	v190p32 = version.MustParseBinary("1.9.0-precise-i386")
   568  	v190q64 = version.MustParseBinary("1.9.0-quantal-amd64")
   569  	v200p64 = version.MustParseBinary("2.0.0-precise-amd64")
   570  	v100All = []version.Binary{
   571  		v100d64, v100p64, v100q64, v100q32,
   572  	}
   573  	v120All = []version.Binary{
   574  		v120d64, v120p64, v120q64, v120q32,
   575  	}
   576  	vAll = []version.Binary{
   577  		v100d64, v100p64, v100q32, v100q64,
   578  		v120d64, v120p64, v120q32, v120q64,
   579  		v190p32, v190q64,
   580  		v200p64,
   581  	}
   582  )