github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/agent/agentbootstrap/bootstrap_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package agentbootstrap_test 5 6 import ( 7 "io/ioutil" 8 "net" 9 "path/filepath" 10 11 "github.com/juju/os/series" 12 gitjujutesting "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 18 "github.com/juju/juju/agent" 19 "github.com/juju/juju/agent/agentbootstrap" 20 "github.com/juju/juju/apiserver/params" 21 "github.com/juju/juju/cloud" 22 "github.com/juju/juju/cloudconfig/instancecfg" 23 "github.com/juju/juju/controller" 24 "github.com/juju/juju/core/constraints" 25 "github.com/juju/juju/core/instance" 26 "github.com/juju/juju/environs" 27 "github.com/juju/juju/environs/config" 28 "github.com/juju/juju/environs/context" 29 "github.com/juju/juju/mongo" 30 "github.com/juju/juju/mongo/mongotest" 31 "github.com/juju/juju/network" 32 "github.com/juju/juju/provider/dummy" 33 "github.com/juju/juju/state" 34 "github.com/juju/juju/state/multiwatcher" 35 "github.com/juju/juju/storage/provider" 36 "github.com/juju/juju/testing" 37 jujuversion "github.com/juju/juju/version" 38 ) 39 40 type bootstrapSuite struct { 41 testing.BaseSuite 42 mgoInst gitjujutesting.MgoInstance 43 } 44 45 var _ = gc.Suite(&bootstrapSuite{}) 46 47 func (s *bootstrapSuite) SetUpTest(c *gc.C) { 48 s.BaseSuite.SetUpTest(c) 49 // Don't use MgoSuite, because we need to ensure 50 // we have a fresh mongo for each test case. 51 s.mgoInst.EnableAuth = true 52 err := s.mgoInst.Start(testing.Certs) 53 c.Assert(err, jc.ErrorIsNil) 54 } 55 56 func (s *bootstrapSuite) TearDownTest(c *gc.C) { 57 s.mgoInst.Destroy() 58 s.BaseSuite.TearDownTest(c) 59 } 60 61 func (s *bootstrapSuite) TestInitializeState(c *gc.C) { 62 dataDir := c.MkDir() 63 64 lxcFakeNetConfig := filepath.Join(c.MkDir(), "lxc-net") 65 netConf := []byte(` 66 # comments ignored 67 LXC_BR= ignored 68 LXC_ADDR = "fooo" 69 LXC_BRIDGE="foobar" # detected 70 anything else ignored 71 LXC_BRIDGE="ignored"`[1:]) 72 err := ioutil.WriteFile(lxcFakeNetConfig, netConf, 0644) 73 c.Assert(err, jc.ErrorIsNil) 74 c.Assert(err, jc.ErrorIsNil) 75 s.PatchValue(&network.InterfaceByNameAddrs, func(name string) ([]net.Addr, error) { 76 if name == "foobar" { 77 // The addresses on the LXC bridge 78 return []net.Addr{ 79 &net.IPAddr{IP: net.IPv4(10, 0, 3, 1)}, 80 &net.IPAddr{IP: net.IPv4(10, 0, 3, 4)}, 81 }, nil 82 } else if name == network.DefaultLXDBridge { 83 // The addresses on the LXD bridge 84 return []net.Addr{ 85 &net.IPAddr{IP: net.IPv4(10, 0, 4, 1)}, 86 &net.IPAddr{IP: net.IPv4(10, 0, 4, 4)}, 87 }, nil 88 } else if name == network.DefaultKVMBridge { 89 // claim we don't have a virbr0 bridge 90 return nil, nil 91 } 92 c.Fatalf("unknown bridge in testing: %v", name) 93 return nil, nil 94 }) 95 s.PatchValue(&network.LXCNetDefaultConfig, lxcFakeNetConfig) 96 97 configParams := agent.AgentConfigParams{ 98 Paths: agent.Paths{DataDir: dataDir}, 99 Tag: names.NewMachineTag("0"), 100 UpgradedToVersion: jujuversion.Current, 101 APIAddresses: []string{"localhost:17070"}, 102 CACert: testing.CACert, 103 Password: testing.DefaultMongoPassword, 104 Controller: testing.ControllerTag, 105 Model: testing.ModelTag, 106 } 107 servingInfo := params.StateServingInfo{ 108 Cert: testing.ServerCert, 109 PrivateKey: testing.ServerKey, 110 CAPrivateKey: testing.CAKey, 111 APIPort: 1234, 112 StatePort: s.mgoInst.Port(), 113 SystemIdentity: "def456", 114 } 115 116 cfg, err := agent.NewStateMachineConfig(configParams, servingInfo) 117 c.Assert(err, jc.ErrorIsNil) 118 119 _, available := cfg.StateServingInfo() 120 c.Assert(available, jc.IsTrue) 121 expectBootstrapConstraints := constraints.MustParse("mem=1024M") 122 expectModelConstraints := constraints.MustParse("mem=512M") 123 expectHW := instance.MustParseHardware("mem=2048M") 124 initialAddrs := network.NewAddresses( 125 "zeroonetwothree", 126 "0.1.2.3", 127 "10.0.3.1", // lxc bridge address filtered. 128 "10.0.3.4", // lxc bridge address filtered (-"-). 129 "10.0.3.3", // not a lxc bridge address 130 "10.0.4.1", // lxd bridge address filtered. 131 "10.0.4.4", // lxd bridge address filtered. 132 "10.0.4.5", // not an lxd bridge address 133 ) 134 filteredAddrs := network.NewAddresses( 135 "zeroonetwothree", 136 "0.1.2.3", 137 "10.0.3.3", 138 "10.0.4.5", 139 ) 140 141 modelAttrs := testing.FakeConfig().Merge(testing.Attrs{ 142 "agent-version": jujuversion.Current.String(), 143 "not-for-hosted": "foo", 144 }) 145 modelCfg, err := config.New(config.NoDefaults, modelAttrs) 146 c.Assert(err, jc.ErrorIsNil) 147 controllerCfg := testing.FakeControllerConfig() 148 149 hostedModelUUID := utils.MustNewUUID().String() 150 hostedModelConfigAttrs := map[string]interface{}{ 151 "name": "hosted", 152 "uuid": hostedModelUUID, 153 } 154 controllerInheritedConfig := map[string]interface{}{ 155 "apt-mirror": "http://mirror", 156 "no-proxy": "value", 157 } 158 regionConfig := cloud.RegionConfig{ 159 "some-region": cloud.Attrs{ 160 "no-proxy": "a-value", 161 }, 162 } 163 var envProvider fakeProvider 164 args := agentbootstrap.InitializeStateParams{ 165 StateInitializationParams: instancecfg.StateInitializationParams{ 166 BootstrapMachineConstraints: expectBootstrapConstraints, 167 BootstrapMachineInstanceId: "i-bootstrap", 168 BootstrapMachineHardwareCharacteristics: &expectHW, 169 ControllerCloud: cloud.Cloud{ 170 Name: "dummy", 171 Type: "dummy", 172 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 173 Regions: []cloud.Region{{Name: "dummy-region"}}, 174 RegionConfig: regionConfig, 175 }, 176 ControllerCloudRegion: "dummy-region", 177 ControllerConfig: controllerCfg, 178 ControllerModelConfig: modelCfg, 179 ControllerModelEnvironVersion: 666, 180 ModelConstraints: expectModelConstraints, 181 ControllerInheritedConfig: controllerInheritedConfig, 182 HostedModelConfig: hostedModelConfigAttrs, 183 }, 184 BootstrapMachineAddresses: initialAddrs, 185 BootstrapMachineJobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, 186 SharedSecret: "abc123", 187 Provider: func(t string) (environs.EnvironProvider, error) { 188 c.Assert(t, gc.Equals, "dummy") 189 return &envProvider, nil 190 }, 191 StorageProviderRegistry: provider.CommonStorageProviders(), 192 } 193 194 adminUser := names.NewLocalUserTag("agent-admin") 195 ctlr, m, err := agentbootstrap.InitializeState( 196 adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil), 197 ) 198 c.Assert(err, jc.ErrorIsNil) 199 defer ctlr.Close() 200 st := ctlr.SystemState() 201 err = cfg.Write() 202 c.Assert(err, jc.ErrorIsNil) 203 204 // Check that the model has been set up. 205 model, err := st.Model() 206 c.Assert(err, jc.ErrorIsNil) 207 c.Assert(model.UUID(), gc.Equals, modelCfg.UUID()) 208 c.Assert(model.EnvironVersion(), gc.Equals, 666) 209 210 // Check that initial admin user has been set up correctly. 211 modelTag := model.Tag().(names.ModelTag) 212 controllerTag := names.NewControllerTag(controllerCfg.ControllerUUID()) 213 s.assertCanLogInAsAdmin(c, modelTag, controllerTag, testing.DefaultMongoPassword) 214 user, err := st.User(model.Owner()) 215 c.Assert(err, jc.ErrorIsNil) 216 c.Assert(user.PasswordValid(testing.DefaultMongoPassword), jc.IsTrue) 217 218 // Check controller config 219 controllerCfg, err = st.ControllerConfig() 220 c.Assert(err, jc.ErrorIsNil) 221 c.Assert(controllerCfg, jc.DeepEquals, controller.Config{ 222 "controller-uuid": testing.ControllerTag.Id(), 223 "ca-cert": testing.CACert, 224 "state-port": 1234, 225 "api-port": 17777, 226 "set-numa-control-policy": false, 227 "max-logs-age": "72h", 228 "max-logs-size": "4G", 229 "max-txn-log-size": "10M", 230 "auditing-enabled": false, 231 "audit-log-capture-args": true, 232 "audit-log-max-size": "200M", 233 "audit-log-max-backups": 5, 234 }) 235 236 // Check that controller model configuration has been added, and 237 // model constraints set. 238 model, err = st.Model() 239 c.Assert(err, jc.ErrorIsNil) 240 241 newModelCfg, err := model.ModelConfig() 242 c.Assert(err, jc.ErrorIsNil) 243 244 // Add in the cloud attributes. 245 expectedCfg, err := config.New(config.UseDefaults, modelAttrs) 246 c.Assert(err, jc.ErrorIsNil) 247 expectedAttrs := expectedCfg.AllAttrs() 248 expectedAttrs["apt-mirror"] = "http://mirror" 249 expectedAttrs["no-proxy"] = "value" 250 c.Assert(newModelCfg.AllAttrs(), jc.DeepEquals, expectedAttrs) 251 252 gotModelConstraints, err := st.ModelConstraints() 253 c.Assert(err, jc.ErrorIsNil) 254 c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints) 255 256 // Check that the hosted model has been added, model constraints 257 // set, and its config contains the same authorized-keys as the 258 // controller model. 259 hostedModelSt, err := ctlr.StatePool().Get(hostedModelUUID) 260 c.Assert(err, jc.ErrorIsNil) 261 defer hostedModelSt.Release() 262 gotModelConstraints, err = hostedModelSt.ModelConstraints() 263 c.Assert(err, jc.ErrorIsNil) 264 c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints) 265 hostedModel, err := hostedModelSt.Model() 266 c.Assert(err, jc.ErrorIsNil) 267 c.Assert(hostedModel.Name(), gc.Equals, "hosted") 268 c.Assert(hostedModel.CloudRegion(), gc.Equals, "dummy-region") 269 c.Assert(hostedModel.EnvironVersion(), gc.Equals, 123) 270 hostedCfg, err := hostedModel.ModelConfig() 271 c.Assert(err, jc.ErrorIsNil) 272 _, hasUnexpected := hostedCfg.AllAttrs()["not-for-hosted"] 273 c.Assert(hasUnexpected, jc.IsFalse) 274 c.Assert(hostedCfg.AuthorizedKeys(), gc.Equals, newModelCfg.AuthorizedKeys()) 275 276 // Check that the bootstrap machine looks correct. 277 c.Assert(m.Id(), gc.Equals, "0") 278 c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel}) 279 c.Assert(m.Series(), gc.Equals, series.MustHostSeries()) 280 c.Assert(m.CheckProvisioned(agent.BootstrapNonce), jc.IsTrue) 281 c.Assert(m.Addresses(), jc.DeepEquals, filteredAddrs) 282 gotBootstrapConstraints, err := m.Constraints() 283 c.Assert(err, jc.ErrorIsNil) 284 c.Assert(gotBootstrapConstraints, gc.DeepEquals, expectBootstrapConstraints) 285 c.Assert(err, jc.ErrorIsNil) 286 gotHW, err := m.HardwareCharacteristics() 287 c.Assert(err, jc.ErrorIsNil) 288 c.Assert(*gotHW, gc.DeepEquals, expectHW) 289 290 // Check that the API host ports are initialised correctly. 291 apiHostPorts, err := st.APIHostPortsForClients() 292 c.Assert(err, jc.ErrorIsNil) 293 c.Assert(apiHostPorts, jc.DeepEquals, [][]network.HostPort{ 294 network.AddressesWithPort(filteredAddrs, 1234), 295 }) 296 297 // Check that the state serving info is initialised correctly. 298 stateServingInfo, err := st.StateServingInfo() 299 c.Assert(err, jc.ErrorIsNil) 300 c.Assert(stateServingInfo, jc.DeepEquals, state.StateServingInfo{ 301 APIPort: 1234, 302 StatePort: s.mgoInst.Port(), 303 Cert: testing.ServerCert, 304 PrivateKey: testing.ServerKey, 305 CAPrivateKey: testing.CAKey, 306 SharedSecret: "abc123", 307 SystemIdentity: "def456", 308 }) 309 310 // Check that the machine agent's config has been written 311 // and that we can use it to connect to mongo. 312 machine0 := names.NewMachineTag("0") 313 newCfg, err := agent.ReadConfig(agent.ConfigPath(dataDir, machine0)) 314 c.Assert(err, jc.ErrorIsNil) 315 c.Assert(newCfg.Tag(), gc.Equals, machine0) 316 info, ok := cfg.MongoInfo() 317 c.Assert(ok, jc.IsTrue) 318 c.Assert(info.Password, gc.Not(gc.Equals), testing.DefaultMongoPassword) 319 320 session, err := mongo.DialWithInfo(*info, mongotest.DialOpts()) 321 c.Assert(err, jc.ErrorIsNil) 322 session.Close() 323 324 // Make sure that the hosted model Environ's Create method is called. 325 envProvider.CheckCallNames(c, 326 "PrepareConfig", 327 "Validate", 328 "Open", 329 "Create", 330 "Provider", 331 "Version", 332 ) 333 // Those attributes are configured during initialization, after "Open". 334 expectedCalledCfg, err := hostedCfg.Apply(map[string]interface{}{"container-networking-method": ""}) 335 c.Assert(err, jc.ErrorIsNil) 336 envProvider.CheckCall(c, 2, "Open", environs.OpenParams{ 337 Cloud: environs.CloudSpec{ 338 Type: "dummy", 339 Name: "dummy", 340 Region: "dummy-region", 341 }, 342 Config: expectedCalledCfg, 343 }) 344 envProvider.CheckCall(c, 3, "Create", 345 envProvider.environ.callCtxUsed, 346 environs.CreateParams{ 347 ControllerUUID: controllerCfg.ControllerUUID(), 348 }) 349 } 350 351 func (s *bootstrapSuite) TestInitializeStateWithStateServingInfoNotAvailable(c *gc.C) { 352 configParams := agent.AgentConfigParams{ 353 Paths: agent.Paths{DataDir: c.MkDir()}, 354 Tag: names.NewMachineTag("0"), 355 UpgradedToVersion: jujuversion.Current, 356 APIAddresses: []string{"localhost:17070"}, 357 CACert: testing.CACert, 358 Password: "fake", 359 Controller: testing.ControllerTag, 360 Model: testing.ModelTag, 361 } 362 cfg, err := agent.NewAgentConfig(configParams) 363 c.Assert(err, jc.ErrorIsNil) 364 365 _, available := cfg.StateServingInfo() 366 c.Assert(available, jc.IsFalse) 367 368 args := agentbootstrap.InitializeStateParams{} 369 370 adminUser := names.NewLocalUserTag("agent-admin") 371 _, _, err = agentbootstrap.InitializeState(adminUser, cfg, args, mongotest.DialOpts(), nil) 372 // InitializeState will fail attempting to get the api port information 373 c.Assert(err, gc.ErrorMatches, "state serving information not available") 374 } 375 376 func (s *bootstrapSuite) TestInitializeStateFailsSecondTime(c *gc.C) { 377 dataDir := c.MkDir() 378 379 configParams := agent.AgentConfigParams{ 380 Paths: agent.Paths{DataDir: dataDir}, 381 Tag: names.NewMachineTag("0"), 382 UpgradedToVersion: jujuversion.Current, 383 APIAddresses: []string{"localhost:17070"}, 384 CACert: testing.CACert, 385 Password: testing.DefaultMongoPassword, 386 Controller: testing.ControllerTag, 387 Model: testing.ModelTag, 388 } 389 cfg, err := agent.NewAgentConfig(configParams) 390 c.Assert(err, jc.ErrorIsNil) 391 cfg.SetStateServingInfo(params.StateServingInfo{ 392 APIPort: 5555, 393 StatePort: s.mgoInst.Port(), 394 Cert: "foo", 395 PrivateKey: "bar", 396 SharedSecret: "baz", 397 SystemIdentity: "qux", 398 }) 399 modelAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{ 400 "agent-version": jujuversion.Current.String(), 401 }) 402 modelCfg, err := config.New(config.NoDefaults, modelAttrs) 403 c.Assert(err, jc.ErrorIsNil) 404 405 hostedModelConfigAttrs := map[string]interface{}{ 406 "name": "hosted", 407 "uuid": utils.MustNewUUID().String(), 408 } 409 410 args := agentbootstrap.InitializeStateParams{ 411 StateInitializationParams: instancecfg.StateInitializationParams{ 412 BootstrapMachineInstanceId: "i-bootstrap", 413 ControllerCloud: cloud.Cloud{ 414 Name: "dummy", 415 Type: "dummy", 416 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 417 }, 418 ControllerConfig: testing.FakeControllerConfig(), 419 ControllerModelConfig: modelCfg, 420 HostedModelConfig: hostedModelConfigAttrs, 421 }, 422 BootstrapMachineJobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, 423 SharedSecret: "abc123", 424 Provider: func(t string) (environs.EnvironProvider, error) { 425 return &fakeProvider{}, nil 426 }, 427 StorageProviderRegistry: provider.CommonStorageProviders(), 428 } 429 430 adminUser := names.NewLocalUserTag("agent-admin") 431 st, _, err := agentbootstrap.InitializeState( 432 adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil), 433 ) 434 c.Assert(err, jc.ErrorIsNil) 435 st.Close() 436 437 st, _, err = agentbootstrap.InitializeState( 438 adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil), 439 ) 440 if err == nil { 441 st.Close() 442 } 443 c.Assert(err, gc.ErrorMatches, "bootstrapping raft cluster: bootstrap only works on new clusters") 444 } 445 446 func (s *bootstrapSuite) TestMachineJobFromParams(c *gc.C) { 447 var tests = []struct { 448 name multiwatcher.MachineJob 449 want state.MachineJob 450 err string 451 }{{ 452 name: multiwatcher.JobHostUnits, 453 want: state.JobHostUnits, 454 }, { 455 name: multiwatcher.JobManageModel, 456 want: state.JobManageModel, 457 }, { 458 name: "invalid", 459 want: -1, 460 err: `invalid machine job "invalid"`, 461 }} 462 for _, test := range tests { 463 got, err := agentbootstrap.MachineJobFromParams(test.name) 464 if err != nil { 465 c.Check(err, gc.ErrorMatches, test.err) 466 } 467 c.Check(got, gc.Equals, test.want) 468 } 469 } 470 471 func (s *bootstrapSuite) assertCanLogInAsAdmin(c *gc.C, modelTag names.ModelTag, controllerTag names.ControllerTag, password string) { 472 session, err := mongo.DialWithInfo(mongo.MongoInfo{ 473 Info: mongo.Info{ 474 Addrs: []string{s.mgoInst.Addr()}, 475 CACert: testing.CACert, 476 }, 477 Tag: nil, // admin user 478 Password: password, 479 }, mongotest.DialOpts()) 480 c.Assert(err, jc.ErrorIsNil) 481 session.Close() 482 } 483 484 type fakeProvider struct { 485 environs.EnvironProvider 486 gitjujutesting.Stub 487 environ *fakeEnviron 488 } 489 490 func (p *fakeProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 491 p.MethodCall(p, "PrepareConfig", args) 492 return args.Config, p.NextErr() 493 } 494 495 func (p *fakeProvider) Validate(newCfg, oldCfg *config.Config) (*config.Config, error) { 496 p.MethodCall(p, "Validate", newCfg, oldCfg) 497 return newCfg, p.NextErr() 498 } 499 500 func (p *fakeProvider) Open(args environs.OpenParams) (environs.Environ, error) { 501 p.MethodCall(p, "Open", args) 502 p.environ = &fakeEnviron{Stub: &p.Stub, provider: p} 503 return p.environ, p.NextErr() 504 } 505 506 func (p *fakeProvider) Version() int { 507 p.MethodCall(p, "Version") 508 p.PopNoErr() 509 return 123 510 } 511 512 type fakeEnviron struct { 513 environs.Environ 514 *gitjujutesting.Stub 515 provider *fakeProvider 516 517 callCtxUsed context.ProviderCallContext 518 } 519 520 func (e *fakeEnviron) Create(ctx context.ProviderCallContext, args environs.CreateParams) error { 521 e.MethodCall(e, "Create", ctx, args) 522 e.callCtxUsed = ctx 523 return e.NextErr() 524 } 525 526 func (e *fakeEnviron) Provider() environs.EnvironProvider { 527 e.MethodCall(e, "Provider") 528 e.PopNoErr() 529 return e.provider 530 }