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 }