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