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