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()