github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/jujud/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  	"encoding/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"time"
    15  
    16  	"github.com/juju/cmd"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/names"
    19  	gitjujutesting "github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils"
    22  	"github.com/juju/utils/set"
    23  	gc "gopkg.in/check.v1"
    24  	"gopkg.in/mgo.v2"
    25  	goyaml "gopkg.in/yaml.v1"
    26  
    27  	"github.com/juju/juju/agent"
    28  	agenttools "github.com/juju/juju/agent/tools"
    29  	"github.com/juju/juju/apiserver/params"
    30  	"github.com/juju/juju/cmd/envcmd"
    31  	agenttesting "github.com/juju/juju/cmd/jujud/agent/testing"
    32  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    33  	"github.com/juju/juju/constraints"
    34  	"github.com/juju/juju/environs"
    35  	"github.com/juju/juju/environs/config"
    36  	"github.com/juju/juju/environs/configstore"
    37  	"github.com/juju/juju/environs/filestorage"
    38  	"github.com/juju/juju/environs/storage"
    39  	envtesting "github.com/juju/juju/environs/testing"
    40  	envtools "github.com/juju/juju/environs/tools"
    41  	"github.com/juju/juju/instance"
    42  	jujutesting "github.com/juju/juju/juju/testing"
    43  	"github.com/juju/juju/mongo"
    44  	"github.com/juju/juju/network"
    45  	"github.com/juju/juju/provider/dummy"
    46  	"github.com/juju/juju/state"
    47  	"github.com/juju/juju/state/multiwatcher"
    48  	statestorage "github.com/juju/juju/state/storage"
    49  	statetesting "github.com/juju/juju/state/testing"
    50  	"github.com/juju/juju/storage/poolmanager"
    51  	"github.com/juju/juju/testing"
    52  	"github.com/juju/juju/tools"
    53  	"github.com/juju/juju/version"
    54  )
    55  
    56  var _ = configstore.Default
    57  
    58  // We don't want to use JujuConnSuite because it gives us
    59  // an already-bootstrapped environment.
    60  type BootstrapSuite struct {
    61  	testing.BaseSuite
    62  	gitjujutesting.MgoSuite
    63  	envcfg          *config.Config
    64  	b64yamlEnvcfg   string
    65  	instanceId      instance.Id
    66  	dataDir         string
    67  	logDir          string
    68  	mongoOplogSize  string
    69  	fakeEnsureMongo *agenttesting.FakeEnsureMongo
    70  	bootstrapName   string
    71  
    72  	toolsStorage storage.Storage
    73  }
    74  
    75  var _ = gc.Suite(&BootstrapSuite{})
    76  
    77  func (s *BootstrapSuite) SetUpSuite(c *gc.C) {
    78  	storageDir := c.MkDir()
    79  	restorer := gitjujutesting.PatchValue(&envtools.DefaultBaseURL, storageDir)
    80  	s.AddSuiteCleanup(func(*gc.C) {
    81  		restorer()
    82  	})
    83  	stor, err := filestorage.NewFileStorageWriter(storageDir)
    84  	c.Assert(err, jc.ErrorIsNil)
    85  	s.toolsStorage = stor
    86  
    87  	s.BaseSuite.SetUpSuite(c)
    88  	s.MgoSuite.SetUpSuite(c)
    89  	s.PatchValue(&version.Current.Number, testing.FakeVersionNumber)
    90  	s.makeTestEnv(c)
    91  }
    92  
    93  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
    94  	s.MgoSuite.TearDownSuite(c)
    95  	s.BaseSuite.TearDownSuite(c)
    96  	dummy.Reset()
    97  }
    98  
    99  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
   100  	s.BaseSuite.SetUpTest(c)
   101  	s.PatchValue(&sshGenerateKey, func(name string) (string, string, error) {
   102  		return "private-key", "public-key", nil
   103  	})
   104  
   105  	s.MgoSuite.SetUpTest(c)
   106  	s.dataDir = c.MkDir()
   107  	s.logDir = c.MkDir()
   108  	s.mongoOplogSize = "1234"
   109  	s.fakeEnsureMongo = agenttesting.InstallFakeEnsureMongo(s)
   110  	s.PatchValue(&maybeInitiateMongoServer, s.fakeEnsureMongo.InitiateMongo)
   111  
   112  	// Create fake tools.tar.gz and downloaded-tools.txt.
   113  	toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, version.Current))
   114  	err := os.MkdirAll(toolsDir, 0755)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	err = ioutil.WriteFile(filepath.Join(toolsDir, "tools.tar.gz"), nil, 0644)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	s.writeDownloadedTools(c, &tools.Tools{Version: version.Current})
   119  }
   120  
   121  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
   122  	s.MgoSuite.TearDownTest(c)
   123  	s.BaseSuite.TearDownTest(c)
   124  }
   125  
   126  func (s *BootstrapSuite) writeDownloadedTools(c *gc.C, tools *tools.Tools) {
   127  	toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, tools.Version))
   128  	err := os.MkdirAll(toolsDir, 0755)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	data, err := json.Marshal(tools)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	err = ioutil.WriteFile(filepath.Join(toolsDir, "downloaded-tools.txt"), data, 0644)
   133  	c.Assert(err, jc.ErrorIsNil)
   134  }
   135  
   136  var testPassword = "my-admin-secret"
   137  
   138  func testPasswordHash() string {
   139  	return utils.UserPasswordHash(testPassword, utils.CompatSalt)
   140  }
   141  
   142  func (s *BootstrapSuite) initBootstrapCommand(c *gc.C, jobs []multiwatcher.MachineJob, args ...string) (machineConf agent.ConfigSetterWriter, cmd *BootstrapCommand, err error) {
   143  	if len(jobs) == 0 {
   144  		// Add default jobs.
   145  		jobs = []multiwatcher.MachineJob{
   146  			multiwatcher.JobManageEnviron,
   147  			multiwatcher.JobHostUnits,
   148  			multiwatcher.JobManageNetworking,
   149  		}
   150  	}
   151  	// NOTE: the old test used an equivalent of the NewAgentConfig, but it
   152  	// really should be using NewStateMachineConfig.
   153  	agentParams := agent.AgentConfigParams{
   154  		LogDir:            s.logDir,
   155  		DataDir:           s.dataDir,
   156  		Jobs:              jobs,
   157  		Tag:               names.NewMachineTag("0"),
   158  		UpgradedToVersion: version.Current.Number,
   159  		Password:          testPasswordHash(),
   160  		Nonce:             agent.BootstrapNonce,
   161  		Environment:       testing.EnvironmentTag,
   162  		StateAddresses:    []string{gitjujutesting.MgoServer.Addr()},
   163  		APIAddresses:      []string{"0.1.2.3:1234"},
   164  		CACert:            testing.CACert,
   165  		Values: map[string]string{
   166  			agent.Namespace:      "foobar",
   167  			agent.MongoOplogSize: s.mongoOplogSize,
   168  		},
   169  	}
   170  	servingInfo := params.StateServingInfo{
   171  		Cert:         "some cert",
   172  		PrivateKey:   "some key",
   173  		CAPrivateKey: "another key",
   174  		APIPort:      3737,
   175  		StatePort:    gitjujutesting.MgoServer.Port(),
   176  	}
   177  
   178  	machineConf, err = agent.NewStateMachineConfig(agentParams, servingInfo)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	err = machineConf.Write()
   181  	c.Assert(err, jc.ErrorIsNil)
   182  
   183  	cmd = NewBootstrapCommand()
   184  
   185  	err = testing.InitCommand(cmd, append([]string{"--data-dir", s.dataDir}, args...))
   186  	return machineConf, cmd, err
   187  }
   188  
   189  func (s *BootstrapSuite) TestInitializeEnvironment(c *gc.C) {
   190  	hw := instance.MustParseHardware("arch=amd64 mem=8G")
   191  	machConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), "--hardware", hw.String())
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	err = cmd.Run(nil)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  
   196  	c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
   197  	c.Assert(s.fakeEnsureMongo.InitiateCount, gc.Equals, 1)
   198  	c.Assert(s.fakeEnsureMongo.EnsureCount, gc.Equals, 1)
   199  	c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
   200  	c.Assert(s.fakeEnsureMongo.OplogSize, gc.Equals, 1234)
   201  
   202  	expectInfo, exists := machConf.StateServingInfo()
   203  	c.Assert(exists, jc.IsTrue)
   204  	c.Assert(expectInfo.SharedSecret, gc.Equals, "")
   205  	c.Assert(expectInfo.SystemIdentity, gc.Equals, "")
   206  
   207  	servingInfo := s.fakeEnsureMongo.Info
   208  	c.Assert(len(servingInfo.SharedSecret), gc.Not(gc.Equals), 0)
   209  	c.Assert(len(servingInfo.SystemIdentity), gc.Not(gc.Equals), 0)
   210  	servingInfo.SharedSecret = ""
   211  	servingInfo.SystemIdentity = ""
   212  	expect := cmdutil.ParamsStateServingInfoToStateStateServingInfo(expectInfo)
   213  	c.Assert(servingInfo, jc.DeepEquals, expect)
   214  	expectDialAddrs := []string{fmt.Sprintf("127.0.0.1:%d", expectInfo.StatePort)}
   215  	gotDialAddrs := s.fakeEnsureMongo.InitiateParams.DialInfo.Addrs
   216  	c.Assert(gotDialAddrs, gc.DeepEquals, expectDialAddrs)
   217  
   218  	memberHost := fmt.Sprintf("%s:%d", s.bootstrapName, expectInfo.StatePort)
   219  	c.Assert(s.fakeEnsureMongo.InitiateParams.MemberHostPort, gc.Equals, memberHost)
   220  	c.Assert(s.fakeEnsureMongo.InitiateParams.User, gc.Equals, "")
   221  	c.Assert(s.fakeEnsureMongo.InitiateParams.Password, gc.Equals, "")
   222  
   223  	st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{
   224  		Info: mongo.Info{
   225  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   226  			CACert: testing.CACert,
   227  		},
   228  		Password: testPasswordHash(),
   229  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	defer st.Close()
   232  	machines, err := st.AllMachines()
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	c.Assert(machines, gc.HasLen, 1)
   235  
   236  	instid, err := machines[0].InstanceId()
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	c.Assert(instid, gc.Equals, instance.Id(string(s.instanceId)))
   239  
   240  	stateHw, err := machines[0].HardwareCharacteristics()
   241  	c.Assert(err, jc.ErrorIsNil)
   242  	c.Assert(stateHw, gc.NotNil)
   243  	c.Assert(*stateHw, gc.DeepEquals, hw)
   244  
   245  	cons, err := st.EnvironConstraints()
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
   248  
   249  	cfg, err := st.EnvironConfig()
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	c.Assert(cfg.AuthorizedKeys(), gc.Equals, s.envcfg.AuthorizedKeys()+"\npublic-key")
   252  }
   253  
   254  func (s *BootstrapSuite) TestInitializeEnvironmentInvalidOplogSize(c *gc.C) {
   255  	s.mongoOplogSize = "NaN"
   256  	hw := instance.MustParseHardware("arch=amd64 mem=8G")
   257  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), "--hardware", hw.String())
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	err = cmd.Run(nil)
   260  	c.Assert(err, gc.ErrorMatches, `invalid oplog size: "NaN"`)
   261  }
   262  
   263  func (s *BootstrapSuite) TestSetConstraints(c *gc.C) {
   264  	tcons := constraints.Value{Mem: uint64p(2048), CpuCores: uint64p(2)}
   265  	_, cmd, err := s.initBootstrapCommand(c, nil,
   266  		"--env-config", s.b64yamlEnvcfg,
   267  		"--instance-id", string(s.instanceId),
   268  		"--constraints", tcons.String(),
   269  	)
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	err = cmd.Run(nil)
   272  	c.Assert(err, jc.ErrorIsNil)
   273  
   274  	st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{
   275  		Info: mongo.Info{
   276  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   277  			CACert: testing.CACert,
   278  		},
   279  		Password: testPasswordHash(),
   280  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	defer st.Close()
   283  	cons, err := st.EnvironConstraints()
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(cons, gc.DeepEquals, tcons)
   286  
   287  	machines, err := st.AllMachines()
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	c.Assert(machines, gc.HasLen, 1)
   290  	cons, err = machines[0].Constraints()
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(cons, gc.DeepEquals, tcons)
   293  }
   294  
   295  func uint64p(v uint64) *uint64 {
   296  	return &v
   297  }
   298  
   299  func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) {
   300  	expectedJobs := []state.MachineJob{
   301  		state.JobManageEnviron,
   302  		state.JobHostUnits,
   303  		state.JobManageNetworking,
   304  	}
   305  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	err = cmd.Run(nil)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  
   310  	st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{
   311  		Info: mongo.Info{
   312  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   313  			CACert: testing.CACert,
   314  		},
   315  		Password: testPasswordHash(),
   316  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	defer st.Close()
   319  	m, err := st.Machine("0")
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	c.Assert(m.Jobs(), gc.DeepEquals, expectedJobs)
   322  }
   323  
   324  func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) {
   325  	jobs := []multiwatcher.MachineJob{multiwatcher.JobManageEnviron}
   326  	_, cmd, err := s.initBootstrapCommand(c, jobs, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   327  	c.Assert(err, jc.ErrorIsNil)
   328  	err = cmd.Run(nil)
   329  	c.Assert(err, jc.ErrorIsNil)
   330  
   331  	st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{
   332  		Info: mongo.Info{
   333  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   334  			CACert: testing.CACert,
   335  		},
   336  		Password: testPasswordHash(),
   337  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   338  	c.Assert(err, jc.ErrorIsNil)
   339  	defer st.Close()
   340  	m, err := st.Machine("0")
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageEnviron})
   343  }
   344  
   345  func testOpenState(c *gc.C, info *mongo.MongoInfo, expectErrType error) {
   346  	st, err := state.Open(testing.EnvironmentTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   347  	if st != nil {
   348  		st.Close()
   349  	}
   350  	if expectErrType != nil {
   351  		c.Assert(err, gc.FitsTypeOf, expectErrType)
   352  	} else {
   353  		c.Assert(err, jc.ErrorIsNil)
   354  	}
   355  }
   356  
   357  func (s *BootstrapSuite) TestInitialPassword(c *gc.C) {
   358  	machineConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   359  	c.Assert(err, jc.ErrorIsNil)
   360  
   361  	err = cmd.Run(nil)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  
   364  	info := &mongo.MongoInfo{
   365  		Info: mongo.Info{
   366  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   367  			CACert: testing.CACert,
   368  		},
   369  	}
   370  
   371  	// Check we can log in to mongo as admin.
   372  	// TODO(dfc) does passing nil for the admin user name make your skin crawl ? mine too.
   373  	info.Tag, info.Password = nil, testPasswordHash()
   374  	st, err := state.Open(testing.EnvironmentTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	defer st.Close()
   377  
   378  	// We're running Mongo with --noauth; let's explicitly verify
   379  	// that we can login as that user. Even with --noauth, an
   380  	// explicit Login will still be verified.
   381  	adminDB := st.MongoSession().DB("admin")
   382  	err = adminDB.Login("admin", "invalid-password")
   383  	c.Assert(err, gc.ErrorMatches, "auth fail(s|ed)")
   384  	err = adminDB.Login("admin", info.Password)
   385  	c.Assert(err, jc.ErrorIsNil)
   386  
   387  	// Check that the admin user has been given an appropriate
   388  	// password
   389  	u, err := st.User(names.NewLocalUserTag("admin"))
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	c.Assert(u.PasswordValid(testPassword), jc.IsTrue)
   392  
   393  	// Check that the machine configuration has been given a new
   394  	// password and that we can connect to mongo as that machine
   395  	// and that the in-mongo password also verifies correctly.
   396  	machineConf1, err := agent.ReadConfig(agent.ConfigPath(machineConf.DataDir(), names.NewMachineTag("0")))
   397  	c.Assert(err, jc.ErrorIsNil)
   398  
   399  	stateinfo, ok := machineConf1.MongoInfo()
   400  	c.Assert(ok, jc.IsTrue)
   401  	st, err = state.Open(testing.EnvironmentTag, stateinfo, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   402  	c.Assert(err, jc.ErrorIsNil)
   403  	defer st.Close()
   404  
   405  	m, err := st.Machine("0")
   406  	c.Assert(err, jc.ErrorIsNil)
   407  	c.Assert(m.HasVote(), jc.IsTrue)
   408  }
   409  
   410  var bootstrapArgTests = []struct {
   411  	input              []string
   412  	err                string
   413  	expectedInstanceId string
   414  	expectedHardware   instance.HardwareCharacteristics
   415  	expectedConfig     map[string]interface{}
   416  }{
   417  	{
   418  		// no value supplied for env-config
   419  		err: "--env-config option must be set",
   420  	}, {
   421  		// empty env-config
   422  		input: []string{"--env-config", ""},
   423  		err:   "--env-config option must be set",
   424  	}, {
   425  		// wrong, should be base64
   426  		input: []string{"--env-config", "name: banana\n"},
   427  		err:   ".*illegal base64 data at input byte.*",
   428  	}, {
   429  		// no value supplied for instance-id
   430  		input: []string{
   431  			"--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   432  		},
   433  		err: "--instance-id option must be set",
   434  	}, {
   435  		// empty instance-id
   436  		input: []string{
   437  			"--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   438  			"--instance-id", "",
   439  		},
   440  		err: "--instance-id option must be set",
   441  	}, {
   442  		input: []string{
   443  			"--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   444  			"--instance-id", "anything",
   445  		},
   446  		expectedInstanceId: "anything",
   447  		expectedConfig:     map[string]interface{}{"name": "banana"},
   448  	}, {
   449  		input: []string{
   450  			"--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   451  			"--instance-id", "anything",
   452  			"--hardware", "nonsense",
   453  		},
   454  		err: `invalid value "nonsense" for flag --hardware: malformed characteristic "nonsense"`,
   455  	}, {
   456  		input: []string{
   457  			"--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   458  			"--instance-id", "anything",
   459  			"--hardware", "arch=amd64 cpu-cores=4 root-disk=2T",
   460  		},
   461  		expectedInstanceId: "anything",
   462  		expectedHardware:   instance.MustParseHardware("arch=amd64 cpu-cores=4 root-disk=2T"),
   463  		expectedConfig:     map[string]interface{}{"name": "banana"},
   464  	},
   465  }
   466  
   467  func (s *BootstrapSuite) TestBootstrapArgs(c *gc.C) {
   468  	for i, t := range bootstrapArgTests {
   469  		c.Logf("test %d", i)
   470  		var args []string
   471  		args = append(args, t.input...)
   472  		_, cmd, err := s.initBootstrapCommand(c, nil, args...)
   473  		if t.err == "" {
   474  			c.Assert(cmd, gc.NotNil)
   475  			c.Assert(err, jc.ErrorIsNil)
   476  			c.Assert(cmd.EnvConfig, gc.DeepEquals, t.expectedConfig)
   477  			c.Assert(cmd.InstanceId, gc.Equals, t.expectedInstanceId)
   478  			c.Assert(cmd.Hardware, gc.DeepEquals, t.expectedHardware)
   479  		} else {
   480  			c.Assert(err, gc.ErrorMatches, t.err)
   481  		}
   482  	}
   483  }
   484  
   485  func (s *BootstrapSuite) TestInitializeStateArgs(c *gc.C) {
   486  	var called int
   487  	initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, machineCfg agent.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) {
   488  		called++
   489  		c.Assert(dialOpts.Direct, jc.IsTrue)
   490  		c.Assert(dialOpts.Timeout, gc.Equals, 30*time.Second)
   491  		c.Assert(dialOpts.SocketTimeout, gc.Equals, 123*time.Second)
   492  		return nil, nil, errors.New("failed to initialize state")
   493  	}
   494  	s.PatchValue(&agentInitializeState, initializeState)
   495  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   496  	c.Assert(err, jc.ErrorIsNil)
   497  	err = cmd.Run(nil)
   498  	c.Assert(err, gc.ErrorMatches, "failed to initialize state")
   499  	c.Assert(called, gc.Equals, 1)
   500  }
   501  
   502  func (s *BootstrapSuite) TestInitializeStateMinSocketTimeout(c *gc.C) {
   503  	var called int
   504  	initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, machineCfg agent.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) {
   505  		called++
   506  		c.Assert(dialOpts.Direct, jc.IsTrue)
   507  		c.Assert(dialOpts.SocketTimeout, gc.Equals, 1*time.Minute)
   508  		return nil, nil, errors.New("failed to initialize state")
   509  	}
   510  
   511  	envcfg, err := s.envcfg.Apply(map[string]interface{}{
   512  		"bootstrap-timeout": "13",
   513  	})
   514  	c.Assert(err, jc.ErrorIsNil)
   515  	b64yamlEnvcfg := b64yaml(envcfg.AllAttrs()).encode()
   516  
   517  	s.PatchValue(&agentInitializeState, initializeState)
   518  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   519  	c.Assert(err, jc.ErrorIsNil)
   520  	err = cmd.Run(nil)
   521  	c.Assert(err, gc.ErrorMatches, "failed to initialize state")
   522  	c.Assert(called, gc.Equals, 1)
   523  }
   524  
   525  func (s *BootstrapSuite) TestSystemIdentityWritten(c *gc.C) {
   526  	_, err := os.Stat(filepath.Join(s.dataDir, agent.SystemIdentity))
   527  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   528  
   529  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   530  	c.Assert(err, jc.ErrorIsNil)
   531  	err = cmd.Run(nil)
   532  	c.Assert(err, jc.ErrorIsNil)
   533  
   534  	data, err := ioutil.ReadFile(filepath.Join(s.dataDir, agent.SystemIdentity))
   535  	c.Assert(err, jc.ErrorIsNil)
   536  	c.Assert(string(data), gc.Equals, "private-key")
   537  }
   538  
   539  func (s *BootstrapSuite) TestDownloadedToolsMetadata(c *gc.C) {
   540  	// Tools downloaded by cloud-init script.
   541  	s.testToolsMetadata(c, false)
   542  }
   543  
   544  func (s *BootstrapSuite) TestUploadedToolsMetadata(c *gc.C) {
   545  	// Tools uploaded over ssh.
   546  	s.writeDownloadedTools(c, &tools.Tools{
   547  		Version: version.Current,
   548  		URL:     "file:///does/not/matter",
   549  	})
   550  	s.testToolsMetadata(c, true)
   551  }
   552  
   553  func (s *BootstrapSuite) testToolsMetadata(c *gc.C, exploded bool) {
   554  	envtesting.RemoveFakeToolsMetadata(c, s.toolsStorage)
   555  
   556  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   557  	c.Assert(err, jc.ErrorIsNil)
   558  	err = cmd.Run(nil)
   559  	c.Assert(err, jc.ErrorIsNil)
   560  
   561  	// We don't write metadata at bootstrap anymore.
   562  	simplestreamsMetadata, err := envtools.ReadMetadata(s.toolsStorage, "released")
   563  	c.Assert(err, jc.ErrorIsNil)
   564  	c.Assert(simplestreamsMetadata, gc.HasLen, 0)
   565  
   566  	// The tools should have been added to tools storage, and
   567  	// exploded into each of the supported series of
   568  	// the same operating system if the tools were uploaded.
   569  	st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{
   570  		Info: mongo.Info{
   571  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   572  			CACert: testing.CACert,
   573  		},
   574  		Password: testPasswordHash(),
   575  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   576  	c.Assert(err, jc.ErrorIsNil)
   577  	defer st.Close()
   578  	expectedSeries := make(set.Strings)
   579  	if exploded {
   580  		for _, series := range version.SupportedSeries() {
   581  			os, err := version.GetOSFromSeries(series)
   582  			c.Assert(err, jc.ErrorIsNil)
   583  			if os == version.Current.OS {
   584  				expectedSeries.Add(series)
   585  			}
   586  		}
   587  	} else {
   588  		expectedSeries.Add(version.Current.Series)
   589  	}
   590  
   591  	storage, err := st.ToolsStorage()
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	defer storage.Close()
   594  	metadata, err := storage.AllMetadata()
   595  	c.Assert(err, jc.ErrorIsNil)
   596  	c.Assert(metadata, gc.HasLen, expectedSeries.Size())
   597  	for _, m := range metadata {
   598  		c.Assert(expectedSeries.Contains(m.Version.Series), jc.IsTrue)
   599  	}
   600  }
   601  
   602  func (s *BootstrapSuite) TestImageMetadata(c *gc.C) {
   603  	metadataDir := c.MkDir()
   604  	expected := []struct{ path, content string }{{
   605  		path:    "images/streams/v1/index.json",
   606  		content: "abc",
   607  	}, {
   608  		path:    "images/streams/v1/products.json",
   609  		content: "def",
   610  	}, {
   611  		path:    "wayward/file.txt",
   612  		content: "ghi",
   613  	}}
   614  	for _, pair := range expected {
   615  		path := filepath.Join(metadataDir, pair.path)
   616  		err := os.MkdirAll(filepath.Dir(path), 0755)
   617  		c.Assert(err, jc.ErrorIsNil)
   618  		err = ioutil.WriteFile(path, []byte(pair.content), 0644)
   619  		c.Assert(err, jc.ErrorIsNil)
   620  	}
   621  
   622  	var stor statetesting.MapStorage
   623  	s.PatchValue(&newStateStorage, func(string, *mgo.Session) statestorage.Storage {
   624  		return &stor
   625  	})
   626  
   627  	_, cmd, err := s.initBootstrapCommand(
   628  		c, nil,
   629  		"--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId),
   630  		"--image-metadata", metadataDir,
   631  	)
   632  	c.Assert(err, jc.ErrorIsNil)
   633  	err = cmd.Run(nil)
   634  	c.Assert(err, jc.ErrorIsNil)
   635  
   636  	// The contents of the directory should have been added to
   637  	// environment storage.
   638  	for _, pair := range expected {
   639  		r, length, err := stor.Get(pair.path)
   640  		c.Assert(err, jc.ErrorIsNil)
   641  		data, err := ioutil.ReadAll(r)
   642  		r.Close()
   643  		c.Assert(err, jc.ErrorIsNil)
   644  		c.Assert(length, gc.Equals, int64(len(pair.content)))
   645  		c.Assert(data, gc.HasLen, int(length))
   646  		c.Assert(string(data), gc.Equals, pair.content)
   647  	}
   648  }
   649  
   650  func (s *BootstrapSuite) makeTestEnv(c *gc.C) {
   651  	attrs := dummy.SampleConfig().Merge(
   652  		testing.Attrs{
   653  			"agent-version":     version.Current.Number.String(),
   654  			"bootstrap-timeout": "123",
   655  		},
   656  	).Delete("admin-secret", "ca-private-key")
   657  	cfg, err := config.New(config.NoDefaults, attrs)
   658  	c.Assert(err, jc.ErrorIsNil)
   659  	provider, err := environs.Provider(cfg.Type())
   660  	c.Assert(err, jc.ErrorIsNil)
   661  	env, err := provider.PrepareForBootstrap(nullContext(), cfg)
   662  	c.Assert(err, jc.ErrorIsNil)
   663  
   664  	envtesting.MustUploadFakeTools(s.toolsStorage, cfg.AgentStream(), cfg.AgentStream())
   665  	inst, _, _, err := jujutesting.StartInstance(env, "0")
   666  	c.Assert(err, jc.ErrorIsNil)
   667  	s.instanceId = inst.Id()
   668  
   669  	addresses, err := inst.Addresses()
   670  	c.Assert(err, jc.ErrorIsNil)
   671  	s.bootstrapName = network.SelectPublicAddress(addresses)
   672  	s.envcfg = env.Config()
   673  	s.b64yamlEnvcfg = b64yaml(s.envcfg.AllAttrs()).encode()
   674  }
   675  
   676  func nullContext() environs.BootstrapContext {
   677  	ctx, _ := cmd.DefaultContext()
   678  	ctx.Stdin = io.LimitReader(nil, 0)
   679  	ctx.Stdout = ioutil.Discard
   680  	ctx.Stderr = ioutil.Discard
   681  	return envcmd.BootstrapContext(ctx)
   682  }
   683  
   684  type b64yaml map[string]interface{}
   685  
   686  func (m b64yaml) encode() string {
   687  	data, err := goyaml.Marshal(m)
   688  	if err != nil {
   689  		panic(err)
   690  	}
   691  	return base64.StdEncoding.EncodeToString(data)
   692  }
   693  
   694  func (s *BootstrapSuite) TestDefaultStoragePools(c *gc.C) {
   695  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
   696  	c.Assert(err, jc.ErrorIsNil)
   697  	err = cmd.Run(nil)
   698  	c.Assert(err, jc.ErrorIsNil)
   699  
   700  	st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{
   701  		Info: mongo.Info{
   702  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   703  			CACert: testing.CACert,
   704  		},
   705  		Password: testPasswordHash(),
   706  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   707  	c.Assert(err, jc.ErrorIsNil)
   708  	defer st.Close()
   709  
   710  	settings := state.NewStateSettings(st)
   711  	pm := poolmanager.New(settings)
   712  	for _, p := range []string{"ebs-ssd"} {
   713  		_, err = pm.Get(p)
   714  		c.Assert(err, jc.ErrorIsNil)
   715  	}
   716  }