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