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