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