github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/juju/apiconn_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package juju_test 5 6 import ( 7 "fmt" 8 "os" 9 "time" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 gc "launchpad.net/gocheck" 14 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/bootstrap" 17 "github.com/juju/juju/environs/config" 18 "github.com/juju/juju/environs/configstore" 19 envtesting "github.com/juju/juju/environs/testing" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/juju" 22 "github.com/juju/juju/juju/osenv" 23 "github.com/juju/juju/provider/dummy" 24 "github.com/juju/juju/state/api" 25 coretesting "github.com/juju/juju/testing" 26 ) 27 28 type NewAPIConnSuite struct { 29 coretesting.FakeJujuHomeSuite 30 envtesting.ToolsFixture 31 } 32 33 var _ = gc.Suite(&NewAPIConnSuite{}) 34 35 func (cs *NewAPIConnSuite) SetUpTest(c *gc.C) { 36 cs.FakeJujuHomeSuite.SetUpTest(c) 37 cs.ToolsFixture.SetUpTest(c) 38 } 39 40 func (cs *NewAPIConnSuite) TearDownTest(c *gc.C) { 41 dummy.Reset() 42 cs.ToolsFixture.TearDownTest(c) 43 cs.FakeJujuHomeSuite.TearDownTest(c) 44 } 45 46 func (*NewAPIConnSuite) TestNewConn(c *gc.C) { 47 cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) 48 c.Assert(err, gc.IsNil) 49 ctx := coretesting.Context(c) 50 env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) 51 c.Assert(err, gc.IsNil) 52 53 envtesting.UploadFakeTools(c, env.Storage()) 54 err = bootstrap.Bootstrap(ctx, env, environs.BootstrapParams{}) 55 c.Assert(err, gc.IsNil) 56 57 cfg = env.Config() 58 cfg, err = cfg.Apply(map[string]interface{}{ 59 "secret": "fnord", 60 }) 61 c.Assert(err, gc.IsNil) 62 err = env.SetConfig(cfg) 63 c.Assert(err, gc.IsNil) 64 65 conn, err := juju.NewAPIConn(env, api.DefaultDialOpts()) 66 c.Assert(err, gc.IsNil) 67 c.Assert(conn.Environ, gc.Equals, env) 68 c.Assert(conn.State, gc.NotNil) 69 70 // the secrets will not be updated, as they already exist 71 attrs, err := conn.State.Client().EnvironmentGet() 72 c.Assert(attrs["secret"], gc.Equals, "pork") 73 74 c.Assert(conn.Close(), gc.IsNil) 75 } 76 77 type NewAPIClientSuite struct { 78 coretesting.FakeJujuHomeSuite 79 } 80 81 var _ = gc.Suite(&NewAPIClientSuite{}) 82 83 func (cs *NewAPIClientSuite) TearDownTest(c *gc.C) { 84 dummy.Reset() 85 cs.FakeJujuHomeSuite.TearDownTest(c) 86 } 87 88 func (s *NewAPIClientSuite) TestNameDefault(c *gc.C) { 89 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 90 // The connection logic should not delay the config connection 91 // at all when there is no environment info available. 92 // Make sure of that by providing a suitably long delay 93 // and checking that the connection happens within that 94 // time. 95 s.PatchValue(juju.ProviderConnectDelay, coretesting.LongWait) 96 bootstrapEnv(c, coretesting.SampleEnvName, defaultConfigStore(c)) 97 98 startTime := time.Now() 99 apiclient, err := juju.NewAPIClientFromName("") 100 c.Assert(err, gc.IsNil) 101 defer apiclient.Close() 102 c.Assert(time.Since(startTime), jc.LessThan, coretesting.LongWait) 103 104 // We should get the default sample environment if we ask for "" 105 assertEnvironmentName(c, apiclient, coretesting.SampleEnvName) 106 } 107 108 func (*NewAPIClientSuite) TestNameNotDefault(c *gc.C) { 109 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 110 envName := coretesting.SampleCertName + "-2" 111 bootstrapEnv(c, envName, defaultConfigStore(c)) 112 apiclient, err := juju.NewAPIClientFromName(envName) 113 c.Assert(err, gc.IsNil) 114 defer apiclient.Close() 115 assertEnvironmentName(c, apiclient, envName) 116 } 117 118 func (s *NewAPIClientSuite) TestWithInfoOnly(c *gc.C) { 119 store := newConfigStore("noconfig", dummyStoreInfo) 120 121 called := 0 122 expectState := &mockAPIState{ 123 apiHostPorts: [][]instance.HostPort{ 124 instance.AddressesWithPort([]instance.Address{instance.NewAddress("0.1.2.3", instance.NetworkUnknown)}, 1234), 125 }, 126 environTag: "environment-fake-uuid", 127 } 128 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 129 checkCommonAPIInfoAttrs(c, apiInfo, opts) 130 c.Check(apiInfo.EnvironTag, gc.Equals, "environment-fake-uuid") 131 called++ 132 return expectState, nil 133 } 134 135 // Give NewAPIFromStore a store interface that can report when the 136 // config was written to, to check if the cache is updated. 137 mockStore := &storageWithWriteNotify{store: store} 138 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 139 c.Assert(err, gc.IsNil) 140 c.Assert(st, gc.Equals, expectState) 141 c.Assert(called, gc.Equals, 1) 142 c.Assert(mockStore.written, jc.IsTrue) 143 info, err := store.ReadInfo("noconfig") 144 c.Assert(err, gc.IsNil) 145 ep := info.APIEndpoint() 146 c.Assert(ep.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"}) 147 c.Check(ep.EnvironUUID, gc.Equals, "fake-uuid") 148 mockStore.written = false 149 150 // If APIHostPorts haven't changed, then the store won't be updated. 151 st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 152 c.Assert(err, gc.IsNil) 153 c.Assert(st, gc.Equals, expectState) 154 c.Assert(called, gc.Equals, 2) 155 c.Assert(mockStore.written, jc.IsFalse) 156 } 157 158 func (s *NewAPIClientSuite) TestWithConfigAndNoInfo(c *gc.C) { 159 coretesting.MakeSampleJujuHome(c) 160 161 store := newConfigStore(coretesting.SampleEnvName, &environInfo{ 162 bootstrapConfig: map[string]interface{}{ 163 "type": "dummy", 164 "name": "myenv", 165 "state-server": true, 166 "authorized-keys": "i-am-a-key", 167 "default-series": config.LatestLtsSeries(), 168 "firewall-mode": config.FwInstance, 169 "development": false, 170 "ssl-hostname-verification": true, 171 "admin-secret": "adminpass", 172 }, 173 }) 174 bootstrapEnv(c, coretesting.SampleEnvName, store) 175 176 // Verify the cache is empty. 177 info, err := store.ReadInfo("myenv") 178 c.Assert(err, gc.IsNil) 179 c.Assert(info, gc.NotNil) 180 c.Assert(info.APIEndpoint(), jc.DeepEquals, configstore.APIEndpoint{}) 181 c.Assert(info.APICredentials(), jc.DeepEquals, configstore.APICredentials{}) 182 183 called := 0 184 expectState := &mockAPIState{} 185 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 186 c.Check(apiInfo.Tag, gc.Equals, "user-admin") 187 c.Check(string(apiInfo.CACert), gc.Not(gc.Equals), "") 188 c.Check(apiInfo.Password, gc.Equals, "adminpass") 189 // EnvironTag wasn't in regular Config 190 c.Check(apiInfo.EnvironTag, gc.Equals, "") 191 c.Check(opts, gc.DeepEquals, api.DefaultDialOpts()) 192 called++ 193 return expectState, nil 194 } 195 st, err := juju.NewAPIFromStore("myenv", store, apiOpen) 196 c.Assert(err, gc.IsNil) 197 c.Assert(st, gc.Equals, expectState) 198 c.Assert(called, gc.Equals, 1) 199 200 // Make sure the cache is updated. 201 info, err = store.ReadInfo("myenv") 202 c.Assert(err, gc.IsNil) 203 c.Assert(info, gc.NotNil) 204 ep := info.APIEndpoint() 205 c.Assert(ep.Addresses, gc.HasLen, 1) 206 c.Check(ep.Addresses[0], gc.Matches, `127\.0\.0\.1:\d+`) 207 c.Check(ep.CACert, gc.Not(gc.Equals), "") 208 // Old servers won't hand back EnvironTag, so it should stay empty in 209 // the cache 210 c.Check(ep.EnvironUUID, gc.Equals, "") 211 creds := info.APICredentials() 212 c.Check(creds.User, gc.Equals, "admin") 213 c.Check(creds.Password, gc.Equals, "adminpass") 214 } 215 216 func (s *NewAPIClientSuite) TestWithInfoError(c *gc.C) { 217 expectErr := fmt.Errorf("an error") 218 store := newConfigStoreWithError(expectErr) 219 client, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen) 220 c.Assert(err, gc.Equals, expectErr) 221 c.Assert(client, gc.IsNil) 222 } 223 224 func (s *NewAPIClientSuite) TestWithInfoNoAddresses(c *gc.C) { 225 store := newConfigStore("noconfig", &environInfo{ 226 endpoint: configstore.APIEndpoint{ 227 Addresses: []string{}, 228 CACert: "certificated", 229 }, 230 }) 231 st, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen) 232 c.Assert(err, gc.ErrorMatches, `environment "noconfig" not found`) 233 c.Assert(st, gc.IsNil) 234 } 235 236 var noTagStoreInfo = &environInfo{ 237 creds: configstore.APICredentials{ 238 User: "foo", 239 Password: "foopass", 240 }, 241 endpoint: configstore.APIEndpoint{ 242 Addresses: []string{"foo.invalid"}, 243 CACert: "certificated", 244 }, 245 } 246 247 func mockedAPIState(hasHostPort, hasEnvironTag bool) *mockAPIState { 248 apiHostPorts := [][]instance.HostPort{} 249 if hasHostPort { 250 address := instance.NewAddress("0.1.2.3", instance.NetworkUnknown) 251 apiHostPorts = [][]instance.HostPort{ 252 instance.AddressesWithPort([]instance.Address{address}, 1234), 253 } 254 } 255 environTag := "" 256 if hasEnvironTag { 257 environTag = "environment-fake-uuid" 258 } 259 return &mockAPIState{ 260 apiHostPorts: apiHostPorts, 261 environTag: environTag, 262 } 263 } 264 265 func checkCommonAPIInfoAttrs(c *gc.C, apiInfo *api.Info, opts api.DialOpts) { 266 c.Check(apiInfo.Tag, gc.Equals, "user-foo") 267 c.Check(string(apiInfo.CACert), gc.Equals, "certificated") 268 c.Check(apiInfo.Password, gc.Equals, "foopass") 269 c.Check(opts, gc.DeepEquals, api.DefaultDialOpts()) 270 } 271 272 func (s *NewAPIClientSuite) TestWithInfoNoEnvironTag(c *gc.C) { 273 store := newConfigStore("noconfig", noTagStoreInfo) 274 275 called := 0 276 expectState := mockedAPIState(true, true) 277 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 278 checkCommonAPIInfoAttrs(c, apiInfo, opts) 279 c.Check(apiInfo.EnvironTag, gc.Equals, "") 280 called++ 281 return expectState, nil 282 } 283 284 // Give NewAPIFromStore a store interface that can report when the 285 // config was written to, to check if the cache is updated. 286 mockStore := &storageWithWriteNotify{store: store} 287 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 288 c.Assert(err, gc.IsNil) 289 c.Assert(st, gc.Equals, expectState) 290 c.Assert(called, gc.Equals, 1) 291 c.Assert(mockStore.written, jc.IsTrue) 292 info, err := store.ReadInfo("noconfig") 293 c.Assert(err, gc.IsNil) 294 c.Assert(info.APIEndpoint().Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"}) 295 c.Check(info.APIEndpoint().EnvironUUID, gc.Equals, "fake-uuid") 296 } 297 298 func (s *NewAPIClientSuite) TestWithInfoNoAPIHostports(c *gc.C) { 299 // The local cache doesn't have an EnvironTag, which the API does 300 // return. However, the API doesn't have apiHostPorts, we don't want to 301 // override the local cache with bad endpoints. 302 store := newConfigStore("noconfig", noTagStoreInfo) 303 304 called := 0 305 expectState := mockedAPIState(false, true) 306 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 307 checkCommonAPIInfoAttrs(c, apiInfo, opts) 308 c.Check(apiInfo.EnvironTag, gc.Equals, "") 309 called++ 310 return expectState, nil 311 } 312 313 mockStore := &storageWithWriteNotify{store: store} 314 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 315 c.Assert(err, gc.IsNil) 316 c.Assert(st, gc.Equals, expectState) 317 c.Assert(called, gc.Equals, 1) 318 c.Assert(mockStore.written, jc.IsTrue) 319 info, err := store.ReadInfo("noconfig") 320 c.Assert(err, gc.IsNil) 321 ep := info.APIEndpoint() 322 // We should have cached the environ tag, but not disturbed the 323 // Addresses 324 c.Check(ep.Addresses, gc.HasLen, 1) 325 c.Check(ep.Addresses[0], gc.Matches, `foo\.invalid`) 326 c.Check(ep.EnvironUUID, gc.Equals, "fake-uuid") 327 } 328 329 func (s *NewAPIClientSuite) TestNoEnvironTagDoesntOverwriteCached(c *gc.C) { 330 store := newConfigStore("noconfig", dummyStoreInfo) 331 called := 0 332 // State returns a new set of APIHostPorts but not a new EnvironTag. We 333 // shouldn't override the cached value with environ tag of "". 334 expectState := mockedAPIState(true, false) 335 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 336 checkCommonAPIInfoAttrs(c, apiInfo, opts) 337 c.Check(apiInfo.EnvironTag, gc.Equals, "environment-fake-uuid") 338 called++ 339 return expectState, nil 340 } 341 342 mockStore := &storageWithWriteNotify{store: store} 343 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 344 c.Assert(err, gc.IsNil) 345 c.Assert(st, gc.Equals, expectState) 346 c.Assert(called, gc.Equals, 1) 347 c.Assert(mockStore.written, jc.IsTrue) 348 info, err := store.ReadInfo("noconfig") 349 c.Assert(err, gc.IsNil) 350 ep := info.APIEndpoint() 351 c.Assert(ep.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"}) 352 c.Check(ep.EnvironUUID, gc.Equals, "fake-uuid") 353 } 354 355 func (s *NewAPIClientSuite) TestWithInfoAPIOpenError(c *gc.C) { 356 store := newConfigStore("noconfig", &environInfo{ 357 endpoint: configstore.APIEndpoint{ 358 Addresses: []string{"foo.invalid"}, 359 }, 360 }) 361 362 expectErr := fmt.Errorf("an error") 363 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 364 return nil, expectErr 365 } 366 st, err := juju.NewAPIFromStore("noconfig", store, apiOpen) 367 c.Assert(err, gc.Equals, expectErr) 368 c.Assert(st, gc.IsNil) 369 } 370 371 func (s *NewAPIClientSuite) TestWithSlowInfoConnect(c *gc.C) { 372 coretesting.MakeSampleJujuHome(c) 373 store := configstore.NewMem() 374 bootstrapEnv(c, coretesting.SampleEnvName, store) 375 setEndpointAddress(c, store, coretesting.SampleEnvName, "infoapi.invalid") 376 377 infoOpenedState := &mockAPIState{} 378 infoEndpointOpened := make(chan struct{}) 379 cfgOpenedState := &mockAPIState{} 380 // On a sample run with no delay, the logic took 45ms to run, so 381 // we make the delay slightly more than that, so that if the 382 // logic doesn't delay at all, the test will fail reasonably consistently. 383 s.PatchValue(juju.ProviderConnectDelay, 50*time.Millisecond) 384 apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) { 385 if info.Addrs[0] == "infoapi.invalid" { 386 infoEndpointOpened <- struct{}{} 387 return infoOpenedState, nil 388 } 389 return cfgOpenedState, nil 390 } 391 392 stateClosed := make(chan juju.APIState) 393 infoOpenedState.close = func(st juju.APIState) error { 394 stateClosed <- st 395 return nil 396 } 397 cfgOpenedState.close = infoOpenedState.close 398 399 startTime := time.Now() 400 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 401 c.Assert(err, gc.IsNil) 402 // The connection logic should wait for some time before opening 403 // the API from the configuration. 404 c.Assert(time.Since(startTime), jc.GreaterThan, *juju.ProviderConnectDelay) 405 c.Assert(st, gc.Equals, cfgOpenedState) 406 407 select { 408 case <-infoEndpointOpened: 409 case <-time.After(coretesting.LongWait): 410 c.Errorf("api never opened via info") 411 } 412 413 // Check that the ignored state was closed. 414 select { 415 case st := <-stateClosed: 416 c.Assert(st, gc.Equals, infoOpenedState) 417 case <-time.After(coretesting.LongWait): 418 c.Errorf("timed out waiting for state to be closed") 419 } 420 } 421 422 type badBootstrapInfo struct { 423 configstore.EnvironInfo 424 } 425 426 // BootstrapConfig is returned as a map with real content, but the content 427 // isn't actually valid configuration, causing config.New to fail 428 func (m *badBootstrapInfo) BootstrapConfig() map[string]interface{} { 429 return map[string]interface{}{"something": "else"} 430 } 431 432 func (s *NewAPIClientSuite) TestBadConfigDoesntPanic(c *gc.C) { 433 badInfo := &badBootstrapInfo{} 434 cfg, err := juju.GetConfig(badInfo, nil, "test") 435 // The specific error we get depends on what key is invalid, which is a 436 // bit spurious, but what we care about is that we didn't get a panic, 437 // but instead got an error 438 c.Assert(err, gc.ErrorMatches, ".*expected.*got nothing") 439 c.Assert(cfg, gc.IsNil) 440 } 441 442 func setEndpointAddress(c *gc.C, store configstore.Storage, envName string, addr string) { 443 // Populate the environment's info with an endpoint 444 // with a known address. 445 info, err := store.ReadInfo(coretesting.SampleEnvName) 446 c.Assert(err, gc.IsNil) 447 info.SetAPIEndpoint(configstore.APIEndpoint{ 448 Addresses: []string{addr}, 449 CACert: "certificated", 450 }) 451 err = info.Write() 452 c.Assert(err, gc.IsNil) 453 } 454 455 func (s *NewAPIClientSuite) TestWithSlowConfigConnect(c *gc.C) { 456 coretesting.MakeSampleJujuHome(c) 457 458 store := configstore.NewMem() 459 bootstrapEnv(c, coretesting.SampleEnvName, store) 460 setEndpointAddress(c, store, coretesting.SampleEnvName, "infoapi.invalid") 461 462 infoOpenedState := &mockAPIState{} 463 infoEndpointOpened := make(chan struct{}) 464 cfgOpenedState := &mockAPIState{} 465 cfgEndpointOpened := make(chan struct{}) 466 467 s.PatchValue(juju.ProviderConnectDelay, 0*time.Second) 468 apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) { 469 if info.Addrs[0] == "infoapi.invalid" { 470 infoEndpointOpened <- struct{}{} 471 <-infoEndpointOpened 472 return infoOpenedState, nil 473 } 474 cfgEndpointOpened <- struct{}{} 475 <-cfgEndpointOpened 476 return cfgOpenedState, nil 477 } 478 479 stateClosed := make(chan juju.APIState) 480 infoOpenedState.close = func(st juju.APIState) error { 481 stateClosed <- st 482 return nil 483 } 484 cfgOpenedState.close = infoOpenedState.close 485 486 done := make(chan struct{}) 487 go func() { 488 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 489 c.Check(err, gc.IsNil) 490 c.Check(st, gc.Equals, infoOpenedState) 491 close(done) 492 }() 493 494 // Check that we're trying to connect to both endpoints: 495 select { 496 case <-infoEndpointOpened: 497 case <-time.After(coretesting.LongWait): 498 c.Fatalf("api never opened via info") 499 } 500 select { 501 case <-cfgEndpointOpened: 502 case <-time.After(coretesting.LongWait): 503 c.Fatalf("api never opened via config") 504 } 505 // Let the info endpoint open go ahead and 506 // check that the NewAPIFromStore call returns. 507 infoEndpointOpened <- struct{}{} 508 select { 509 case <-done: 510 case <-time.After(coretesting.LongWait): 511 c.Errorf("timed out opening API") 512 } 513 514 // Let the config endpoint open go ahead and 515 // check that its state is closed. 516 cfgEndpointOpened <- struct{}{} 517 select { 518 case st := <-stateClosed: 519 c.Assert(st, gc.Equals, cfgOpenedState) 520 case <-time.After(coretesting.LongWait): 521 c.Errorf("timed out waiting for state to be closed") 522 } 523 } 524 525 func (s *NewAPIClientSuite) TestBothError(c *gc.C) { 526 coretesting.MakeSampleJujuHome(c) 527 store := configstore.NewMem() 528 bootstrapEnv(c, coretesting.SampleEnvName, store) 529 setEndpointAddress(c, store, coretesting.SampleEnvName, "infoapi.invalid") 530 531 s.PatchValue(juju.ProviderConnectDelay, 0*time.Second) 532 apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) { 533 if info.Addrs[0] == "infoapi.invalid" { 534 return nil, fmt.Errorf("info connect failed") 535 } 536 return nil, fmt.Errorf("config connect failed") 537 } 538 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 539 c.Check(err, gc.ErrorMatches, "config connect failed") 540 c.Check(st, gc.IsNil) 541 } 542 543 func defaultConfigStore(c *gc.C) configstore.Storage { 544 store, err := configstore.Default() 545 c.Assert(err, gc.IsNil) 546 return store 547 } 548 549 // TODO(jam): 2013-08-27 This should move somewhere in api.* 550 func (s *NewAPIClientSuite) TestMultipleCloseOk(c *gc.C) { 551 coretesting.MakeSampleJujuHome(c) 552 bootstrapEnv(c, "", defaultConfigStore(c)) 553 client, _ := juju.NewAPIClientFromName("") 554 c.Assert(client.Close(), gc.IsNil) 555 c.Assert(client.Close(), gc.IsNil) 556 c.Assert(client.Close(), gc.IsNil) 557 } 558 559 func (s *NewAPIClientSuite) TestWithBootstrapConfigAndNoEnvironmentsFile(c *gc.C) { 560 coretesting.MakeSampleJujuHome(c) 561 store := configstore.NewMem() 562 bootstrapEnv(c, coretesting.SampleEnvName, store) 563 info, err := store.ReadInfo(coretesting.SampleEnvName) 564 c.Assert(err, gc.IsNil) 565 c.Assert(info.BootstrapConfig(), gc.NotNil) 566 c.Assert(info.APIEndpoint().Addresses, gc.HasLen, 0) 567 568 err = os.Remove(osenv.JujuHomePath("environments.yaml")) 569 c.Assert(err, gc.IsNil) 570 571 apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) { 572 return &mockAPIState{}, nil 573 } 574 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 575 c.Check(err, gc.IsNil) 576 st.Close() 577 } 578 579 func (*NewAPIClientSuite) TestWithBootstrapConfigTakesPrecedence(c *gc.C) { 580 // We want to make sure that the code is using the bootstrap 581 // config rather than information from environments.yaml, 582 // even when there is an entry in environments.yaml 583 // We can do that by changing the info bootstrap config 584 // so it has a different environment name. 585 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 586 587 store := configstore.NewMem() 588 bootstrapEnv(c, coretesting.SampleEnvName, store) 589 info, err := store.ReadInfo(coretesting.SampleEnvName) 590 c.Assert(err, gc.IsNil) 591 592 envName2 := coretesting.SampleCertName + "-2" 593 info2, err := store.CreateInfo(envName2) 594 c.Assert(err, gc.IsNil) 595 info2.SetBootstrapConfig(info.BootstrapConfig()) 596 err = info2.Write() 597 c.Assert(err, gc.IsNil) 598 599 // Now we have info for envName2 which will actually 600 // cause a connection to the originally bootstrapped 601 // state. 602 apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) { 603 return &mockAPIState{}, nil 604 } 605 st, err := juju.NewAPIFromStore(envName2, store, apiOpen) 606 c.Check(err, gc.IsNil) 607 st.Close() 608 609 // Sanity check that connecting to the envName2 610 // but with no info fails. 611 // Currently this panics with an "environment not prepared" error. 612 // Disable for now until an upcoming branch fixes it. 613 // err = info2.Destroy() 614 // c.Assert(err, gc.IsNil) 615 // st, err = juju.NewAPIFromStore(envName2, store) 616 // if err == nil { 617 // st.Close() 618 // } 619 // c.Assert(err, gc.ErrorMatches, "fooobie") 620 } 621 622 func assertEnvironmentName(c *gc.C, client *api.Client, expectName string) { 623 envInfo, err := client.EnvironmentInfo() 624 c.Assert(err, gc.IsNil) 625 c.Assert(envInfo.Name, gc.Equals, expectName) 626 } 627 628 // newConfigStoreWithError that will return the given 629 // error from ReadInfo. 630 func newConfigStoreWithError(err error) configstore.Storage { 631 return &errorConfigStorage{ 632 Storage: configstore.NewMem(), 633 err: err, 634 } 635 } 636 637 type errorConfigStorage struct { 638 configstore.Storage 639 err error 640 } 641 642 func (store *errorConfigStorage) ReadInfo(envName string) (configstore.EnvironInfo, error) { 643 return nil, store.err 644 } 645 646 type environInfo struct { 647 creds configstore.APICredentials 648 endpoint configstore.APIEndpoint 649 bootstrapConfig map[string]interface{} 650 } 651 652 // newConfigStore returns a storage that contains information 653 // for the environment name. 654 func newConfigStore(envName string, info *environInfo) configstore.Storage { 655 store := configstore.NewMem() 656 newInfo, err := store.CreateInfo(envName) 657 if err != nil { 658 panic(err) 659 } 660 newInfo.SetAPICredentials(info.creds) 661 newInfo.SetAPIEndpoint(info.endpoint) 662 newInfo.SetBootstrapConfig(info.bootstrapConfig) 663 err = newInfo.Write() 664 if err != nil { 665 panic(err) 666 } 667 return store 668 } 669 670 type storageWithWriteNotify struct { 671 written bool 672 store configstore.Storage 673 } 674 675 func (*storageWithWriteNotify) CreateInfo(envName string) (configstore.EnvironInfo, error) { 676 panic("CreateInfo not implemented") 677 } 678 679 func (s *storageWithWriteNotify) ReadInfo(envName string) (configstore.EnvironInfo, error) { 680 info, err := s.store.ReadInfo(envName) 681 if err != nil { 682 return nil, err 683 } 684 return &infoWithWriteNotify{ 685 written: &s.written, 686 EnvironInfo: info, 687 }, nil 688 } 689 690 type infoWithWriteNotify struct { 691 configstore.EnvironInfo 692 written *bool 693 } 694 695 func (info *infoWithWriteNotify) Write() error { 696 *info.written = true 697 return info.EnvironInfo.Write() 698 } 699 700 type APIEndpointForEnvSuite struct { 701 coretesting.FakeJujuHomeSuite 702 } 703 704 var _ = gc.Suite(&APIEndpointForEnvSuite{}) 705 706 var dummyStoreInfo = &environInfo{ 707 creds: configstore.APICredentials{ 708 User: "foo", 709 Password: "foopass", 710 }, 711 endpoint: configstore.APIEndpoint{ 712 Addresses: []string{"foo.invalid"}, 713 CACert: "certificated", 714 EnvironUUID: "fake-uuid", 715 }, 716 } 717 718 func (s *APIEndpointForEnvSuite) TestAPIEndpointInStoreCached(c *gc.C) { 719 store := newConfigStore("env-name", dummyStoreInfo) 720 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 721 return nil, nil 722 } 723 endpoint, err := juju.APIEndpointInStore("env-name", false, store, apiOpen) 724 c.Assert(err, gc.IsNil) 725 c.Check(endpoint, gc.DeepEquals, dummyStoreInfo.endpoint) 726 } 727 728 func (s *APIEndpointForEnvSuite) TestAPIEndpointForEnvSuchName(c *gc.C) { 729 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 730 _, err := juju.APIEndpointForEnv("no-such-env", false) 731 c.Check(err, jc.Satisfies, errors.IsNotFound) 732 c.Check(err, gc.ErrorMatches, `environment "no-such-env" not found`) 733 } 734 735 func (s *APIEndpointForEnvSuite) TestAPIEndpointNotCached(c *gc.C) { 736 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 737 store, err := configstore.Default() 738 c.Assert(err, gc.IsNil) 739 ctx := coretesting.Context(c) 740 env, err := environs.PrepareFromName("erewhemos", ctx, store) 741 c.Assert(err, gc.IsNil) 742 defer dummy.Reset() 743 envtesting.UploadFakeTools(c, env.Storage()) 744 err = bootstrap.Bootstrap(ctx, env, environs.BootstrapParams{}) 745 c.Assert(err, gc.IsNil) 746 747 // Note: if we get Bootstrap to start caching the API endpoint 748 // immediately, we'll still want to have this test for compatibility. 749 // We can just write blank info instead of reading and checking it is empty. 750 savedInfo, err := store.ReadInfo("erewhemos") 751 c.Assert(err, gc.IsNil) 752 // Ensure that the data isn't cached 753 c.Check(savedInfo.APIEndpoint().Addresses, gc.HasLen, 0) 754 755 called := 0 756 expectState := mockedAPIState(true, true) 757 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 758 c.Check(apiInfo.Tag, gc.Equals, "user-admin") 759 c.Check(string(apiInfo.CACert), gc.Equals, coretesting.CACert) 760 c.Check(apiInfo.Password, gc.Equals, coretesting.DefaultMongoPassword) 761 c.Check(opts, gc.DeepEquals, api.DefaultDialOpts()) 762 // we didn't know about it when connecting 763 c.Check(apiInfo.EnvironTag, gc.Equals, "") 764 called++ 765 return expectState, nil 766 } 767 endpoint, err := juju.APIEndpointInStore("erewhemos", false, store, apiOpen) 768 c.Assert(err, gc.IsNil) 769 c.Assert(called, gc.Equals, 1) 770 c.Check(endpoint.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"}) 771 c.Check(endpoint.EnvironUUID, gc.Equals, "fake-uuid") 772 } 773 774 func (s *APIEndpointForEnvSuite) TestAPIEndpointRefresh(c *gc.C) { 775 store := newConfigStore("env-name", dummyStoreInfo) 776 called := 0 777 expectState := mockedAPIState(true, false) 778 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 779 checkCommonAPIInfoAttrs(c, apiInfo, opts) 780 called++ 781 return expectState, nil 782 } 783 endpoint, err := juju.APIEndpointInStore("env-name", false, store, apiOpen) 784 c.Assert(err, gc.IsNil) 785 c.Assert(called, gc.Equals, 0) 786 c.Check(endpoint.Addresses, gc.DeepEquals, []string{"foo.invalid"}) 787 // However, if we ask to refresh them, we'll connect to the API and get 788 // the freshest set 789 endpoint, err = juju.APIEndpointInStore("env-name", true, store, apiOpen) 790 c.Assert(err, gc.IsNil) 791 c.Check(called, gc.Equals, 1) 792 // This refresh now gives us the values return by APIHostPorts 793 c.Check(endpoint.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"}) 794 } 795 796 func (s *APIEndpointForEnvSuite) TestAPIEndpointNotMachineLocal(c *gc.C) { 797 store := newConfigStore("env-name", dummyStoreInfo) 798 called := 0 799 hostPorts := [][]instance.HostPort{ 800 instance.AddressesWithPort([]instance.Address{ 801 instance.NewAddress("1.0.0.1", instance.NetworkPublic), 802 instance.NewAddress("192.0.0.1", instance.NetworkCloudLocal), 803 instance.NewAddress("127.0.0.1", instance.NetworkMachineLocal), 804 instance.NewAddress("localhost", instance.NetworkMachineLocal), 805 }, 1234), 806 instance.AddressesWithPort([]instance.Address{ 807 instance.NewAddress("1.0.0.2", instance.NetworkUnknown), 808 instance.NewAddress("2002:0:0:0:0:0:100:2", instance.NetworkUnknown), 809 instance.NewAddress("::1", instance.NetworkMachineLocal), 810 instance.NewAddress("127.0.0.1", instance.NetworkMachineLocal), 811 instance.NewAddress("localhost", instance.NetworkMachineLocal), 812 }, 1235), 813 } 814 815 expectState := &mockAPIState{apiHostPorts: hostPorts} 816 apiOpen := func(_ *api.Info, _ api.DialOpts) (juju.APIState, error) { 817 called++ 818 return expectState, nil 819 } 820 endpoint, err := juju.APIEndpointInStore("env-name", true, store, apiOpen) 821 c.Assert(err, gc.IsNil) 822 c.Check(called, gc.Equals, 1) 823 c.Check(endpoint.Addresses, gc.DeepEquals, []string{ 824 "1.0.0.1:1234", 825 "192.0.0.1:1234", 826 "1.0.0.2:1235", 827 }) 828 }