github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/bootstrap/bootstrap_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package bootstrap_test
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  
    17  	"github.com/juju/errors"
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/utils/arch"
    20  	"github.com/juju/utils/series"
    21  	"github.com/juju/version"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	"github.com/juju/juju/cloudconfig/instancecfg"
    25  	"github.com/juju/juju/cmd/modelcmd"
    26  	"github.com/juju/juju/constraints"
    27  	"github.com/juju/juju/environs"
    28  	"github.com/juju/juju/environs/bootstrap"
    29  	"github.com/juju/juju/environs/config"
    30  	"github.com/juju/juju/environs/filestorage"
    31  	"github.com/juju/juju/environs/gui"
    32  	"github.com/juju/juju/environs/imagemetadata"
    33  	"github.com/juju/juju/environs/simplestreams"
    34  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    35  	"github.com/juju/juju/environs/storage"
    36  	"github.com/juju/juju/environs/sync"
    37  	envtesting "github.com/juju/juju/environs/testing"
    38  	envtools "github.com/juju/juju/environs/tools"
    39  	"github.com/juju/juju/juju"
    40  	"github.com/juju/juju/provider/dummy"
    41  	coretesting "github.com/juju/juju/testing"
    42  	"github.com/juju/juju/tools"
    43  	jujuversion "github.com/juju/juju/version"
    44  )
    45  
    46  const (
    47  	useDefaultKeys = true
    48  	noKeysDefined  = false
    49  )
    50  
    51  type bootstrapSuite struct {
    52  	coretesting.BaseSuite
    53  	envtesting.ToolsFixture
    54  }
    55  
    56  var _ = gc.Suite(&bootstrapSuite{})
    57  
    58  func (s *bootstrapSuite) SetUpTest(c *gc.C) {
    59  	s.BaseSuite.SetUpTest(c)
    60  	s.ToolsFixture.SetUpTest(c)
    61  
    62  	s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey)
    63  	storageDir := c.MkDir()
    64  	s.PatchValue(&envtools.DefaultBaseURL, storageDir)
    65  	stor, err := filestorage.NewFileStorageWriter(storageDir)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
    68  	envtesting.UploadFakeTools(c, stor, "released", "released")
    69  
    70  	// Patch the function used to retrieve GUI archive info from simplestreams.
    71  	s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) {
    72  		return nil, nil
    73  	})
    74  }
    75  
    76  func (s *bootstrapSuite) TearDownTest(c *gc.C) {
    77  	s.ToolsFixture.TearDownTest(c)
    78  	s.BaseSuite.TearDownTest(c)
    79  }
    80  
    81  func (s *bootstrapSuite) TestBootstrapNeedsSettings(c *gc.C) {
    82  	env := newEnviron("bar", noKeysDefined, nil)
    83  	s.setDummyStorage(c, env)
    84  	fixEnv := func(key string, value interface{}) {
    85  		cfg, err := env.Config().Apply(map[string]interface{}{
    86  			key: value,
    87  		})
    88  		c.Assert(err, jc.ErrorIsNil)
    89  		env.cfg = cfg
    90  	}
    91  
    92  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
    93  	c.Assert(err, gc.ErrorMatches, "model configuration has no admin-secret")
    94  
    95  	fixEnv("admin-secret", "whatever")
    96  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
    97  	c.Assert(err, gc.ErrorMatches, "model configuration has no ca-cert")
    98  
    99  	fixEnv("ca-cert", coretesting.CACert)
   100  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   101  	c.Assert(err, gc.ErrorMatches, "model configuration has no ca-private-key")
   102  
   103  	fixEnv("ca-private-key", coretesting.CAKey)
   104  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   105  	c.Assert(err, jc.ErrorIsNil)
   106  }
   107  
   108  func (s *bootstrapSuite) TestBootstrapEmptyConstraints(c *gc.C) {
   109  	env := newEnviron("foo", useDefaultKeys, nil)
   110  	s.setDummyStorage(c, env)
   111  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	c.Assert(env.bootstrapCount, gc.Equals, 1)
   114  	env.args.AvailableTools = nil
   115  	c.Assert(env.args, gc.DeepEquals, environs.BootstrapParams{})
   116  }
   117  
   118  func (s *bootstrapSuite) TestBootstrapSpecifiedConstraints(c *gc.C) {
   119  	env := newEnviron("foo", useDefaultKeys, nil)
   120  	s.setDummyStorage(c, env)
   121  	bootstrapCons := constraints.MustParse("cpu-cores=3 mem=7G")
   122  	modelCons := constraints.MustParse("cpu-cores=2 mem=4G")
   123  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   124  		BootstrapConstraints: bootstrapCons,
   125  		ModelConstraints:     modelCons,
   126  	})
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	c.Assert(env.bootstrapCount, gc.Equals, 1)
   129  	c.Assert(env.args.BootstrapConstraints, gc.DeepEquals, bootstrapCons)
   130  	c.Assert(env.args.ModelConstraints, gc.DeepEquals, modelCons)
   131  }
   132  
   133  func (s *bootstrapSuite) TestBootstrapSpecifiedBootstrapSeries(c *gc.C) {
   134  	env := newEnviron("foo", useDefaultKeys, nil)
   135  	s.setDummyStorage(c, env)
   136  	cfg, err := env.Config().Apply(map[string]interface{}{
   137  		"default-series": "wily",
   138  	})
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	env.cfg = cfg
   141  
   142  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   143  		BootstrapSeries: "trusty",
   144  	})
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	c.Check(env.bootstrapCount, gc.Equals, 1)
   147  	c.Check(env.args.BootstrapSeries, gc.Equals, "trusty")
   148  	c.Check(env.args.AvailableTools.AllSeries(), jc.SameContents, []string{"trusty"})
   149  }
   150  
   151  func (s *bootstrapSuite) TestBootstrapSpecifiedPlacement(c *gc.C) {
   152  	env := newEnviron("foo", useDefaultKeys, nil)
   153  	s.setDummyStorage(c, env)
   154  	placement := "directive"
   155  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{Placement: placement})
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	c.Assert(env.bootstrapCount, gc.Equals, 1)
   158  	c.Assert(env.args.Placement, gc.DeepEquals, placement)
   159  }
   160  
   161  func (s *bootstrapSuite) TestBootstrapImage(c *gc.C) {
   162  	s.PatchValue(&series.HostSeries, func() string { return "precise" })
   163  	s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
   164  
   165  	metadataDir, metadata := createImageMetadata(c)
   166  	stor, err := filestorage.NewFileStorageWriter(metadataDir)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	envtesting.UploadFakeTools(c, stor, "released", "released")
   169  
   170  	env := bootstrapEnvironWithRegion{
   171  		newEnviron("foo", useDefaultKeys, nil),
   172  		simplestreams.CloudSpec{
   173  			Region:   "nether",
   174  			Endpoint: "hearnoretheir",
   175  		},
   176  	}
   177  	s.setDummyStorage(c, env.bootstrapEnviron)
   178  
   179  	bootstrapCons := constraints.MustParse("arch=amd64")
   180  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   181  		BootstrapImage:       "img-id",
   182  		BootstrapSeries:      "precise",
   183  		BootstrapConstraints: bootstrapCons,
   184  		MetadataDir:          metadataDir,
   185  	})
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	c.Assert(env.bootstrapCount, gc.Equals, 1)
   188  	c.Assert(env.args.ImageMetadata, gc.HasLen, 1)
   189  	c.Assert(env.args.ImageMetadata[0], jc.DeepEquals, &imagemetadata.ImageMetadata{
   190  		Id:         "img-id",
   191  		Arch:       "amd64",
   192  		Version:    "12.04",
   193  		RegionName: "nether",
   194  		Endpoint:   "hearnoretheir",
   195  		Stream:     "released",
   196  	})
   197  	c.Assert(env.instanceConfig.CustomImageMetadata, gc.HasLen, 2)
   198  	c.Assert(env.instanceConfig.CustomImageMetadata[0], jc.DeepEquals, metadata[0])
   199  	c.Assert(env.instanceConfig.CustomImageMetadata[1], jc.DeepEquals, env.args.ImageMetadata[0])
   200  	c.Assert(env.instanceConfig.Constraints, jc.DeepEquals, bootstrapCons)
   201  }
   202  
   203  // TestBootstrapImageMetadataFromAllSources tests that we are looking for
   204  // image metadata in all data sources available to environment.
   205  // Abandoning look up too soon led to misleading bootstrap failures:
   206  // Juju reported no images available for a particular configuration,
   207  // despite image metadata in other data sources compatible with the same configuration as well.
   208  // Related to bug#1560625.
   209  func (s *bootstrapSuite) TestBootstrapImageMetadataFromAllSources(c *gc.C) {
   210  	s.PatchValue(&series.HostSeries, func() string { return "raring" })
   211  	s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
   212  
   213  	// Ensure that we can find at least one image metadata
   214  	// early on in the image metadata lookup.
   215  	// We should continue looking despite it.
   216  	metadataDir, _ := createImageMetadata(c)
   217  	stor, err := filestorage.NewFileStorageWriter(metadataDir)
   218  	c.Assert(err, jc.ErrorIsNil)
   219  	envtesting.UploadFakeTools(c, stor, "released", "released")
   220  
   221  	env := bootstrapEnvironWithRegion{
   222  		newEnviron("foo", useDefaultKeys, nil),
   223  		simplestreams.CloudSpec{
   224  			Region:   "region",
   225  			Endpoint: "endpoint",
   226  		},
   227  	}
   228  	s.setDummyStorage(c, env.bootstrapEnviron)
   229  
   230  	bootstrapCons := constraints.MustParse("arch=amd64")
   231  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   232  		BootstrapConstraints: bootstrapCons,
   233  		MetadataDir:          metadataDir,
   234  	})
   235  	c.Assert(err, jc.ErrorIsNil)
   236  
   237  	datasources, err := environs.ImageMetadataSources(env)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	for _, source := range datasources {
   240  		// make sure we looked in each and all...
   241  		c.Assert(c.GetTestLog(), jc.Contains, fmt.Sprintf("image metadata in %s", source.Description()))
   242  	}
   243  }
   244  
   245  func (s *bootstrapSuite) TestBootstrapNoToolsNonReleaseStream(c *gc.C) {
   246  	if runtime.GOOS == "windows" {
   247  		c.Skip("issue 1403084: Currently does not work because of jujud problems")
   248  	}
   249  
   250  	s.PatchValue(&arch.HostArch, func() string { return arch.ARM64 })
   251  	s.PatchValue(bootstrap.FindTools, func(environs.Environ, int, int, string, tools.Filter) (tools.List, error) {
   252  		return nil, errors.NotFoundf("tools")
   253  	})
   254  	env := newEnviron("foo", useDefaultKeys, map[string]interface{}{
   255  		"agent-stream": "proposed"})
   256  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   257  		BuildToolsTarball: func(*version.Number, string) (*sync.BuiltTools, error) {
   258  			return &sync.BuiltTools{Dir: c.MkDir()}, nil
   259  		},
   260  	})
   261  	// bootstrap.Bootstrap leaves it to the provider to
   262  	// locate bootstrap tools.
   263  	c.Assert(err, jc.ErrorIsNil)
   264  }
   265  
   266  func (s *bootstrapSuite) TestBootstrapNoToolsDevelopmentConfig(c *gc.C) {
   267  	if runtime.GOOS == "windows" {
   268  		c.Skip("issue 1403084: Currently does not work because of jujud problems")
   269  	}
   270  
   271  	s.PatchValue(&arch.HostArch, func() string { return arch.ARM64 })
   272  	s.PatchValue(bootstrap.FindTools, func(environs.Environ, int, int, string, tools.Filter) (tools.List, error) {
   273  		return nil, errors.NotFoundf("tools")
   274  	})
   275  	env := newEnviron("foo", useDefaultKeys, map[string]interface{}{
   276  		"development": true})
   277  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   278  		BuildToolsTarball: func(*version.Number, string) (*sync.BuiltTools, error) {
   279  			return &sync.BuiltTools{Dir: c.MkDir()}, nil
   280  		},
   281  	})
   282  	// bootstrap.Bootstrap leaves it to the provider to
   283  	// locate bootstrap tools.
   284  	c.Assert(err, jc.ErrorIsNil)
   285  }
   286  
   287  func (s *bootstrapSuite) TestSetBootstrapTools(c *gc.C) {
   288  	availableVersions := []version.Binary{
   289  		version.MustParseBinary("1.18.0-trusty-arm64"),
   290  		version.MustParseBinary("1.18.1-trusty-arm64"),
   291  		version.MustParseBinary("1.18.1.1-trusty-arm64"),
   292  		version.MustParseBinary("1.18.1.2-trusty-arm64"),
   293  		version.MustParseBinary("1.18.1.3-trusty-arm64"),
   294  	}
   295  	availableTools := make(tools.List, len(availableVersions))
   296  	for i, v := range availableVersions {
   297  		availableTools[i] = &tools.Tools{Version: v}
   298  	}
   299  
   300  	type test struct {
   301  		currentVersion       version.Number
   302  		expectedTools        version.Number
   303  		expectedAgentVersion version.Number
   304  	}
   305  	tests := []test{{
   306  		currentVersion:       version.MustParse("1.18.0"),
   307  		expectedTools:        version.MustParse("1.18.0"),
   308  		expectedAgentVersion: version.MustParse("1.18.1.3"),
   309  	}, {
   310  		currentVersion:       version.MustParse("1.18.1.4"),
   311  		expectedTools:        version.MustParse("1.18.1.3"),
   312  		expectedAgentVersion: version.MustParse("1.18.1.3"),
   313  	}, {
   314  		// build number is ignored unless major/minor don't
   315  		// match the latest.
   316  		currentVersion:       version.MustParse("1.18.1.2"),
   317  		expectedTools:        version.MustParse("1.18.1.3"),
   318  		expectedAgentVersion: version.MustParse("1.18.1.3"),
   319  	}, {
   320  		// If the current patch level exceeds whatever's in
   321  		// the tools source (e.g. when bootstrapping from trunk)
   322  		// then the latest available tools will be chosen.
   323  		currentVersion:       version.MustParse("1.18.2"),
   324  		expectedTools:        version.MustParse("1.18.1.3"),
   325  		expectedAgentVersion: version.MustParse("1.18.1.3"),
   326  	}}
   327  
   328  	env := newEnviron("foo", useDefaultKeys, nil)
   329  	for i, t := range tests {
   330  		c.Logf("test %d: %+v", i, t)
   331  		cfg, err := env.Config().Remove([]string{"agent-version"})
   332  		c.Assert(err, jc.ErrorIsNil)
   333  		err = env.SetConfig(cfg)
   334  		c.Assert(err, jc.ErrorIsNil)
   335  		s.PatchValue(&jujuversion.Current, t.currentVersion)
   336  		bootstrapTools, err := bootstrap.SetBootstrapTools(env, availableTools)
   337  		c.Assert(err, jc.ErrorIsNil)
   338  		for _, tools := range bootstrapTools {
   339  			c.Assert(tools.Version.Number, gc.Equals, t.expectedTools)
   340  		}
   341  		agentVersion, _ := env.Config().AgentVersion()
   342  		c.Assert(agentVersion, gc.Equals, t.expectedAgentVersion)
   343  	}
   344  }
   345  
   346  func (s *bootstrapSuite) TestBootstrapGUISuccessRemote(c *gc.C) {
   347  	s.PatchValue(bootstrap.GUIFetchMetadata, func(stream string, sources ...simplestreams.DataSource) ([]*gui.Metadata, error) {
   348  		c.Assert(stream, gc.Equals, gui.ReleasedStream)
   349  		c.Assert(sources[0].Description(), gc.Equals, "gui simplestreams")
   350  		c.Assert(sources[0].RequireSigned(), jc.IsTrue)
   351  		return []*gui.Metadata{{
   352  			Version:  version.MustParse("2.0.42"),
   353  			FullPath: "https://1.2.3.4/juju-gui-2.0.42.tar.bz2",
   354  			SHA256:   "hash-2.0.42",
   355  			Size:     42,
   356  		}, {
   357  			Version:  version.MustParse("2.0.47"),
   358  			FullPath: "https://1.2.3.4/juju-gui-2.0.47.tar.bz2",
   359  			SHA256:   "hash-2.0.47",
   360  			Size:     47,
   361  		}}, nil
   362  	})
   363  	env := newEnviron("foo", useDefaultKeys, nil)
   364  	ctx := coretesting.Context(c)
   365  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{
   366  		GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources",
   367  	})
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	c.Assert(coretesting.Stderr(ctx), jc.Contains, "Preparing for Juju GUI 2.0.42 release installation\n")
   370  
   371  	// The most recent GUI release info has been stored.
   372  	c.Assert(env.instanceConfig.GUI.URL, gc.Equals, "https://1.2.3.4/juju-gui-2.0.42.tar.bz2")
   373  	c.Assert(env.instanceConfig.GUI.Version.String(), gc.Equals, "2.0.42")
   374  	c.Assert(env.instanceConfig.GUI.Size, gc.Equals, int64(42))
   375  	c.Assert(env.instanceConfig.GUI.SHA256, gc.Equals, "hash-2.0.42")
   376  }
   377  
   378  func (s *bootstrapSuite) TestBootstrapGUISuccessLocal(c *gc.C) {
   379  	path := makeGUIArchive(c, "jujugui-2.2.0")
   380  	s.PatchEnvironment("JUJU_GUI", path)
   381  	env := newEnviron("foo", useDefaultKeys, nil)
   382  	ctx := coretesting.Context(c)
   383  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{})
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	c.Assert(coretesting.Stderr(ctx), jc.Contains, "Preparing for Juju GUI 2.2.0 installation from local archive\n")
   386  
   387  	// Check GUI URL and version.
   388  	c.Assert(env.instanceConfig.GUI.URL, gc.Equals, "file://"+path)
   389  	c.Assert(env.instanceConfig.GUI.Version.String(), gc.Equals, "2.2.0")
   390  
   391  	// Check GUI size.
   392  	f, err := os.Open(path)
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	defer f.Close()
   395  	info, err := f.Stat()
   396  	c.Assert(err, jc.ErrorIsNil)
   397  	c.Assert(env.instanceConfig.GUI.Size, gc.Equals, info.Size())
   398  
   399  	// Check GUI hash.
   400  	h := sha256.New()
   401  	_, err = io.Copy(h, f)
   402  	c.Assert(err, jc.ErrorIsNil)
   403  	c.Assert(env.instanceConfig.GUI.SHA256, gc.Equals, fmt.Sprintf("%x", h.Sum(nil)))
   404  }
   405  
   406  func (s *bootstrapSuite) TestBootstrapGUISuccessNoGUI(c *gc.C) {
   407  	env := newEnviron("foo", useDefaultKeys, nil)
   408  	ctx := coretesting.Context(c)
   409  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{})
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	c.Assert(coretesting.Stderr(ctx), jc.Contains, "Juju GUI installation has been disabled\n")
   412  	c.Assert(env.instanceConfig.GUI, gc.IsNil)
   413  }
   414  
   415  func (s *bootstrapSuite) TestBootstrapGUINoStreams(c *gc.C) {
   416  	s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) {
   417  		return nil, nil
   418  	})
   419  	env := newEnviron("foo", useDefaultKeys, nil)
   420  	ctx := coretesting.Context(c)
   421  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{
   422  		GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources",
   423  	})
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	c.Assert(coretesting.Stderr(ctx), jc.Contains, "No available Juju GUI archives found\n")
   426  	c.Assert(env.instanceConfig.GUI, gc.IsNil)
   427  }
   428  
   429  func (s *bootstrapSuite) TestBootstrapGUIStreamsFailure(c *gc.C) {
   430  	s.PatchValue(bootstrap.GUIFetchMetadata, func(string, ...simplestreams.DataSource) ([]*gui.Metadata, error) {
   431  		return nil, errors.New("bad wolf")
   432  	})
   433  	env := newEnviron("foo", useDefaultKeys, nil)
   434  	ctx := coretesting.Context(c)
   435  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{
   436  		GUIDataSourceBaseURL: "https://1.2.3.4/gui/sources",
   437  	})
   438  	c.Assert(err, jc.ErrorIsNil)
   439  	c.Assert(coretesting.Stderr(ctx), jc.Contains, "Unable to fetch Juju GUI info: bad wolf\n")
   440  	c.Assert(env.instanceConfig.GUI, gc.IsNil)
   441  }
   442  
   443  func (s *bootstrapSuite) TestBootstrapGUIErrorNotFound(c *gc.C) {
   444  	s.PatchEnvironment("JUJU_GUI", "/no/such/file")
   445  	env := newEnviron("foo", useDefaultKeys, nil)
   446  	ctx := coretesting.Context(c)
   447  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{})
   448  	c.Assert(err, jc.ErrorIsNil)
   449  	c.Assert(coretesting.Stderr(ctx), jc.Contains, `Cannot use Juju GUI at "/no/such/file": cannot open Juju GUI archive:`)
   450  }
   451  
   452  func (s *bootstrapSuite) TestBootstrapGUIErrorInvalidArchive(c *gc.C) {
   453  	path := filepath.Join(c.MkDir(), "gui.bz2")
   454  	err := ioutil.WriteFile(path, []byte("invalid"), 0666)
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	s.PatchEnvironment("JUJU_GUI", path)
   457  	env := newEnviron("foo", useDefaultKeys, nil)
   458  	ctx := coretesting.Context(c)
   459  	err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{})
   460  	c.Assert(err, jc.ErrorIsNil)
   461  	c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf("Cannot use Juju GUI at %q: cannot read Juju GUI archive", path))
   462  }
   463  
   464  func (s *bootstrapSuite) TestBootstrapGUIErrorInvalidVersion(c *gc.C) {
   465  	path := makeGUIArchive(c, "jujugui-invalid")
   466  	s.PatchEnvironment("JUJU_GUI", path)
   467  	env := newEnviron("foo", useDefaultKeys, nil)
   468  	ctx := coretesting.Context(c)
   469  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{})
   470  	c.Assert(err, jc.ErrorIsNil)
   471  	c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf(`Cannot use Juju GUI at %q: cannot parse version "invalid"`, path))
   472  }
   473  
   474  func (s *bootstrapSuite) TestBootstrapGUIErrorUnexpectedArchive(c *gc.C) {
   475  	path := makeGUIArchive(c, "not-a-gui")
   476  	s.PatchEnvironment("JUJU_GUI", path)
   477  	env := newEnviron("foo", useDefaultKeys, nil)
   478  	ctx := coretesting.Context(c)
   479  	err := bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), env, bootstrap.BootstrapParams{})
   480  	c.Assert(err, jc.ErrorIsNil)
   481  	c.Assert(coretesting.Stderr(ctx), jc.Contains, fmt.Sprintf("Cannot use Juju GUI at %q: cannot find Juju GUI version", path))
   482  }
   483  
   484  func makeGUIArchive(c *gc.C, dir string) string {
   485  	if runtime.GOOS == "windows" {
   486  		c.Skip("tar command not available")
   487  	}
   488  	target := filepath.Join(c.MkDir(), "gui.tar.bz2")
   489  	source := c.MkDir()
   490  	err := os.Mkdir(filepath.Join(source, dir), 0777)
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	err = exec.Command("tar", "cjf", target, "-C", source, dir).Run()
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	return target
   495  }
   496  
   497  // createImageMetadata creates some image metadata in a local directory.
   498  func createImageMetadata(c *gc.C) (dir string, _ []*imagemetadata.ImageMetadata) {
   499  	// Generate some image metadata.
   500  	im := []*imagemetadata.ImageMetadata{{
   501  		Id:         "1234",
   502  		Arch:       "amd64",
   503  		Version:    "13.04",
   504  		RegionName: "region",
   505  		Endpoint:   "endpoint",
   506  	}}
   507  	cloudSpec := &simplestreams.CloudSpec{
   508  		Region:   "region",
   509  		Endpoint: "endpoint",
   510  	}
   511  	sourceDir := c.MkDir()
   512  	sourceStor, err := filestorage.NewFileStorageWriter(sourceDir)
   513  	c.Assert(err, jc.ErrorIsNil)
   514  	err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor)
   515  	c.Assert(err, jc.ErrorIsNil)
   516  	return sourceDir, im
   517  }
   518  
   519  func (s *bootstrapSuite) TestBootstrapMetadata(c *gc.C) {
   520  	environs.UnregisterImageDataSourceFunc("bootstrap metadata")
   521  
   522  	metadataDir, metadata := createImageMetadata(c)
   523  	stor, err := filestorage.NewFileStorageWriter(metadataDir)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  	envtesting.UploadFakeTools(c, stor, "released", "released")
   526  
   527  	env := newEnviron("foo", useDefaultKeys, nil)
   528  	s.setDummyStorage(c, env)
   529  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   530  		MetadataDir: metadataDir,
   531  	})
   532  	c.Assert(err, jc.ErrorIsNil)
   533  	c.Assert(env.bootstrapCount, gc.Equals, 1)
   534  	c.Assert(envtools.DefaultBaseURL, gc.Equals, metadataDir)
   535  
   536  	datasources, err := environs.ImageMetadataSources(env)
   537  	c.Assert(err, jc.ErrorIsNil)
   538  	c.Assert(datasources, gc.HasLen, 3)
   539  	c.Assert(datasources[0].Description(), gc.Equals, "bootstrap metadata")
   540  	// This data source does not require to contain signed data.
   541  	// However, it may still contain it.
   542  	// Since we will always try to read signed data first,
   543  	// we want to be able to try to read this signed data
   544  	// with a user provided key.
   545  	// for this test, user provided key is empty.
   546  	// Bugs #1542127, #1542131
   547  	c.Assert(datasources[0].PublicSigningKey(), gc.Equals, "")
   548  	c.Assert(env.instanceConfig, gc.NotNil)
   549  	c.Assert(env.instanceConfig.CustomImageMetadata, gc.HasLen, 1)
   550  	c.Assert(env.instanceConfig.CustomImageMetadata[0], gc.DeepEquals, metadata[0])
   551  }
   552  
   553  func (s *bootstrapSuite) TestPublicKeyEnvVar(c *gc.C) {
   554  	path := filepath.Join(c.MkDir(), "key")
   555  	ioutil.WriteFile(path, []byte("publickey"), 0644)
   556  	s.PatchEnvironment("JUJU_STREAMS_PUBLICKEY_FILE", path)
   557  
   558  	env := newEnviron("foo", useDefaultKeys, nil)
   559  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   560  	c.Assert(err, jc.ErrorIsNil)
   561  	c.Assert(env.instanceConfig.PublicImageSigningKey, gc.Equals, "publickey")
   562  }
   563  
   564  func (s *bootstrapSuite) TestBootstrapMetadataImagesMissing(c *gc.C) {
   565  	environs.UnregisterImageDataSourceFunc("bootstrap metadata")
   566  
   567  	noImagesDir := c.MkDir()
   568  	stor, err := filestorage.NewFileStorageWriter(noImagesDir)
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	envtesting.UploadFakeTools(c, stor, "released", "released")
   571  
   572  	env := newEnviron("foo", useDefaultKeys, nil)
   573  	s.setDummyStorage(c, env)
   574  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   575  		MetadataDir: noImagesDir,
   576  	})
   577  	c.Assert(err, jc.ErrorIsNil)
   578  	c.Assert(env.bootstrapCount, gc.Equals, 1)
   579  
   580  	datasources, err := environs.ImageMetadataSources(env)
   581  	c.Assert(err, jc.ErrorIsNil)
   582  	c.Assert(datasources, gc.HasLen, 2)
   583  	c.Assert(datasources[0].Description(), gc.Equals, "default cloud images")
   584  	c.Assert(datasources[1].Description(), gc.Equals, "default ubuntu cloud images")
   585  }
   586  
   587  func (s *bootstrapSuite) setupBootstrapSpecificVersion(c *gc.C, clientMajor, clientMinor int, toolsVersion *version.Number) (error, int, version.Number) {
   588  	currentVersion := jujuversion.Current
   589  	currentVersion.Major = clientMajor
   590  	currentVersion.Minor = clientMinor
   591  	currentVersion.Tag = ""
   592  	s.PatchValue(&jujuversion.Current, currentVersion)
   593  	s.PatchValue(&series.HostSeries, func() string { return "trusty" })
   594  	s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
   595  
   596  	env := newEnviron("foo", useDefaultKeys, nil)
   597  	s.setDummyStorage(c, env)
   598  	envtools.RegisterToolsDataSourceFunc("local storage", func(environs.Environ) (simplestreams.DataSource, error) {
   599  		return storage.NewStorageSimpleStreamsDataSource("test datasource", env.storage, "tools", simplestreams.CUSTOM_CLOUD_DATA, false), nil
   600  	})
   601  	defer envtools.UnregisterToolsDataSourceFunc("local storage")
   602  
   603  	toolsBinaries := []version.Binary{
   604  		version.MustParseBinary("10.11.12-trusty-amd64"),
   605  		version.MustParseBinary("10.11.13-trusty-amd64"),
   606  		version.MustParseBinary("10.11-beta1-trusty-amd64"),
   607  	}
   608  	stream := "released"
   609  	if toolsVersion != nil && toolsVersion.Tag != "" {
   610  		stream = "devel"
   611  		currentVersion.Tag = toolsVersion.Tag
   612  	}
   613  	_, err := envtesting.UploadFakeToolsVersions(env.storage, stream, stream, toolsBinaries...)
   614  	c.Assert(err, jc.ErrorIsNil)
   615  
   616  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   617  		AgentVersion: toolsVersion,
   618  	})
   619  	vers, _ := env.cfg.AgentVersion()
   620  	return err, env.bootstrapCount, vers
   621  }
   622  
   623  func (s *bootstrapSuite) TestBootstrapSpecificVersion(c *gc.C) {
   624  	toolsVersion := version.MustParse("10.11.12")
   625  	err, bootstrapCount, vers := s.setupBootstrapSpecificVersion(c, 10, 11, &toolsVersion)
   626  	c.Assert(err, jc.ErrorIsNil)
   627  	c.Assert(bootstrapCount, gc.Equals, 1)
   628  	c.Assert(vers, gc.DeepEquals, version.Number{
   629  		Major: 10,
   630  		Minor: 11,
   631  		Patch: 12,
   632  	})
   633  }
   634  
   635  func (s *bootstrapSuite) TestBootstrapSpecificVersionWithTag(c *gc.C) {
   636  	toolsVersion := version.MustParse("10.11-beta1")
   637  	err, bootstrapCount, vers := s.setupBootstrapSpecificVersion(c, 10, 11, &toolsVersion)
   638  	c.Assert(err, jc.ErrorIsNil)
   639  	c.Assert(bootstrapCount, gc.Equals, 1)
   640  	c.Assert(vers, gc.DeepEquals, version.Number{
   641  		Major: 10,
   642  		Minor: 11,
   643  		Patch: 1,
   644  		Tag:   "beta",
   645  	})
   646  }
   647  
   648  func (s *bootstrapSuite) TestBootstrapNoSpecificVersion(c *gc.C) {
   649  	// bootstrap with no specific version will use latest major.minor tools version.
   650  	err, bootstrapCount, vers := s.setupBootstrapSpecificVersion(c, 10, 11, nil)
   651  	c.Assert(err, jc.ErrorIsNil)
   652  	c.Assert(bootstrapCount, gc.Equals, 1)
   653  	c.Assert(vers, gc.DeepEquals, version.Number{
   654  		Major: 10,
   655  		Minor: 11,
   656  		Patch: 13,
   657  	})
   658  }
   659  
   660  func (s *bootstrapSuite) TestBootstrapSpecificVersionClientMinorMismatch(c *gc.C) {
   661  	// bootstrap using a specified version only works if the patch number is different.
   662  	// The bootstrap client major and minor versions need to match the tools asked for.
   663  	toolsVersion := version.MustParse("10.11.12")
   664  	err, bootstrapCount, _ := s.setupBootstrapSpecificVersion(c, 10, 1, &toolsVersion)
   665  	c.Assert(strings.Replace(err.Error(), "\n", "", -1), gc.Matches, ".* no tools are available .*")
   666  	c.Assert(bootstrapCount, gc.Equals, 0)
   667  }
   668  
   669  func (s *bootstrapSuite) TestBootstrapSpecificVersionClientMajorMismatch(c *gc.C) {
   670  	// bootstrap using a specified version only works if the patch number is different.
   671  	// The bootstrap client major and minor versions need to match the tools asked for.
   672  	toolsVersion := version.MustParse("10.11.12")
   673  	err, bootstrapCount, _ := s.setupBootstrapSpecificVersion(c, 1, 11, &toolsVersion)
   674  	c.Assert(strings.Replace(err.Error(), "\n", "", -1), gc.Matches, ".* no tools are available .*")
   675  	c.Assert(bootstrapCount, gc.Equals, 0)
   676  }
   677  
   678  type bootstrapEnviron struct {
   679  	cfg              *config.Config
   680  	environs.Environ // stub out all methods we don't care about.
   681  
   682  	// The following fields are filled in when Bootstrap is called.
   683  	bootstrapCount              int
   684  	finalizerCount              int
   685  	supportedArchitecturesCount int
   686  	args                        environs.BootstrapParams
   687  	instanceConfig              *instancecfg.InstanceConfig
   688  	storage                     storage.Storage
   689  }
   690  
   691  func newEnviron(name string, defaultKeys bool, extraAttrs map[string]interface{}) *bootstrapEnviron {
   692  	m := dummy.SampleConfig().Merge(extraAttrs)
   693  	if !defaultKeys {
   694  		m = m.Delete(
   695  			"ca-cert",
   696  			"ca-private-key",
   697  			"admin-secret",
   698  		)
   699  	}
   700  	m["name"] = name // overwrite name provided by dummy.SampleConfig
   701  	cfg, err := config.New(config.NoDefaults, m)
   702  	if err != nil {
   703  		panic(fmt.Errorf("cannot create config from %#v: %v", m, err))
   704  	}
   705  	return &bootstrapEnviron{
   706  		cfg: cfg,
   707  	}
   708  }
   709  
   710  // setDummyStorage injects the local provider's fake storage implementation
   711  // into the given environment, so that tests can manipulate storage as if it
   712  // were real.
   713  func (s *bootstrapSuite) setDummyStorage(c *gc.C, env *bootstrapEnviron) {
   714  	closer, stor, _ := envtesting.CreateLocalTestStorage(c)
   715  	env.storage = stor
   716  	s.AddCleanup(func(c *gc.C) { closer.Close() })
   717  }
   718  
   719  func (e *bootstrapEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   720  	e.bootstrapCount++
   721  	e.args = args
   722  	finalizer := func(_ environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error {
   723  		e.finalizerCount++
   724  		e.instanceConfig = icfg
   725  		return nil
   726  	}
   727  	series := series.HostSeries()
   728  	if args.BootstrapSeries != "" {
   729  		series = args.BootstrapSeries
   730  	}
   731  	return &environs.BootstrapResult{Arch: arch.HostArch(), Series: series, Finalize: finalizer}, nil
   732  }
   733  
   734  func (e *bootstrapEnviron) Config() *config.Config {
   735  	return e.cfg
   736  }
   737  
   738  func (e *bootstrapEnviron) SetConfig(cfg *config.Config) error {
   739  	e.cfg = cfg
   740  	return nil
   741  }
   742  
   743  func (e *bootstrapEnviron) Storage() storage.Storage {
   744  	return e.storage
   745  }
   746  
   747  func (e *bootstrapEnviron) SupportedArchitectures() ([]string, error) {
   748  	e.supportedArchitecturesCount++
   749  	return []string{arch.AMD64, arch.ARM64}, nil
   750  }
   751  
   752  func (e *bootstrapEnviron) ConstraintsValidator() (constraints.Validator, error) {
   753  	return constraints.NewValidator(), nil
   754  }
   755  
   756  type bootstrapEnvironWithRegion struct {
   757  	*bootstrapEnviron
   758  	region simplestreams.CloudSpec
   759  }
   760  
   761  func (e bootstrapEnvironWithRegion) Region() (simplestreams.CloudSpec, error) {
   762  	return e.region, nil
   763  }