github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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.makeTestEnv(c) 90 } 91 92 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 93 s.MgoSuite.TearDownSuite(c) 94 s.BaseSuite.TearDownSuite(c) 95 dummy.Reset() 96 } 97 98 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 99 s.BaseSuite.SetUpTest(c) 100 s.PatchValue(&sshGenerateKey, func(name string) (string, string, error) { 101 return "private-key", "public-key", nil 102 }) 103 104 s.MgoSuite.SetUpTest(c) 105 s.dataDir = c.MkDir() 106 s.logDir = c.MkDir() 107 s.mongoOplogSize = "1234" 108 s.fakeEnsureMongo = agenttesting.InstallFakeEnsureMongo(s) 109 s.PatchValue(&maybeInitiateMongoServer, s.fakeEnsureMongo.InitiateMongo) 110 111 // Create fake tools.tar.gz and downloaded-tools.txt. 112 toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, version.Current)) 113 err := os.MkdirAll(toolsDir, 0755) 114 c.Assert(err, jc.ErrorIsNil) 115 err = ioutil.WriteFile(filepath.Join(toolsDir, "tools.tar.gz"), nil, 0644) 116 c.Assert(err, jc.ErrorIsNil) 117 s.writeDownloadedTools(c, &tools.Tools{Version: version.Current}) 118 } 119 120 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 121 s.MgoSuite.TearDownTest(c) 122 s.BaseSuite.TearDownTest(c) 123 } 124 125 func (s *BootstrapSuite) writeDownloadedTools(c *gc.C, tools *tools.Tools) { 126 toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, tools.Version)) 127 err := os.MkdirAll(toolsDir, 0755) 128 c.Assert(err, jc.ErrorIsNil) 129 data, err := json.Marshal(tools) 130 c.Assert(err, jc.ErrorIsNil) 131 err = ioutil.WriteFile(filepath.Join(toolsDir, "downloaded-tools.txt"), data, 0644) 132 c.Assert(err, jc.ErrorIsNil) 133 } 134 135 var testPassword = "my-admin-secret" 136 137 func testPasswordHash() string { 138 return utils.UserPasswordHash(testPassword, utils.CompatSalt) 139 } 140 141 func (s *BootstrapSuite) initBootstrapCommand(c *gc.C, jobs []multiwatcher.MachineJob, args ...string) (machineConf agent.ConfigSetterWriter, cmd *BootstrapCommand, err error) { 142 if len(jobs) == 0 { 143 // Add default jobs. 144 jobs = []multiwatcher.MachineJob{ 145 multiwatcher.JobManageEnviron, 146 multiwatcher.JobHostUnits, 147 multiwatcher.JobManageNetworking, 148 } 149 } 150 // NOTE: the old test used an equivalent of the NewAgentConfig, but it 151 // really should be using NewStateMachineConfig. 152 agentParams := agent.AgentConfigParams{ 153 LogDir: s.logDir, 154 DataDir: s.dataDir, 155 Jobs: jobs, 156 Tag: names.NewMachineTag("0"), 157 UpgradedToVersion: version.Current.Number, 158 Password: testPasswordHash(), 159 Nonce: agent.BootstrapNonce, 160 Environment: testing.EnvironmentTag, 161 StateAddresses: []string{gitjujutesting.MgoServer.Addr()}, 162 APIAddresses: []string{"0.1.2.3:1234"}, 163 CACert: testing.CACert, 164 Values: map[string]string{ 165 agent.Namespace: "foobar", 166 agent.MongoOplogSize: s.mongoOplogSize, 167 }, 168 } 169 servingInfo := params.StateServingInfo{ 170 Cert: "some cert", 171 PrivateKey: "some key", 172 CAPrivateKey: "another key", 173 APIPort: 3737, 174 StatePort: gitjujutesting.MgoServer.Port(), 175 } 176 177 machineConf, err = agent.NewStateMachineConfig(agentParams, servingInfo) 178 c.Assert(err, jc.ErrorIsNil) 179 err = machineConf.Write() 180 c.Assert(err, jc.ErrorIsNil) 181 182 cmd = NewBootstrapCommand() 183 184 err = testing.InitCommand(cmd, append([]string{"--data-dir", s.dataDir}, args...)) 185 return machineConf, cmd, err 186 } 187 188 func (s *BootstrapSuite) TestInitializeEnvironment(c *gc.C) { 189 hw := instance.MustParseHardware("arch=amd64 mem=8G") 190 machConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), "--hardware", hw.String()) 191 c.Assert(err, jc.ErrorIsNil) 192 err = cmd.Run(nil) 193 c.Assert(err, jc.ErrorIsNil) 194 195 c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir) 196 c.Assert(s.fakeEnsureMongo.InitiateCount, gc.Equals, 1) 197 c.Assert(s.fakeEnsureMongo.EnsureCount, gc.Equals, 1) 198 c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir) 199 c.Assert(s.fakeEnsureMongo.OplogSize, gc.Equals, 1234) 200 201 expectInfo, exists := machConf.StateServingInfo() 202 c.Assert(exists, jc.IsTrue) 203 c.Assert(expectInfo.SharedSecret, gc.Equals, "") 204 c.Assert(expectInfo.SystemIdentity, gc.Equals, "") 205 206 servingInfo := s.fakeEnsureMongo.Info 207 c.Assert(len(servingInfo.SharedSecret), gc.Not(gc.Equals), 0) 208 c.Assert(len(servingInfo.SystemIdentity), gc.Not(gc.Equals), 0) 209 servingInfo.SharedSecret = "" 210 servingInfo.SystemIdentity = "" 211 expect := cmdutil.ParamsStateServingInfoToStateStateServingInfo(expectInfo) 212 c.Assert(servingInfo, jc.DeepEquals, expect) 213 expectDialAddrs := []string{fmt.Sprintf("127.0.0.1:%d", expectInfo.StatePort)} 214 gotDialAddrs := s.fakeEnsureMongo.InitiateParams.DialInfo.Addrs 215 c.Assert(gotDialAddrs, gc.DeepEquals, expectDialAddrs) 216 217 memberHost := fmt.Sprintf("%s:%d", s.bootstrapName, expectInfo.StatePort) 218 c.Assert(s.fakeEnsureMongo.InitiateParams.MemberHostPort, gc.Equals, memberHost) 219 c.Assert(s.fakeEnsureMongo.InitiateParams.User, gc.Equals, "") 220 c.Assert(s.fakeEnsureMongo.InitiateParams.Password, gc.Equals, "") 221 222 st, err := state.Open(&mongo.MongoInfo{ 223 Info: mongo.Info{ 224 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 225 CACert: testing.CACert, 226 }, 227 Password: testPasswordHash(), 228 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 229 c.Assert(err, jc.ErrorIsNil) 230 defer st.Close() 231 machines, err := st.AllMachines() 232 c.Assert(err, jc.ErrorIsNil) 233 c.Assert(machines, gc.HasLen, 1) 234 235 instid, err := machines[0].InstanceId() 236 c.Assert(err, jc.ErrorIsNil) 237 c.Assert(instid, gc.Equals, instance.Id(string(s.instanceId))) 238 239 stateHw, err := machines[0].HardwareCharacteristics() 240 c.Assert(err, jc.ErrorIsNil) 241 c.Assert(stateHw, gc.NotNil) 242 c.Assert(*stateHw, gc.DeepEquals, hw) 243 244 cons, err := st.EnvironConstraints() 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(&cons, jc.Satisfies, constraints.IsEmpty) 247 248 cfg, err := st.EnvironConfig() 249 c.Assert(err, jc.ErrorIsNil) 250 c.Assert(cfg.AuthorizedKeys(), gc.Equals, s.envcfg.AuthorizedKeys()+"\npublic-key") 251 } 252 253 func (s *BootstrapSuite) TestInitializeEnvironmentInvalidOplogSize(c *gc.C) { 254 s.mongoOplogSize = "NaN" 255 hw := instance.MustParseHardware("arch=amd64 mem=8G") 256 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), "--hardware", hw.String()) 257 c.Assert(err, jc.ErrorIsNil) 258 err = cmd.Run(nil) 259 c.Assert(err, gc.ErrorMatches, `invalid oplog size: "NaN"`) 260 } 261 262 func (s *BootstrapSuite) TestSetConstraints(c *gc.C) { 263 tcons := constraints.Value{Mem: uint64p(2048), CpuCores: uint64p(2)} 264 _, cmd, err := s.initBootstrapCommand(c, nil, 265 "--env-config", s.b64yamlEnvcfg, 266 "--instance-id", string(s.instanceId), 267 "--constraints", tcons.String(), 268 ) 269 c.Assert(err, jc.ErrorIsNil) 270 err = cmd.Run(nil) 271 c.Assert(err, jc.ErrorIsNil) 272 273 st, err := state.Open(&mongo.MongoInfo{ 274 Info: mongo.Info{ 275 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 276 CACert: testing.CACert, 277 }, 278 Password: testPasswordHash(), 279 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 280 c.Assert(err, jc.ErrorIsNil) 281 defer st.Close() 282 cons, err := st.EnvironConstraints() 283 c.Assert(err, jc.ErrorIsNil) 284 c.Assert(cons, gc.DeepEquals, tcons) 285 286 machines, err := st.AllMachines() 287 c.Assert(err, jc.ErrorIsNil) 288 c.Assert(machines, gc.HasLen, 1) 289 cons, err = machines[0].Constraints() 290 c.Assert(err, jc.ErrorIsNil) 291 c.Assert(cons, gc.DeepEquals, tcons) 292 } 293 294 func uint64p(v uint64) *uint64 { 295 return &v 296 } 297 298 func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) { 299 expectedJobs := []state.MachineJob{ 300 state.JobManageEnviron, 301 state.JobHostUnits, 302 state.JobManageNetworking, 303 } 304 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 305 c.Assert(err, jc.ErrorIsNil) 306 err = cmd.Run(nil) 307 c.Assert(err, jc.ErrorIsNil) 308 309 st, err := state.Open(&mongo.MongoInfo{ 310 Info: mongo.Info{ 311 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 312 CACert: testing.CACert, 313 }, 314 Password: testPasswordHash(), 315 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 316 c.Assert(err, jc.ErrorIsNil) 317 defer st.Close() 318 m, err := st.Machine("0") 319 c.Assert(err, jc.ErrorIsNil) 320 c.Assert(m.Jobs(), gc.DeepEquals, expectedJobs) 321 } 322 323 func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) { 324 jobs := []multiwatcher.MachineJob{multiwatcher.JobManageEnviron} 325 _, cmd, err := s.initBootstrapCommand(c, jobs, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 326 c.Assert(err, jc.ErrorIsNil) 327 err = cmd.Run(nil) 328 c.Assert(err, jc.ErrorIsNil) 329 330 st, err := state.Open(&mongo.MongoInfo{ 331 Info: mongo.Info{ 332 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 333 CACert: testing.CACert, 334 }, 335 Password: testPasswordHash(), 336 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 337 c.Assert(err, jc.ErrorIsNil) 338 defer st.Close() 339 m, err := st.Machine("0") 340 c.Assert(err, jc.ErrorIsNil) 341 c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageEnviron}) 342 } 343 344 func testOpenState(c *gc.C, info *mongo.MongoInfo, expectErrType error) { 345 st, err := state.Open(info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 346 if st != nil { 347 st.Close() 348 } 349 if expectErrType != nil { 350 c.Assert(err, gc.FitsTypeOf, expectErrType) 351 } else { 352 c.Assert(err, jc.ErrorIsNil) 353 } 354 } 355 356 func (s *BootstrapSuite) TestInitialPassword(c *gc.C) { 357 machineConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 358 c.Assert(err, jc.ErrorIsNil) 359 360 err = cmd.Run(nil) 361 c.Assert(err, jc.ErrorIsNil) 362 363 info := &mongo.MongoInfo{ 364 Info: mongo.Info{ 365 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 366 CACert: testing.CACert, 367 }, 368 } 369 370 // Check we can log in to mongo as admin. 371 // TODO(dfc) does passing nil for the admin user name make your skin crawl ? mine too. 372 info.Tag, info.Password = nil, testPasswordHash() 373 st, err := state.Open(info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 374 c.Assert(err, jc.ErrorIsNil) 375 defer st.Close() 376 377 // We're running Mongo with --noauth; let's explicitly verify 378 // that we can login as that user. Even with --noauth, an 379 // explicit Login will still be verified. 380 adminDB := st.MongoSession().DB("admin") 381 err = adminDB.Login("admin", "invalid-password") 382 c.Assert(err, gc.ErrorMatches, "auth fail(s|ed)") 383 err = adminDB.Login("admin", info.Password) 384 c.Assert(err, jc.ErrorIsNil) 385 386 // Check that the admin user has been given an appropriate 387 // password 388 u, err := st.User(names.NewLocalUserTag("admin")) 389 c.Assert(err, jc.ErrorIsNil) 390 c.Assert(u.PasswordValid(testPassword), jc.IsTrue) 391 392 // Check that the machine configuration has been given a new 393 // password and that we can connect to mongo as that machine 394 // and that the in-mongo password also verifies correctly. 395 machineConf1, err := agent.ReadConfig(agent.ConfigPath(machineConf.DataDir(), names.NewMachineTag("0"))) 396 c.Assert(err, jc.ErrorIsNil) 397 398 stateinfo, ok := machineConf1.MongoInfo() 399 c.Assert(ok, jc.IsTrue) 400 st, err = state.Open(stateinfo, mongo.DialOpts{}, environs.NewStatePolicy()) 401 c.Assert(err, jc.ErrorIsNil) 402 defer st.Close() 403 404 m, err := st.Machine("0") 405 c.Assert(err, jc.ErrorIsNil) 406 c.Assert(m.HasVote(), jc.IsTrue) 407 } 408 409 var bootstrapArgTests = []struct { 410 input []string 411 err string 412 expectedInstanceId string 413 expectedHardware instance.HardwareCharacteristics 414 expectedConfig map[string]interface{} 415 }{ 416 { 417 // no value supplied for env-config 418 err: "--env-config option must be set", 419 }, { 420 // empty env-config 421 input: []string{"--env-config", ""}, 422 err: "--env-config option must be set", 423 }, { 424 // wrong, should be base64 425 input: []string{"--env-config", "name: banana\n"}, 426 err: ".*illegal base64 data at input byte.*", 427 }, { 428 // no value supplied for instance-id 429 input: []string{ 430 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 431 }, 432 err: "--instance-id option must be set", 433 }, { 434 // empty instance-id 435 input: []string{ 436 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 437 "--instance-id", "", 438 }, 439 err: "--instance-id option must be set", 440 }, { 441 input: []string{ 442 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 443 "--instance-id", "anything", 444 }, 445 expectedInstanceId: "anything", 446 expectedConfig: map[string]interface{}{"name": "banana"}, 447 }, { 448 input: []string{ 449 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 450 "--instance-id", "anything", 451 "--hardware", "nonsense", 452 }, 453 err: `invalid value "nonsense" for flag --hardware: malformed characteristic "nonsense"`, 454 }, { 455 input: []string{ 456 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 457 "--instance-id", "anything", 458 "--hardware", "arch=amd64 cpu-cores=4 root-disk=2T", 459 }, 460 expectedInstanceId: "anything", 461 expectedHardware: instance.MustParseHardware("arch=amd64 cpu-cores=4 root-disk=2T"), 462 expectedConfig: map[string]interface{}{"name": "banana"}, 463 }, 464 } 465 466 func (s *BootstrapSuite) TestBootstrapArgs(c *gc.C) { 467 for i, t := range bootstrapArgTests { 468 c.Logf("test %d", i) 469 var args []string 470 args = append(args, t.input...) 471 _, cmd, err := s.initBootstrapCommand(c, nil, args...) 472 if t.err == "" { 473 c.Assert(cmd, gc.NotNil) 474 c.Assert(err, jc.ErrorIsNil) 475 c.Assert(cmd.EnvConfig, gc.DeepEquals, t.expectedConfig) 476 c.Assert(cmd.InstanceId, gc.Equals, t.expectedInstanceId) 477 c.Assert(cmd.Hardware, gc.DeepEquals, t.expectedHardware) 478 } else { 479 c.Assert(err, gc.ErrorMatches, t.err) 480 } 481 } 482 } 483 484 func (s *BootstrapSuite) TestInitializeStateArgs(c *gc.C) { 485 var called int 486 initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, machineCfg agent.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) { 487 called++ 488 c.Assert(dialOpts.Direct, jc.IsTrue) 489 c.Assert(dialOpts.Timeout, gc.Equals, 30*time.Second) 490 c.Assert(dialOpts.SocketTimeout, gc.Equals, 123*time.Second) 491 return nil, nil, errors.New("failed to initialize state") 492 } 493 s.PatchValue(&agentInitializeState, initializeState) 494 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 495 c.Assert(err, jc.ErrorIsNil) 496 err = cmd.Run(nil) 497 c.Assert(err, gc.ErrorMatches, "failed to initialize state") 498 c.Assert(called, gc.Equals, 1) 499 } 500 501 func (s *BootstrapSuite) TestInitializeStateMinSocketTimeout(c *gc.C) { 502 var called int 503 initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, machineCfg agent.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) { 504 called++ 505 c.Assert(dialOpts.Direct, jc.IsTrue) 506 c.Assert(dialOpts.SocketTimeout, gc.Equals, 1*time.Minute) 507 return nil, nil, errors.New("failed to initialize state") 508 } 509 510 envcfg, err := s.envcfg.Apply(map[string]interface{}{ 511 "bootstrap-timeout": "13", 512 }) 513 c.Assert(err, jc.ErrorIsNil) 514 b64yamlEnvcfg := b64yaml(envcfg.AllAttrs()).encode() 515 516 s.PatchValue(&agentInitializeState, initializeState) 517 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 518 c.Assert(err, jc.ErrorIsNil) 519 err = cmd.Run(nil) 520 c.Assert(err, gc.ErrorMatches, "failed to initialize state") 521 c.Assert(called, gc.Equals, 1) 522 } 523 524 func (s *BootstrapSuite) TestSystemIdentityWritten(c *gc.C) { 525 _, err := os.Stat(filepath.Join(s.dataDir, agent.SystemIdentity)) 526 c.Assert(err, jc.Satisfies, os.IsNotExist) 527 528 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 529 c.Assert(err, jc.ErrorIsNil) 530 err = cmd.Run(nil) 531 c.Assert(err, jc.ErrorIsNil) 532 533 data, err := ioutil.ReadFile(filepath.Join(s.dataDir, agent.SystemIdentity)) 534 c.Assert(err, jc.ErrorIsNil) 535 c.Assert(string(data), gc.Equals, "private-key") 536 } 537 538 func (s *BootstrapSuite) TestDownloadedToolsMetadata(c *gc.C) { 539 // Tools downloaded by cloud-init script. 540 s.testToolsMetadata(c, false) 541 } 542 543 func (s *BootstrapSuite) TestUploadedToolsMetadata(c *gc.C) { 544 // Tools uploaded over ssh. 545 s.writeDownloadedTools(c, &tools.Tools{ 546 Version: version.Current, 547 URL: "file:///does/not/matter", 548 }) 549 s.testToolsMetadata(c, true) 550 } 551 552 func (s *BootstrapSuite) testToolsMetadata(c *gc.C, exploded bool) { 553 envtesting.RemoveFakeToolsMetadata(c, s.toolsStorage) 554 555 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 556 c.Assert(err, jc.ErrorIsNil) 557 err = cmd.Run(nil) 558 c.Assert(err, jc.ErrorIsNil) 559 560 // We don't write metadata at bootstrap anymore. 561 simplestreamsMetadata, err := envtools.ReadMetadata(s.toolsStorage, "released") 562 c.Assert(err, jc.ErrorIsNil) 563 c.Assert(simplestreamsMetadata, gc.HasLen, 0) 564 565 // The tools should have been added to tools storage, and 566 // exploded into each of the supported series of 567 // the same operating system if the tools were uploaded. 568 st, err := state.Open(&mongo.MongoInfo{ 569 Info: mongo.Info{ 570 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 571 CACert: testing.CACert, 572 }, 573 Password: testPasswordHash(), 574 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 575 c.Assert(err, jc.ErrorIsNil) 576 defer st.Close() 577 expectedSeries := make(set.Strings) 578 if exploded { 579 for _, series := range version.SupportedSeries() { 580 os, err := version.GetOSFromSeries(series) 581 c.Assert(err, jc.ErrorIsNil) 582 if os == version.Current.OS { 583 expectedSeries.Add(series) 584 } 585 } 586 } else { 587 expectedSeries.Add(version.Current.Series) 588 } 589 590 storage, err := st.ToolsStorage() 591 c.Assert(err, jc.ErrorIsNil) 592 defer storage.Close() 593 metadata, err := storage.AllMetadata() 594 c.Assert(err, jc.ErrorIsNil) 595 c.Assert(metadata, gc.HasLen, expectedSeries.Size()) 596 for _, m := range metadata { 597 c.Assert(expectedSeries.Contains(m.Version.Series), jc.IsTrue) 598 } 599 } 600 601 func (s *BootstrapSuite) TestImageMetadata(c *gc.C) { 602 metadataDir := c.MkDir() 603 expected := []struct{ path, content string }{{ 604 path: "images/streams/v1/index.json", 605 content: "abc", 606 }, { 607 path: "images/streams/v1/products.json", 608 content: "def", 609 }, { 610 path: "wayward/file.txt", 611 content: "ghi", 612 }} 613 for _, pair := range expected { 614 path := filepath.Join(metadataDir, pair.path) 615 err := os.MkdirAll(filepath.Dir(path), 0755) 616 c.Assert(err, jc.ErrorIsNil) 617 err = ioutil.WriteFile(path, []byte(pair.content), 0644) 618 c.Assert(err, jc.ErrorIsNil) 619 } 620 621 var stor statetesting.MapStorage 622 s.PatchValue(&newStateStorage, func(string, *mgo.Session) statestorage.Storage { 623 return &stor 624 }) 625 626 _, cmd, err := s.initBootstrapCommand( 627 c, nil, 628 "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), 629 "--image-metadata", metadataDir, 630 ) 631 c.Assert(err, jc.ErrorIsNil) 632 err = cmd.Run(nil) 633 c.Assert(err, jc.ErrorIsNil) 634 635 // The contents of the directory should have been added to 636 // environment storage. 637 for _, pair := range expected { 638 r, length, err := stor.Get(pair.path) 639 c.Assert(err, jc.ErrorIsNil) 640 data, err := ioutil.ReadAll(r) 641 r.Close() 642 c.Assert(err, jc.ErrorIsNil) 643 c.Assert(length, gc.Equals, int64(len(pair.content))) 644 c.Assert(data, gc.HasLen, int(length)) 645 c.Assert(string(data), gc.Equals, pair.content) 646 } 647 } 648 649 func (s *BootstrapSuite) makeTestEnv(c *gc.C) { 650 attrs := dummy.SampleConfig().Merge( 651 testing.Attrs{ 652 "agent-version": version.Current.Number.String(), 653 "bootstrap-timeout": "123", 654 }, 655 ).Delete("admin-secret", "ca-private-key") 656 cfg, err := config.New(config.NoDefaults, attrs) 657 c.Assert(err, jc.ErrorIsNil) 658 provider, err := environs.Provider(cfg.Type()) 659 c.Assert(err, jc.ErrorIsNil) 660 env, err := provider.PrepareForBootstrap(nullContext(), cfg) 661 c.Assert(err, jc.ErrorIsNil) 662 663 envtesting.MustUploadFakeTools(s.toolsStorage, cfg.AgentStream(), cfg.AgentStream()) 664 inst, _, _, err := jujutesting.StartInstance(env, "0") 665 c.Assert(err, jc.ErrorIsNil) 666 s.instanceId = inst.Id() 667 668 addresses, err := inst.Addresses() 669 c.Assert(err, jc.ErrorIsNil) 670 s.bootstrapName = network.SelectPublicAddress(addresses) 671 s.envcfg = env.Config() 672 s.b64yamlEnvcfg = b64yaml(s.envcfg.AllAttrs()).encode() 673 } 674 675 func nullContext() environs.BootstrapContext { 676 ctx, _ := cmd.DefaultContext() 677 ctx.Stdin = io.LimitReader(nil, 0) 678 ctx.Stdout = ioutil.Discard 679 ctx.Stderr = ioutil.Discard 680 return envcmd.BootstrapContext(ctx) 681 } 682 683 type b64yaml map[string]interface{} 684 685 func (m b64yaml) encode() string { 686 data, err := goyaml.Marshal(m) 687 if err != nil { 688 panic(err) 689 } 690 return base64.StdEncoding.EncodeToString(data) 691 } 692 693 func (s *BootstrapSuite) TestDefaultStoragePools(c *gc.C) { 694 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 695 c.Assert(err, jc.ErrorIsNil) 696 err = cmd.Run(nil) 697 c.Assert(err, jc.ErrorIsNil) 698 699 st, err := state.Open(&mongo.MongoInfo{ 700 Info: mongo.Info{ 701 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 702 CACert: testing.CACert, 703 }, 704 Password: testPasswordHash(), 705 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 706 c.Assert(err, jc.ErrorIsNil) 707 defer st.Close() 708 709 settings := state.NewStateSettings(st) 710 pm := poolmanager.New(settings) 711 for _, p := range []string{"ebs-ssd"} { 712 _, err = pm.Get(p) 713 c.Assert(err, jc.ErrorIsNil) 714 } 715 }