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