github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/series" 23 "github.com/juju/utils/set" 24 gc "gopkg.in/check.v1" 25 "gopkg.in/mgo.v2" 26 goyaml "gopkg.in/yaml.v1" 27 28 "github.com/juju/juju/agent" 29 agenttools "github.com/juju/juju/agent/tools" 30 "github.com/juju/juju/apiserver/params" 31 "github.com/juju/juju/cmd/envcmd" 32 agenttesting "github.com/juju/juju/cmd/jujud/agent/testing" 33 cmdutil "github.com/juju/juju/cmd/jujud/util" 34 "github.com/juju/juju/constraints" 35 "github.com/juju/juju/environs" 36 "github.com/juju/juju/environs/config" 37 "github.com/juju/juju/environs/configstore" 38 "github.com/juju/juju/environs/filestorage" 39 "github.com/juju/juju/environs/storage" 40 envtesting "github.com/juju/juju/environs/testing" 41 envtools "github.com/juju/juju/environs/tools" 42 "github.com/juju/juju/instance" 43 jujutesting "github.com/juju/juju/juju/testing" 44 "github.com/juju/juju/mongo" 45 "github.com/juju/juju/network" 46 "github.com/juju/juju/provider/dummy" 47 "github.com/juju/juju/state" 48 "github.com/juju/juju/state/cloudimagemetadata" 49 "github.com/juju/juju/state/multiwatcher" 50 statestorage "github.com/juju/juju/state/storage" 51 statetesting "github.com/juju/juju/state/testing" 52 "github.com/juju/juju/storage/poolmanager" 53 "github.com/juju/juju/testing" 54 "github.com/juju/juju/tools" 55 "github.com/juju/juju/version" 56 ) 57 58 var _ = configstore.Default 59 60 // We don't want to use JujuConnSuite because it gives us 61 // an already-bootstrapped environment. 62 type BootstrapSuite struct { 63 testing.BaseSuite 64 gitjujutesting.MgoSuite 65 envcfg *config.Config 66 b64yamlEnvcfg string 67 instanceId instance.Id 68 dataDir string 69 logDir string 70 mongoOplogSize string 71 fakeEnsureMongo *agenttesting.FakeEnsureMongo 72 bootstrapName string 73 74 toolsStorage storage.Storage 75 } 76 77 var _ = gc.Suite(&BootstrapSuite{}) 78 79 func (s *BootstrapSuite) SetUpSuite(c *gc.C) { 80 storageDir := c.MkDir() 81 restorer := gitjujutesting.PatchValue(&envtools.DefaultBaseURL, storageDir) 82 s.AddSuiteCleanup(func(*gc.C) { 83 restorer() 84 }) 85 stor, err := filestorage.NewFileStorageWriter(storageDir) 86 c.Assert(err, jc.ErrorIsNil) 87 s.toolsStorage = stor 88 89 s.BaseSuite.SetUpSuite(c) 90 s.MgoSuite.SetUpSuite(c) 91 s.PatchValue(&version.Current.Number, testing.FakeVersionNumber) 92 s.makeTestEnv(c) 93 } 94 95 func (s *BootstrapSuite) TearDownSuite(c *gc.C) { 96 s.MgoSuite.TearDownSuite(c) 97 s.BaseSuite.TearDownSuite(c) 98 dummy.Reset() 99 } 100 101 func (s *BootstrapSuite) SetUpTest(c *gc.C) { 102 s.BaseSuite.SetUpTest(c) 103 s.PatchValue(&sshGenerateKey, func(name string) (string, string, error) { 104 return "private-key", "public-key", nil 105 }) 106 107 s.MgoSuite.SetUpTest(c) 108 s.dataDir = c.MkDir() 109 s.logDir = c.MkDir() 110 s.mongoOplogSize = "1234" 111 s.fakeEnsureMongo = agenttesting.InstallFakeEnsureMongo(s) 112 s.PatchValue(&maybeInitiateMongoServer, s.fakeEnsureMongo.InitiateMongo) 113 114 // Create fake tools.tar.gz and downloaded-tools.txt. 115 toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, version.Current)) 116 err := os.MkdirAll(toolsDir, 0755) 117 c.Assert(err, jc.ErrorIsNil) 118 err = ioutil.WriteFile(filepath.Join(toolsDir, "tools.tar.gz"), nil, 0644) 119 c.Assert(err, jc.ErrorIsNil) 120 s.writeDownloadedTools(c, &tools.Tools{Version: version.Current}) 121 } 122 123 func (s *BootstrapSuite) TearDownTest(c *gc.C) { 124 s.MgoSuite.TearDownTest(c) 125 s.BaseSuite.TearDownTest(c) 126 } 127 128 func (s *BootstrapSuite) writeDownloadedTools(c *gc.C, tools *tools.Tools) { 129 toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, tools.Version)) 130 err := os.MkdirAll(toolsDir, 0755) 131 c.Assert(err, jc.ErrorIsNil) 132 data, err := json.Marshal(tools) 133 c.Assert(err, jc.ErrorIsNil) 134 err = ioutil.WriteFile(filepath.Join(toolsDir, "downloaded-tools.txt"), data, 0644) 135 c.Assert(err, jc.ErrorIsNil) 136 } 137 138 var testPassword = "my-admin-secret" 139 140 func testPasswordHash() string { 141 return utils.UserPasswordHash(testPassword, utils.CompatSalt) 142 } 143 144 func (s *BootstrapSuite) initBootstrapCommand(c *gc.C, jobs []multiwatcher.MachineJob, args ...string) (machineConf agent.ConfigSetterWriter, cmd *BootstrapCommand, err error) { 145 if len(jobs) == 0 { 146 // Add default jobs. 147 jobs = []multiwatcher.MachineJob{ 148 multiwatcher.JobManageEnviron, 149 multiwatcher.JobHostUnits, 150 multiwatcher.JobManageNetworking, 151 } 152 } 153 // NOTE: the old test used an equivalent of the NewAgentConfig, but it 154 // really should be using NewStateMachineConfig. 155 agentParams := agent.AgentConfigParams{ 156 Paths: agent.Paths{ 157 LogDir: s.logDir, 158 DataDir: s.dataDir, 159 }, 160 Jobs: jobs, 161 Tag: names.NewMachineTag("0"), 162 UpgradedToVersion: version.Current.Number, 163 Password: testPasswordHash(), 164 Nonce: agent.BootstrapNonce, 165 Environment: testing.EnvironmentTag, 166 StateAddresses: []string{gitjujutesting.MgoServer.Addr()}, 167 APIAddresses: []string{"0.1.2.3:1234"}, 168 CACert: testing.CACert, 169 Values: map[string]string{ 170 agent.Namespace: "foobar", 171 agent.MongoOplogSize: s.mongoOplogSize, 172 }, 173 } 174 servingInfo := params.StateServingInfo{ 175 Cert: "some cert", 176 PrivateKey: "some key", 177 CAPrivateKey: "another key", 178 APIPort: 3737, 179 StatePort: gitjujutesting.MgoServer.Port(), 180 } 181 182 machineConf, err = agent.NewStateMachineConfig(agentParams, servingInfo) 183 c.Assert(err, jc.ErrorIsNil) 184 err = machineConf.Write() 185 c.Assert(err, jc.ErrorIsNil) 186 187 cmd = NewBootstrapCommand() 188 189 err = testing.InitCommand(cmd, append([]string{"--data-dir", s.dataDir}, args...)) 190 return machineConf, cmd, err 191 } 192 193 func (s *BootstrapSuite) TestInitializeEnvironment(c *gc.C) { 194 hw := instance.MustParseHardware("arch=amd64 mem=8G") 195 machConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), "--hardware", hw.String()) 196 c.Assert(err, jc.ErrorIsNil) 197 err = cmd.Run(nil) 198 c.Assert(err, jc.ErrorIsNil) 199 200 c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir) 201 c.Assert(s.fakeEnsureMongo.InitiateCount, gc.Equals, 1) 202 c.Assert(s.fakeEnsureMongo.EnsureCount, gc.Equals, 1) 203 c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir) 204 c.Assert(s.fakeEnsureMongo.OplogSize, gc.Equals, 1234) 205 206 expectInfo, exists := machConf.StateServingInfo() 207 c.Assert(exists, jc.IsTrue) 208 c.Assert(expectInfo.SharedSecret, gc.Equals, "") 209 c.Assert(expectInfo.SystemIdentity, gc.Equals, "") 210 211 servingInfo := s.fakeEnsureMongo.Info 212 c.Assert(len(servingInfo.SharedSecret), gc.Not(gc.Equals), 0) 213 c.Assert(len(servingInfo.SystemIdentity), gc.Not(gc.Equals), 0) 214 servingInfo.SharedSecret = "" 215 servingInfo.SystemIdentity = "" 216 expect := cmdutil.ParamsStateServingInfoToStateStateServingInfo(expectInfo) 217 c.Assert(servingInfo, jc.DeepEquals, expect) 218 expectDialAddrs := []string{fmt.Sprintf("127.0.0.1:%d", expectInfo.StatePort)} 219 gotDialAddrs := s.fakeEnsureMongo.InitiateParams.DialInfo.Addrs 220 c.Assert(gotDialAddrs, gc.DeepEquals, expectDialAddrs) 221 222 memberHost := fmt.Sprintf("%s:%d", s.bootstrapName, expectInfo.StatePort) 223 c.Assert(s.fakeEnsureMongo.InitiateParams.MemberHostPort, gc.Equals, memberHost) 224 c.Assert(s.fakeEnsureMongo.InitiateParams.User, gc.Equals, "") 225 c.Assert(s.fakeEnsureMongo.InitiateParams.Password, gc.Equals, "") 226 227 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 228 Info: mongo.Info{ 229 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 230 CACert: testing.CACert, 231 }, 232 Password: testPasswordHash(), 233 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 234 c.Assert(err, jc.ErrorIsNil) 235 defer st.Close() 236 machines, err := st.AllMachines() 237 c.Assert(err, jc.ErrorIsNil) 238 c.Assert(machines, gc.HasLen, 1) 239 240 instid, err := machines[0].InstanceId() 241 c.Assert(err, jc.ErrorIsNil) 242 c.Assert(instid, gc.Equals, instance.Id(string(s.instanceId))) 243 244 stateHw, err := machines[0].HardwareCharacteristics() 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(stateHw, gc.NotNil) 247 c.Assert(*stateHw, gc.DeepEquals, hw) 248 249 cons, err := st.EnvironConstraints() 250 c.Assert(err, jc.ErrorIsNil) 251 c.Assert(&cons, jc.Satisfies, constraints.IsEmpty) 252 253 cfg, err := st.EnvironConfig() 254 c.Assert(err, jc.ErrorIsNil) 255 c.Assert(cfg.AuthorizedKeys(), gc.Equals, s.envcfg.AuthorizedKeys()+"\npublic-key") 256 } 257 258 func (s *BootstrapSuite) TestInitializeEnvironmentInvalidOplogSize(c *gc.C) { 259 s.mongoOplogSize = "NaN" 260 hw := instance.MustParseHardware("arch=amd64 mem=8G") 261 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), "--hardware", hw.String()) 262 c.Assert(err, jc.ErrorIsNil) 263 err = cmd.Run(nil) 264 c.Assert(err, gc.ErrorMatches, `invalid oplog size: "NaN"`) 265 } 266 267 func (s *BootstrapSuite) TestSetConstraints(c *gc.C) { 268 tcons := constraints.Value{Mem: uint64p(2048), CpuCores: uint64p(2)} 269 _, cmd, err := s.initBootstrapCommand(c, nil, 270 "--env-config", s.b64yamlEnvcfg, 271 "--instance-id", string(s.instanceId), 272 "--constraints", tcons.String(), 273 ) 274 c.Assert(err, jc.ErrorIsNil) 275 err = cmd.Run(nil) 276 c.Assert(err, jc.ErrorIsNil) 277 278 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 279 Info: mongo.Info{ 280 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 281 CACert: testing.CACert, 282 }, 283 Password: testPasswordHash(), 284 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 285 c.Assert(err, jc.ErrorIsNil) 286 defer st.Close() 287 cons, err := st.EnvironConstraints() 288 c.Assert(err, jc.ErrorIsNil) 289 c.Assert(cons, gc.DeepEquals, tcons) 290 291 machines, err := st.AllMachines() 292 c.Assert(err, jc.ErrorIsNil) 293 c.Assert(machines, gc.HasLen, 1) 294 cons, err = machines[0].Constraints() 295 c.Assert(err, jc.ErrorIsNil) 296 c.Assert(cons, gc.DeepEquals, tcons) 297 } 298 299 func uint64p(v uint64) *uint64 { 300 return &v 301 } 302 303 func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) { 304 expectedJobs := []state.MachineJob{ 305 state.JobManageEnviron, 306 state.JobHostUnits, 307 state.JobManageNetworking, 308 } 309 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 310 c.Assert(err, jc.ErrorIsNil) 311 err = cmd.Run(nil) 312 c.Assert(err, jc.ErrorIsNil) 313 314 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 315 Info: mongo.Info{ 316 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 317 CACert: testing.CACert, 318 }, 319 Password: testPasswordHash(), 320 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 321 c.Assert(err, jc.ErrorIsNil) 322 defer st.Close() 323 m, err := st.Machine("0") 324 c.Assert(err, jc.ErrorIsNil) 325 c.Assert(m.Jobs(), gc.DeepEquals, expectedJobs) 326 } 327 328 func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) { 329 jobs := []multiwatcher.MachineJob{multiwatcher.JobManageEnviron} 330 _, cmd, err := s.initBootstrapCommand(c, jobs, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 331 c.Assert(err, jc.ErrorIsNil) 332 err = cmd.Run(nil) 333 c.Assert(err, jc.ErrorIsNil) 334 335 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 336 Info: mongo.Info{ 337 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 338 CACert: testing.CACert, 339 }, 340 Password: testPasswordHash(), 341 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 342 c.Assert(err, jc.ErrorIsNil) 343 defer st.Close() 344 m, err := st.Machine("0") 345 c.Assert(err, jc.ErrorIsNil) 346 c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageEnviron}) 347 } 348 349 func testOpenState(c *gc.C, info *mongo.MongoInfo, expectErrType error) { 350 st, err := state.Open(testing.EnvironmentTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 351 if st != nil { 352 st.Close() 353 } 354 if expectErrType != nil { 355 c.Assert(err, gc.FitsTypeOf, expectErrType) 356 } else { 357 c.Assert(err, jc.ErrorIsNil) 358 } 359 } 360 361 func (s *BootstrapSuite) TestInitialPassword(c *gc.C) { 362 machineConf, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 363 c.Assert(err, jc.ErrorIsNil) 364 365 err = cmd.Run(nil) 366 c.Assert(err, jc.ErrorIsNil) 367 368 info := &mongo.MongoInfo{ 369 Info: mongo.Info{ 370 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 371 CACert: testing.CACert, 372 }, 373 } 374 375 // Check we can log in to mongo as admin. 376 // TODO(dfc) does passing nil for the admin user name make your skin crawl ? mine too. 377 info.Tag, info.Password = nil, testPasswordHash() 378 st, err := state.Open(testing.EnvironmentTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 379 c.Assert(err, jc.ErrorIsNil) 380 defer st.Close() 381 382 // We're running Mongo with --noauth; let's explicitly verify 383 // that we can login as that user. Even with --noauth, an 384 // explicit Login will still be verified. 385 adminDB := st.MongoSession().DB("admin") 386 err = adminDB.Login("admin", "invalid-password") 387 c.Assert(err, gc.ErrorMatches, "auth fail(s|ed)") 388 err = adminDB.Login("admin", info.Password) 389 c.Assert(err, jc.ErrorIsNil) 390 391 // Check that the admin user has been given an appropriate 392 // password 393 u, err := st.User(names.NewLocalUserTag("admin")) 394 c.Assert(err, jc.ErrorIsNil) 395 c.Assert(u.PasswordValid(testPassword), jc.IsTrue) 396 397 // Check that the machine configuration has been given a new 398 // password and that we can connect to mongo as that machine 399 // and that the in-mongo password also verifies correctly. 400 machineConf1, err := agent.ReadConfig(agent.ConfigPath(machineConf.DataDir(), names.NewMachineTag("0"))) 401 c.Assert(err, jc.ErrorIsNil) 402 403 stateinfo, ok := machineConf1.MongoInfo() 404 c.Assert(ok, jc.IsTrue) 405 st, err = state.Open(testing.EnvironmentTag, stateinfo, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 406 c.Assert(err, jc.ErrorIsNil) 407 defer st.Close() 408 409 m, err := st.Machine("0") 410 c.Assert(err, jc.ErrorIsNil) 411 c.Assert(m.HasVote(), jc.IsTrue) 412 } 413 414 var bootstrapArgTests = []struct { 415 input []string 416 err string 417 expectedInstanceId string 418 expectedHardware instance.HardwareCharacteristics 419 expectedConfig map[string]interface{} 420 }{ 421 { 422 // no value supplied for env-config 423 err: "--env-config option must be set", 424 }, { 425 // empty env-config 426 input: []string{"--env-config", ""}, 427 err: "--env-config option must be set", 428 }, { 429 // wrong, should be base64 430 input: []string{"--env-config", "name: banana\n"}, 431 err: ".*illegal base64 data at input byte.*", 432 }, { 433 // no value supplied for instance-id 434 input: []string{ 435 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 436 }, 437 err: "--instance-id option must be set", 438 }, { 439 // empty instance-id 440 input: []string{ 441 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 442 "--instance-id", "", 443 }, 444 err: "--instance-id option must be set", 445 }, { 446 input: []string{ 447 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 448 "--instance-id", "anything", 449 }, 450 expectedInstanceId: "anything", 451 expectedConfig: map[string]interface{}{"name": "banana"}, 452 }, { 453 input: []string{ 454 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 455 "--instance-id", "anything", 456 "--hardware", "nonsense", 457 }, 458 err: `invalid value "nonsense" for flag --hardware: malformed characteristic "nonsense"`, 459 }, { 460 input: []string{ 461 "--env-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")), 462 "--instance-id", "anything", 463 "--hardware", "arch=amd64 cpu-cores=4 root-disk=2T", 464 }, 465 expectedInstanceId: "anything", 466 expectedHardware: instance.MustParseHardware("arch=amd64 cpu-cores=4 root-disk=2T"), 467 expectedConfig: map[string]interface{}{"name": "banana"}, 468 }, 469 } 470 471 func (s *BootstrapSuite) TestBootstrapArgs(c *gc.C) { 472 for i, t := range bootstrapArgTests { 473 c.Logf("test %d", i) 474 var args []string 475 args = append(args, t.input...) 476 _, cmd, err := s.initBootstrapCommand(c, nil, args...) 477 if t.err == "" { 478 c.Assert(cmd, gc.NotNil) 479 c.Assert(err, jc.ErrorIsNil) 480 c.Assert(cmd.EnvConfig, gc.DeepEquals, t.expectedConfig) 481 c.Assert(cmd.InstanceId, gc.Equals, t.expectedInstanceId) 482 c.Assert(cmd.Hardware, gc.DeepEquals, t.expectedHardware) 483 } else { 484 c.Assert(err, gc.ErrorMatches, t.err) 485 } 486 } 487 } 488 489 func (s *BootstrapSuite) TestInitializeStateArgs(c *gc.C) { 490 var called int 491 initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, machineCfg agent.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) { 492 called++ 493 c.Assert(dialOpts.Direct, jc.IsTrue) 494 c.Assert(dialOpts.Timeout, gc.Equals, 30*time.Second) 495 c.Assert(dialOpts.SocketTimeout, gc.Equals, 123*time.Second) 496 return nil, nil, errors.New("failed to initialize state") 497 } 498 s.PatchValue(&agentInitializeState, initializeState) 499 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 500 c.Assert(err, jc.ErrorIsNil) 501 err = cmd.Run(nil) 502 c.Assert(err, gc.ErrorMatches, "failed to initialize state") 503 c.Assert(called, gc.Equals, 1) 504 } 505 506 func (s *BootstrapSuite) TestInitializeStateMinSocketTimeout(c *gc.C) { 507 var called int 508 initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, machineCfg agent.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) { 509 called++ 510 c.Assert(dialOpts.Direct, jc.IsTrue) 511 c.Assert(dialOpts.SocketTimeout, gc.Equals, 1*time.Minute) 512 return nil, nil, errors.New("failed to initialize state") 513 } 514 515 envcfg, err := s.envcfg.Apply(map[string]interface{}{ 516 "bootstrap-timeout": "13", 517 }) 518 c.Assert(err, jc.ErrorIsNil) 519 b64yamlEnvcfg := b64yaml(envcfg.AllAttrs()).encode() 520 521 s.PatchValue(&agentInitializeState, initializeState) 522 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 523 c.Assert(err, jc.ErrorIsNil) 524 err = cmd.Run(nil) 525 c.Assert(err, gc.ErrorMatches, "failed to initialize state") 526 c.Assert(called, gc.Equals, 1) 527 } 528 529 func (s *BootstrapSuite) TestSystemIdentityWritten(c *gc.C) { 530 _, err := os.Stat(filepath.Join(s.dataDir, agent.SystemIdentity)) 531 c.Assert(err, jc.Satisfies, os.IsNotExist) 532 533 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 534 c.Assert(err, jc.ErrorIsNil) 535 err = cmd.Run(nil) 536 c.Assert(err, jc.ErrorIsNil) 537 538 data, err := ioutil.ReadFile(filepath.Join(s.dataDir, agent.SystemIdentity)) 539 c.Assert(err, jc.ErrorIsNil) 540 c.Assert(string(data), gc.Equals, "private-key") 541 } 542 543 func (s *BootstrapSuite) TestDownloadedToolsMetadata(c *gc.C) { 544 // Tools downloaded by cloud-init script. 545 s.testToolsMetadata(c, false) 546 } 547 548 func (s *BootstrapSuite) TestUploadedToolsMetadata(c *gc.C) { 549 // Tools uploaded over ssh. 550 s.writeDownloadedTools(c, &tools.Tools{ 551 Version: version.Current, 552 URL: "file:///does/not/matter", 553 }) 554 s.testToolsMetadata(c, true) 555 } 556 557 func (s *BootstrapSuite) testToolsMetadata(c *gc.C, exploded bool) { 558 envtesting.RemoveFakeToolsMetadata(c, s.toolsStorage) 559 560 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 561 c.Assert(err, jc.ErrorIsNil) 562 err = cmd.Run(nil) 563 c.Assert(err, jc.ErrorIsNil) 564 565 // We don't write metadata at bootstrap anymore. 566 simplestreamsMetadata, err := envtools.ReadMetadata(s.toolsStorage, "released") 567 c.Assert(err, jc.ErrorIsNil) 568 c.Assert(simplestreamsMetadata, gc.HasLen, 0) 569 570 // The tools should have been added to tools storage, and 571 // exploded into each of the supported series of 572 // the same operating system if the tools were uploaded. 573 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 574 Info: mongo.Info{ 575 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 576 CACert: testing.CACert, 577 }, 578 Password: testPasswordHash(), 579 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 580 c.Assert(err, jc.ErrorIsNil) 581 defer st.Close() 582 expectedSeries := make(set.Strings) 583 if exploded { 584 for _, ser := range series.SupportedSeries() { 585 os, err := series.GetOSFromSeries(ser) 586 c.Assert(err, jc.ErrorIsNil) 587 hostos, err := series.GetOSFromSeries(version.Current.Series) 588 c.Assert(err, jc.ErrorIsNil) 589 if os == hostos { 590 expectedSeries.Add(ser) 591 } 592 } 593 } else { 594 expectedSeries.Add(series.HostSeries()) 595 } 596 597 storage, err := st.ToolsStorage() 598 c.Assert(err, jc.ErrorIsNil) 599 defer storage.Close() 600 metadata, err := storage.AllMetadata() 601 c.Assert(err, jc.ErrorIsNil) 602 c.Assert(metadata, gc.HasLen, expectedSeries.Size()) 603 for _, m := range metadata { 604 c.Assert(expectedSeries.Contains(m.Version.Series), jc.IsTrue) 605 } 606 } 607 608 const ( 609 indexContent = `{ 610 "index": { 611 "com.ubuntu.cloud:%v": { 612 "updated": "Fri, 17 Jul 2015 13:42:48 +1000", 613 "format": "products:1.0", 614 "datatype": "image-ids", 615 "cloudname": "custom", 616 "clouds": [ 617 { 618 "region": "%v", 619 "endpoint": "endpoint" 620 } 621 ], 622 "path": "streams/v1/products.json", 623 "products": [ 624 "com.ubuntu.cloud:server:14.04:%v" 625 ] 626 } 627 }, 628 "updated": "Fri, 17 Jul 2015 13:42:48 +1000", 629 "format": "index:1.0" 630 }` 631 632 productContent = `{ 633 "products": { 634 "com.ubuntu.cloud:server:14.04:%v": { 635 "version": "14.04", 636 "arch": "%v", 637 "versions": { 638 "20151707": { 639 "items": { 640 "%v": { 641 "id": "%v", 642 "root_store": "%v", 643 "virt": "%v", 644 "region": "%v", 645 "endpoint": "endpoint" 646 } 647 } 648 } 649 } 650 } 651 }, 652 "updated": "Fri, 17 Jul 2015 13:42:48 +1000", 653 "format": "products:1.0", 654 "content_id": "com.ubuntu.cloud:%v" 655 }` 656 ) 657 658 func writeTempFiles(c *gc.C, metadataDir string, expected []struct{ path, content string }) { 659 for _, pair := range expected { 660 path := filepath.Join(metadataDir, pair.path) 661 err := os.MkdirAll(filepath.Dir(path), 0755) 662 c.Assert(err, jc.ErrorIsNil) 663 err = ioutil.WriteFile(path, []byte(pair.content), 0644) 664 c.Assert(err, jc.ErrorIsNil) 665 } 666 } 667 668 func createImageMetadata(c *gc.C) (string, cloudimagemetadata.Metadata) { 669 // setup data for this test 670 metadata := cloudimagemetadata.Metadata{ 671 MetadataAttributes: cloudimagemetadata.MetadataAttributes{ 672 Region: "region", 673 Series: "trusty", 674 Arch: "amd64", 675 VirtType: "virtType", 676 RootStorageType: "rootStore", 677 Source: cloudimagemetadata.Custom}, 678 ImageId: "imageId"} 679 680 // setup files containing test's data 681 metadataDir := c.MkDir() 682 expected := []struct{ path, content string }{{ 683 path: "streams/v1/index.json", 684 content: fmt.Sprintf(indexContent, metadata.Source, metadata.Region, metadata.Arch), 685 }, { 686 path: "streams/v1/products.json", 687 content: fmt.Sprintf(productContent, metadata.Arch, metadata.Arch, metadata.ImageId, metadata.ImageId, metadata.RootStorageType, metadata.VirtType, metadata.Region, metadata.Source), 688 }, { 689 path: "wayward/file.txt", 690 content: "ghi", 691 }} 692 writeTempFiles(c, metadataDir, expected) 693 return metadataDir, metadata 694 } 695 696 func assertWrittenToState(c *gc.C, metadata cloudimagemetadata.Metadata) { 697 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 698 Info: mongo.Info{ 699 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 700 CACert: testing.CACert, 701 }, 702 Password: testPasswordHash(), 703 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 704 c.Assert(err, jc.ErrorIsNil) 705 defer st.Close() 706 707 // find all image metadata in state 708 all, err := st.CloudImageMetadataStorage.FindMetadata(cloudimagemetadata.MetadataFilter{}) 709 c.Assert(err, jc.ErrorIsNil) 710 c.Assert(all, gc.DeepEquals, map[cloudimagemetadata.SourceType][]cloudimagemetadata.Metadata{ 711 metadata.Source: []cloudimagemetadata.Metadata{metadata}, 712 }) 713 } 714 715 func (s *BootstrapSuite) TestStructuredImageMetadataStored(c *gc.C) { 716 dir, m := createImageMetadata(c) 717 _, cmd, err := s.initBootstrapCommand( 718 c, nil, 719 "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), 720 "--image-metadata", dir, 721 ) 722 c.Assert(err, jc.ErrorIsNil) 723 err = cmd.Run(nil) 724 c.Assert(err, jc.ErrorIsNil) 725 726 // This metadata should have also been written to state... 727 assertWrittenToState(c, m) 728 } 729 730 func (s *BootstrapSuite) TestStructuredImageMetadataInvalidSeries(c *gc.C) { 731 dir, _ := createImageMetadata(c) 732 733 msg := "my test error" 734 s.PatchValue(&seriesFromVersion, func(string) (string, error) { 735 return "", errors.New(msg) 736 }) 737 738 _, cmd, err := s.initBootstrapCommand( 739 c, nil, 740 "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), 741 "--image-metadata", dir, 742 ) 743 c.Assert(err, jc.ErrorIsNil) 744 err = cmd.Run(nil) 745 c.Assert(err, gc.ErrorMatches, fmt.Sprintf(".*%v.*", msg)) 746 } 747 748 // TODO (anastasiamac 2015-09-26) This test will become obsolete when store 749 // functionality will be removed. 750 func (s *BootstrapSuite) TestImageMetadata(c *gc.C) { 751 metadataDir := c.MkDir() 752 expected := []struct{ path, content string }{{ 753 path: "images/streams/v1/index.json", 754 content: "abc", 755 }, { 756 path: "images/streams/v1/products.json", 757 content: "def", 758 }, { 759 path: "wayward/file.txt", 760 content: "ghi", 761 }} 762 for _, pair := range expected { 763 path := filepath.Join(metadataDir, pair.path) 764 err := os.MkdirAll(filepath.Dir(path), 0755) 765 c.Assert(err, jc.ErrorIsNil) 766 err = ioutil.WriteFile(path, []byte(pair.content), 0644) 767 c.Assert(err, jc.ErrorIsNil) 768 } 769 770 var stor statetesting.MapStorage 771 s.PatchValue(&newStateStorage, func(string, *mgo.Session) statestorage.Storage { 772 return &stor 773 }) 774 775 _, cmd, err := s.initBootstrapCommand( 776 c, nil, 777 "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId), 778 "--image-metadata", metadataDir, 779 ) 780 c.Assert(err, jc.ErrorIsNil) 781 err = cmd.Run(nil) 782 c.Assert(err, jc.ErrorIsNil) 783 784 // The contents of the directory should have been added to 785 // environment storage. 786 for _, pair := range expected { 787 r, length, err := stor.Get(pair.path) 788 c.Assert(err, jc.ErrorIsNil) 789 data, err := ioutil.ReadAll(r) 790 r.Close() 791 c.Assert(err, jc.ErrorIsNil) 792 c.Assert(length, gc.Equals, int64(len(pair.content))) 793 c.Assert(data, gc.HasLen, int(length)) 794 c.Assert(string(data), gc.Equals, pair.content) 795 } 796 } 797 798 func (s *BootstrapSuite) makeTestEnv(c *gc.C) { 799 attrs := dummy.SampleConfig().Merge( 800 testing.Attrs{ 801 "agent-version": version.Current.Number.String(), 802 "bootstrap-timeout": "123", 803 }, 804 ).Delete("admin-secret", "ca-private-key") 805 cfg, err := config.New(config.NoDefaults, attrs) 806 c.Assert(err, jc.ErrorIsNil) 807 provider, err := environs.Provider(cfg.Type()) 808 c.Assert(err, jc.ErrorIsNil) 809 env, err := provider.PrepareForBootstrap(nullContext(), cfg) 810 c.Assert(err, jc.ErrorIsNil) 811 812 envtesting.MustUploadFakeTools(s.toolsStorage, cfg.AgentStream(), cfg.AgentStream()) 813 inst, _, _, err := jujutesting.StartInstance(env, "0") 814 c.Assert(err, jc.ErrorIsNil) 815 s.instanceId = inst.Id() 816 817 addresses, err := inst.Addresses() 818 c.Assert(err, jc.ErrorIsNil) 819 addr, _ := network.SelectPublicAddress(addresses) 820 s.bootstrapName = addr.Value 821 s.envcfg = env.Config() 822 s.b64yamlEnvcfg = b64yaml(s.envcfg.AllAttrs()).encode() 823 } 824 825 func nullContext() environs.BootstrapContext { 826 ctx, _ := cmd.DefaultContext() 827 ctx.Stdin = io.LimitReader(nil, 0) 828 ctx.Stdout = ioutil.Discard 829 ctx.Stderr = ioutil.Discard 830 return envcmd.BootstrapContext(ctx) 831 } 832 833 type b64yaml map[string]interface{} 834 835 func (m b64yaml) encode() string { 836 data, err := goyaml.Marshal(m) 837 if err != nil { 838 panic(err) 839 } 840 return base64.StdEncoding.EncodeToString(data) 841 } 842 843 func (s *BootstrapSuite) TestDefaultStoragePools(c *gc.C) { 844 _, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId)) 845 c.Assert(err, jc.ErrorIsNil) 846 err = cmd.Run(nil) 847 c.Assert(err, jc.ErrorIsNil) 848 849 st, err := state.Open(testing.EnvironmentTag, &mongo.MongoInfo{ 850 Info: mongo.Info{ 851 Addrs: []string{gitjujutesting.MgoServer.Addr()}, 852 CACert: testing.CACert, 853 }, 854 Password: testPasswordHash(), 855 }, mongo.DefaultDialOpts(), environs.NewStatePolicy()) 856 c.Assert(err, jc.ErrorIsNil) 857 defer st.Close() 858 859 settings := state.NewStateSettings(st) 860 pm := poolmanager.New(settings) 861 for _, p := range []string{"ebs-ssd"} { 862 _, err = pm.Get(p) 863 c.Assert(err, jc.ErrorIsNil) 864 } 865 }