github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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  	"io/ioutil"
     9  	"path/filepath"
    10  
    11  	gc "launchpad.net/gocheck"
    12  	"launchpad.net/goyaml"
    13  
    14  	"launchpad.net/juju-core/agent"
    15  	"launchpad.net/juju-core/constraints"
    16  	"launchpad.net/juju-core/environs"
    17  	"launchpad.net/juju-core/environs/bootstrap"
    18  	"launchpad.net/juju-core/environs/jujutest"
    19  	"launchpad.net/juju-core/errors"
    20  	"launchpad.net/juju-core/instance"
    21  	"launchpad.net/juju-core/provider/dummy"
    22  	"launchpad.net/juju-core/state"
    23  	"launchpad.net/juju-core/state/api/params"
    24  	"launchpad.net/juju-core/testing"
    25  	jc "launchpad.net/juju-core/testing/checkers"
    26  	"launchpad.net/juju-core/testing/testbase"
    27  	"launchpad.net/juju-core/utils"
    28  	"launchpad.net/juju-core/version"
    29  )
    30  
    31  // We don't want to use JujuConnSuite because it gives us
    32  // an already-bootstrapped environment.
    33  type BootstrapSuite struct {
    34  	testbase.LoggingSuite
    35  	testing.MgoSuite
    36  	dataDir              string
    37  	logDir               string
    38  	providerStateURLFile string
    39  }
    40  
    41  var _ = gc.Suite(&BootstrapSuite{})
    42  
    43  var testRoundTripper = &jujutest.ProxyRoundTripper{}
    44  
    45  func init() {
    46  	// Prepare mock http transport for provider-state output in tests.
    47  	testRoundTripper.RegisterForScheme("test")
    48  }
    49  
    50  func (s *BootstrapSuite) SetUpSuite(c *gc.C) {
    51  	s.LoggingSuite.SetUpSuite(c)
    52  	s.MgoSuite.SetUpSuite(c)
    53  	stateInfo := bootstrap.BootstrapState{
    54  		StateInstances: []instance.Id{instance.Id("dummy.instance.id")},
    55  	}
    56  	stateData, err := goyaml.Marshal(stateInfo)
    57  	c.Assert(err, gc.IsNil)
    58  	content := map[string]string{"/" + bootstrap.StateFile: string(stateData)}
    59  	testRoundTripper.Sub = jujutest.NewCannedRoundTripper(content, nil)
    60  	s.providerStateURLFile = filepath.Join(c.MkDir(), "provider-state-url")
    61  	providerStateURLFile = s.providerStateURLFile
    62  }
    63  
    64  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
    65  	s.MgoSuite.TearDownSuite(c)
    66  	s.LoggingSuite.TearDownSuite(c)
    67  }
    68  
    69  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    70  	s.LoggingSuite.SetUpTest(c)
    71  	s.MgoSuite.SetUpTest(c)
    72  	s.dataDir = c.MkDir()
    73  	s.logDir = c.MkDir()
    74  }
    75  
    76  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
    77  	s.MgoSuite.TearDownTest(c)
    78  	s.LoggingSuite.TearDownTest(c)
    79  }
    80  
    81  var testPassword = "my-admin-secret"
    82  
    83  func testPasswordHash() string {
    84  	return utils.UserPasswordHash(testPassword, utils.CompatSalt)
    85  }
    86  
    87  func (s *BootstrapSuite) initBootstrapCommand(c *gc.C, jobs []params.MachineJob, args ...string) (machineConf agent.Config, cmd *BootstrapCommand, err error) {
    88  	ioutil.WriteFile(s.providerStateURLFile, []byte("test://localhost/provider-state\n"), 0600)
    89  	if len(jobs) == 0 {
    90  		// Add default jobs.
    91  		jobs = []params.MachineJob{
    92  			params.JobManageEnviron, params.JobHostUnits,
    93  		}
    94  	}
    95  	// NOTE: the old test used an equivalent of the NewAgentConfig, but it
    96  	// really should be using NewStateMachineConfig.
    97  	params := agent.AgentConfigParams{
    98  		LogDir:            s.logDir,
    99  		DataDir:           s.dataDir,
   100  		Jobs:              jobs,
   101  		Tag:               "bootstrap",
   102  		UpgradedToVersion: version.Current.Number,
   103  		Password:          testPasswordHash(),
   104  		Nonce:             state.BootstrapNonce,
   105  		StateAddresses:    []string{testing.MgoServer.Addr()},
   106  		APIAddresses:      []string{"0.1.2.3:1234"},
   107  		CACert:            []byte(testing.CACert),
   108  	}
   109  	bootConf, err := agent.NewAgentConfig(params)
   110  	c.Assert(err, gc.IsNil)
   111  	err = bootConf.Write()
   112  	c.Assert(err, gc.IsNil)
   113  
   114  	params.Tag = "machine-0"
   115  	machineConf, err = agent.NewAgentConfig(params)
   116  	c.Assert(err, gc.IsNil)
   117  	err = machineConf.Write()
   118  	c.Assert(err, gc.IsNil)
   119  
   120  	cmd = &BootstrapCommand{}
   121  	err = testing.InitCommand(cmd, append([]string{"--data-dir", s.dataDir}, args...))
   122  	return machineConf, cmd, err
   123  }
   124  
   125  func (s *BootstrapSuite) TestInitializeEnvironment(c *gc.C) {
   126  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", testConfig)
   127  	c.Assert(err, gc.IsNil)
   128  	err = cmd.Run(nil)
   129  	c.Assert(err, gc.IsNil)
   130  
   131  	st, err := state.Open(&state.Info{
   132  		Addrs:    []string{testing.MgoServer.Addr()},
   133  		CACert:   []byte(testing.CACert),
   134  		Password: testPasswordHash(),
   135  	}, state.DefaultDialOpts(), environs.NewStatePolicy())
   136  	c.Assert(err, gc.IsNil)
   137  	defer st.Close()
   138  	machines, err := st.AllMachines()
   139  	c.Assert(err, gc.IsNil)
   140  	c.Assert(machines, gc.HasLen, 1)
   141  
   142  	instid, err := machines[0].InstanceId()
   143  	c.Assert(err, gc.IsNil)
   144  	c.Assert(instid, gc.Equals, instance.Id("dummy.instance.id"))
   145  
   146  	cons, err := st.EnvironConstraints()
   147  	c.Assert(err, gc.IsNil)
   148  	c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
   149  }
   150  
   151  func (s *BootstrapSuite) TestSetConstraints(c *gc.C) {
   152  	tcons := constraints.Value{Mem: uint64p(2048), CpuCores: uint64p(2)}
   153  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", testConfig, "--constraints", tcons.String())
   154  	c.Assert(err, gc.IsNil)
   155  	err = cmd.Run(nil)
   156  	c.Assert(err, gc.IsNil)
   157  
   158  	st, err := state.Open(&state.Info{
   159  		Addrs:    []string{testing.MgoServer.Addr()},
   160  		CACert:   []byte(testing.CACert),
   161  		Password: testPasswordHash(),
   162  	}, state.DefaultDialOpts(), environs.NewStatePolicy())
   163  	c.Assert(err, gc.IsNil)
   164  	defer st.Close()
   165  	cons, err := st.EnvironConstraints()
   166  	c.Assert(err, gc.IsNil)
   167  	c.Assert(cons, gc.DeepEquals, tcons)
   168  
   169  	machines, err := st.AllMachines()
   170  	c.Assert(err, gc.IsNil)
   171  	c.Assert(machines, gc.HasLen, 1)
   172  	cons, err = machines[0].Constraints()
   173  	c.Assert(err, gc.IsNil)
   174  	c.Assert(cons, gc.DeepEquals, tcons)
   175  }
   176  
   177  func uint64p(v uint64) *uint64 {
   178  	return &v
   179  }
   180  
   181  func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) {
   182  	expectedJobs := []state.MachineJob{
   183  		state.JobManageEnviron, state.JobHostUnits,
   184  	}
   185  	_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", testConfig)
   186  	c.Assert(err, gc.IsNil)
   187  	err = cmd.Run(nil)
   188  	c.Assert(err, gc.IsNil)
   189  
   190  	st, err := state.Open(&state.Info{
   191  		Addrs:    []string{testing.MgoServer.Addr()},
   192  		CACert:   []byte(testing.CACert),
   193  		Password: testPasswordHash(),
   194  	}, state.DefaultDialOpts(), environs.NewStatePolicy())
   195  	c.Assert(err, gc.IsNil)
   196  	defer st.Close()
   197  	m, err := st.Machine("0")
   198  	c.Assert(err, gc.IsNil)
   199  	c.Assert(m.Jobs(), gc.DeepEquals, expectedJobs)
   200  }
   201  
   202  func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) {
   203  	jobs := []params.MachineJob{params.JobManageEnviron}
   204  	_, cmd, err := s.initBootstrapCommand(c, jobs, "--env-config", testConfig)
   205  	c.Assert(err, gc.IsNil)
   206  	err = cmd.Run(nil)
   207  	c.Assert(err, gc.IsNil)
   208  
   209  	st, err := state.Open(&state.Info{
   210  		Addrs:    []string{testing.MgoServer.Addr()},
   211  		CACert:   []byte(testing.CACert),
   212  		Password: testPasswordHash(),
   213  	}, state.DefaultDialOpts(), environs.NewStatePolicy())
   214  	c.Assert(err, gc.IsNil)
   215  	defer st.Close()
   216  	m, err := st.Machine("0")
   217  	c.Assert(err, gc.IsNil)
   218  	c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageEnviron})
   219  }
   220  
   221  func testOpenState(c *gc.C, info *state.Info, expectErrType error) {
   222  	st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
   223  	if st != nil {
   224  		st.Close()
   225  	}
   226  	if expectErrType != nil {
   227  		c.Assert(err, gc.FitsTypeOf, expectErrType)
   228  	} else {
   229  		c.Assert(err, gc.IsNil)
   230  	}
   231  }
   232  
   233  func (s *BootstrapSuite) TestInitialPassword(c *gc.C) {
   234  	machineConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", testConfig)
   235  	c.Assert(err, gc.IsNil)
   236  
   237  	err = cmd.Run(nil)
   238  	c.Assert(err, gc.IsNil)
   239  
   240  	// Check that we cannot now connect to the state without a
   241  	// password.
   242  	info := &state.Info{
   243  		Addrs:  []string{testing.MgoServer.Addr()},
   244  		CACert: []byte(testing.CACert),
   245  	}
   246  	testOpenState(c, info, errors.Unauthorizedf(""))
   247  
   248  	// Check we can log in to mongo as admin.
   249  	info.Tag, info.Password = "", testPasswordHash()
   250  	st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
   251  	c.Assert(err, gc.IsNil)
   252  	// Reset password so the tests can continue to use the same server.
   253  	defer st.Close()
   254  	defer st.SetAdminMongoPassword("")
   255  
   256  	// Check that the admin user has been given an appropriate
   257  	// password
   258  	u, err := st.User("admin")
   259  	c.Assert(err, gc.IsNil)
   260  	c.Assert(u.PasswordValid(testPassword), gc.Equals, true)
   261  
   262  	// Check that the machine configuration has been given a new
   263  	// password and that we can connect to mongo as that machine
   264  	// and that the in-mongo password also verifies correctly.
   265  	machineConf1, err := agent.ReadConf(agent.ConfigPath(machineConf.DataDir(), "machine-0"))
   266  	c.Assert(err, gc.IsNil)
   267  
   268  	st, err = machineConf1.OpenState(environs.NewStatePolicy())
   269  	c.Assert(err, gc.IsNil)
   270  	defer st.Close()
   271  }
   272  
   273  var base64ConfigTests = []struct {
   274  	input    []string
   275  	err      string
   276  	expected map[string]interface{}
   277  }{
   278  	{
   279  		// no value supplied
   280  		nil,
   281  		"--env-config option must be set",
   282  		nil,
   283  	}, {
   284  		// empty
   285  		[]string{"--env-config", ""},
   286  		"--env-config option must be set",
   287  		nil,
   288  	}, {
   289  		// wrong, should be base64
   290  		[]string{"--env-config", "name: banana\n"},
   291  		".*illegal base64 data at input byte.*",
   292  		nil,
   293  	}, {
   294  		[]string{"--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n"))},
   295  		"",
   296  		map[string]interface{}{"name": "banana"},
   297  	},
   298  }
   299  
   300  func (s *BootstrapSuite) TestBase64Config(c *gc.C) {
   301  	for i, t := range base64ConfigTests {
   302  		c.Logf("test %d", i)
   303  		var args []string
   304  		args = append(args, t.input...)
   305  		_, cmd, err := s.initBootstrapCommand(c, nil, args...)
   306  		if t.err == "" {
   307  			c.Assert(cmd, gc.NotNil)
   308  			c.Assert(err, gc.IsNil)
   309  			c.Assert(cmd.EnvConfig, gc.DeepEquals, t.expected)
   310  		} else {
   311  			c.Assert(err, gc.ErrorMatches, t.err)
   312  		}
   313  	}
   314  }
   315  
   316  type b64yaml map[string]interface{}
   317  
   318  func (m b64yaml) encode() string {
   319  	data, err := goyaml.Marshal(m)
   320  	if err != nil {
   321  		panic(err)
   322  	}
   323  	return base64.StdEncoding.EncodeToString(data)
   324  }
   325  
   326  var testConfig = b64yaml(
   327  	dummy.SampleConfig().Merge(
   328  		testing.Attrs{
   329  			"state-server":  false,
   330  			"agent-version": "3.4.5",
   331  		},
   332  	).Delete("admin-secret", "ca-private-key")).encode()