github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/juju/api_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 "net" 9 "os" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/names" 16 "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/api" 21 "github.com/juju/juju/environs" 22 "github.com/juju/juju/environs/bootstrap" 23 "github.com/juju/juju/environs/config" 24 "github.com/juju/juju/environs/configstore" 25 "github.com/juju/juju/environs/filestorage" 26 envtesting "github.com/juju/juju/environs/testing" 27 envtools "github.com/juju/juju/environs/tools" 28 "github.com/juju/juju/juju" 29 "github.com/juju/juju/juju/osenv" 30 jujutesting "github.com/juju/juju/juju/testing" 31 "github.com/juju/juju/network" 32 "github.com/juju/juju/provider/dummy" 33 coretesting "github.com/juju/juju/testing" 34 ) 35 36 type NewAPIStateSuite struct { 37 coretesting.FakeJujuHomeSuite 38 testing.MgoSuite 39 envtesting.ToolsFixture 40 } 41 42 var _ = gc.Suite(&NewAPIStateSuite{}) 43 44 func (cs *NewAPIStateSuite) SetUpSuite(c *gc.C) { 45 cs.FakeJujuHomeSuite.SetUpSuite(c) 46 cs.MgoSuite.SetUpSuite(c) 47 } 48 49 func (cs *NewAPIStateSuite) TearDownSuite(c *gc.C) { 50 cs.MgoSuite.TearDownSuite(c) 51 cs.FakeJujuHomeSuite.TearDownSuite(c) 52 } 53 54 func (cs *NewAPIStateSuite) SetUpTest(c *gc.C) { 55 cs.FakeJujuHomeSuite.SetUpTest(c) 56 cs.MgoSuite.SetUpTest(c) 57 cs.ToolsFixture.SetUpTest(c) 58 } 59 60 func (cs *NewAPIStateSuite) TearDownTest(c *gc.C) { 61 dummy.Reset() 62 cs.ToolsFixture.TearDownTest(c) 63 cs.MgoSuite.TearDownTest(c) 64 cs.FakeJujuHomeSuite.TearDownTest(c) 65 } 66 67 func (cs *NewAPIStateSuite) TestNewAPIState(c *gc.C) { 68 cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) 69 c.Assert(err, jc.ErrorIsNil) 70 ctx := envtesting.BootstrapContext(c) 71 env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) 72 c.Assert(err, jc.ErrorIsNil) 73 74 storageDir := c.MkDir() 75 cs.PatchValue(&envtools.DefaultBaseURL, storageDir) 76 stor, err := filestorage.NewFileStorageWriter(storageDir) 77 c.Assert(err, jc.ErrorIsNil) 78 envtesting.UploadFakeTools(c, stor, "released", "released") 79 80 err = bootstrap.Bootstrap(ctx, env, bootstrap.BootstrapParams{}) 81 c.Assert(err, jc.ErrorIsNil) 82 83 cfg = env.Config() 84 cfg, err = cfg.Apply(map[string]interface{}{ 85 "secret": "fnord", 86 }) 87 c.Assert(err, jc.ErrorIsNil) 88 err = env.SetConfig(cfg) 89 c.Assert(err, jc.ErrorIsNil) 90 91 st, err := juju.NewAPIState(dummy.AdminUserTag(), env, api.DialOpts{}) 92 c.Assert(st, gc.NotNil) 93 94 // the secrets will not be updated, as they already exist 95 attrs, err := st.Client().EnvironmentGet() 96 c.Assert(attrs["secret"], gc.Equals, "pork") 97 98 c.Assert(st.Close(), gc.IsNil) 99 } 100 101 type NewAPIClientSuite struct { 102 coretesting.FakeJujuHomeSuite 103 testing.MgoSuite 104 envtesting.ToolsFixture 105 } 106 107 var _ = gc.Suite(&NewAPIClientSuite{}) 108 109 func (cs *NewAPIClientSuite) SetUpSuite(c *gc.C) { 110 cs.FakeJujuHomeSuite.SetUpSuite(c) 111 cs.MgoSuite.SetUpSuite(c) 112 // Since most tests use invalid testing API server addresses, we 113 // need to mock this to avoid errors. 114 cs.PatchValue(juju.ServerAddress, func(addr string) (network.HostPort, error) { 115 host, strPort, err := net.SplitHostPort(addr) 116 if err != nil { 117 c.Logf("serverAddress %q invalid, ignoring error: %v", addr, err) 118 } 119 port, err := strconv.Atoi(strPort) 120 if err != nil { 121 c.Logf("serverAddress %q port, ignoring error: %v", addr, err) 122 port = 0 123 } 124 return network.NewHostPorts(port, host)[0], nil 125 }) 126 } 127 128 func (cs *NewAPIClientSuite) TearDownSuite(c *gc.C) { 129 cs.MgoSuite.TearDownSuite(c) 130 cs.FakeJujuHomeSuite.TearDownSuite(c) 131 } 132 133 func (cs *NewAPIClientSuite) SetUpTest(c *gc.C) { 134 cs.ToolsFixture.SetUpTest(c) 135 cs.FakeJujuHomeSuite.SetUpTest(c) 136 cs.MgoSuite.SetUpTest(c) 137 } 138 139 func (cs *NewAPIClientSuite) TearDownTest(c *gc.C) { 140 dummy.Reset() 141 cs.ToolsFixture.TearDownTest(c) 142 cs.MgoSuite.TearDownTest(c) 143 cs.FakeJujuHomeSuite.TearDownTest(c) 144 } 145 146 func (s *NewAPIClientSuite) bootstrapEnv(c *gc.C, envName string, store configstore.Storage) { 147 if store == nil { 148 store = configstore.NewMem() 149 } 150 ctx := envtesting.BootstrapContext(c) 151 c.Logf("env name: %s", envName) 152 env, err := environs.PrepareFromName(envName, ctx, store) 153 c.Assert(err, jc.ErrorIsNil) 154 155 storageDir := c.MkDir() 156 s.PatchValue(&envtools.DefaultBaseURL, storageDir) 157 stor, err := filestorage.NewFileStorageWriter(storageDir) 158 c.Assert(err, jc.ErrorIsNil) 159 envtesting.UploadFakeTools(c, stor, "released", "released") 160 161 err = bootstrap.Bootstrap(ctx, env, bootstrap.BootstrapParams{}) 162 c.Assert(err, jc.ErrorIsNil) 163 info, err := store.ReadInfo(envName) 164 c.Assert(err, jc.ErrorIsNil) 165 creds := info.APICredentials() 166 creds.User = dummy.AdminUserTag().Name() 167 c.Logf("set creds: %#v", creds) 168 info.SetAPICredentials(creds) 169 err = info.Write() 170 c.Assert(err, jc.ErrorIsNil) 171 c.Logf("creds: %#v", info.APICredentials()) 172 info, err = store.ReadInfo(envName) 173 c.Assert(err, jc.ErrorIsNil) 174 c.Logf("read creds: %#v", info.APICredentials()) 175 c.Logf("store: %#v", store) 176 } 177 178 func (s *NewAPIClientSuite) TestNameDefault(c *gc.C) { 179 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 180 // The connection logic should not delay the config connection 181 // at all when there is no environment info available. 182 // Make sure of that by providing a suitably long delay 183 // and checking that the connection happens within that 184 // time. 185 s.PatchValue(juju.ProviderConnectDelay, coretesting.LongWait) 186 s.bootstrapEnv(c, coretesting.SampleEnvName, defaultConfigStore(c)) 187 188 startTime := time.Now() 189 apiclient, err := juju.NewAPIClientFromName("") 190 c.Assert(err, jc.ErrorIsNil) 191 defer apiclient.Close() 192 c.Assert(time.Since(startTime), jc.LessThan, coretesting.LongWait) 193 194 // We should get the default sample environment if we ask for "" 195 assertEnvironmentName(c, apiclient, coretesting.SampleEnvName) 196 } 197 198 func (s *NewAPIClientSuite) TestNameNotDefault(c *gc.C) { 199 envName := coretesting.SampleCertName + "-2" 200 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig, envName) 201 s.bootstrapEnv(c, envName, defaultConfigStore(c)) 202 apiclient, err := juju.NewAPIClientFromName(envName) 203 c.Assert(err, jc.ErrorIsNil) 204 defer apiclient.Close() 205 assertEnvironmentName(c, apiclient, envName) 206 } 207 208 func (s *NewAPIClientSuite) TestWithInfoOnly(c *gc.C) { 209 store := newConfigStore("noconfig", dummyStoreInfo) 210 211 called := 0 212 expectState := mockedAPIState(mockedHostPort | mockedEnvironTag) 213 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 214 checkCommonAPIInfoAttrs(c, apiInfo, opts) 215 c.Check(apiInfo.EnvironTag, gc.Equals, names.NewEnvironTag(fakeUUID)) 216 called++ 217 return expectState, nil 218 } 219 220 // Give NewAPIFromStore a store interface that can report when the 221 // config was written to, to check if the cache is updated. 222 mockStore := &storageWithWriteNotify{store: store} 223 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 224 c.Assert(err, jc.ErrorIsNil) 225 c.Assert(st, gc.Equals, expectState) 226 c.Assert(called, gc.Equals, 1) 227 c.Assert(mockStore.written, jc.IsTrue) 228 info, err := store.ReadInfo("noconfig") 229 c.Assert(err, jc.ErrorIsNil) 230 ep := info.APIEndpoint() 231 c.Check(ep.Addresses, jc.DeepEquals, []string{ 232 "0.1.2.3:1234", "[2001:db8::1]:1234", 233 }) 234 c.Check(ep.EnvironUUID, gc.Equals, fakeUUID) 235 mockStore.written = false 236 237 // If APIHostPorts haven't changed, then the store won't be updated. 238 st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 239 c.Assert(err, jc.ErrorIsNil) 240 c.Assert(st, gc.Equals, expectState) 241 c.Assert(called, gc.Equals, 2) 242 c.Assert(mockStore.written, jc.IsFalse) 243 } 244 245 func (s *NewAPIClientSuite) TestWithConfigAndNoInfo(c *gc.C) { 246 c.Skip("not really possible now that there is no defined admin user") 247 coretesting.MakeSampleJujuHome(c) 248 249 store := newConfigStore(coretesting.SampleEnvName, &environInfo{ 250 bootstrapConfig: map[string]interface{}{ 251 "type": "dummy", 252 "name": "myenv", 253 "state-server": true, 254 "authorized-keys": "i-am-a-key", 255 "default-series": config.LatestLtsSeries(), 256 "firewall-mode": config.FwInstance, 257 "development": false, 258 "ssl-hostname-verification": true, 259 "admin-secret": "adminpass", 260 }, 261 }) 262 s.bootstrapEnv(c, coretesting.SampleEnvName, store) 263 264 info, err := store.ReadInfo("myenv") 265 c.Assert(err, jc.ErrorIsNil) 266 c.Assert(info, gc.NotNil) 267 c.Logf("%#v", info.APICredentials()) 268 269 called := 0 270 expectState := mockedAPIState(0) 271 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 272 c.Check(apiInfo.Tag, gc.Equals, dummy.AdminUserTag()) 273 c.Check(string(apiInfo.CACert), gc.Not(gc.Equals), "") 274 c.Check(apiInfo.Password, gc.Equals, "adminpass") 275 // EnvironTag wasn't in regular Config 276 c.Check(apiInfo.EnvironTag.Id(), gc.Equals, "") 277 c.Check(opts, gc.DeepEquals, api.DefaultDialOpts()) 278 called++ 279 return expectState, nil 280 } 281 st, err := juju.NewAPIFromStore("myenv", store, apiOpen) 282 c.Assert(err, jc.ErrorIsNil) 283 c.Assert(st, gc.Equals, expectState) 284 c.Assert(called, gc.Equals, 1) 285 286 // Make sure the cache is updated. 287 info, err = store.ReadInfo("myenv") 288 c.Assert(err, jc.ErrorIsNil) 289 c.Assert(info, gc.NotNil) 290 ep := info.APIEndpoint() 291 c.Assert(ep.Addresses, gc.HasLen, 1) 292 c.Check(ep.Addresses[0], gc.Matches, `localhost:\d+`) 293 c.Check(ep.CACert, gc.Not(gc.Equals), "") 294 } 295 296 func (s *NewAPIClientSuite) TestWithInfoError(c *gc.C) { 297 expectErr := fmt.Errorf("an error") 298 store := newConfigStoreWithError(expectErr) 299 client, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen) 300 c.Assert(err, gc.Equals, expectErr) 301 c.Assert(client, gc.IsNil) 302 } 303 304 func (s *NewAPIClientSuite) TestWithInfoNoAddresses(c *gc.C) { 305 store := newConfigStore("noconfig", &environInfo{ 306 endpoint: configstore.APIEndpoint{ 307 Addresses: []string{}, 308 CACert: "certificated", 309 }, 310 }) 311 st, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen) 312 c.Assert(err, gc.ErrorMatches, `environment "noconfig" not found`) 313 c.Assert(st, gc.IsNil) 314 } 315 316 var noTagStoreInfo = &environInfo{ 317 creds: configstore.APICredentials{ 318 User: "foo", 319 Password: "foopass", 320 }, 321 endpoint: configstore.APIEndpoint{ 322 Addresses: []string{"foo.invalid"}, 323 CACert: "certificated", 324 }, 325 } 326 327 type mockedStateFlags int 328 329 const ( 330 noFlags mockedStateFlags = 0x0000 331 mockedHostPort mockedStateFlags = 0x0001 332 mockedEnvironTag mockedStateFlags = 0x0002 333 mockedPreferIPv6 mockedStateFlags = 0x0004 334 ) 335 336 func mockedAPIState(flags mockedStateFlags) *mockAPIState { 337 hasHostPort := flags&mockedHostPort == mockedHostPort 338 hasEnvironTag := flags&mockedEnvironTag == mockedEnvironTag 339 preferIPv6 := flags&mockedPreferIPv6 == mockedPreferIPv6 340 addr := "" 341 342 apiHostPorts := [][]network.HostPort{} 343 if hasHostPort { 344 var apiAddrs []network.Address 345 ipv4Address := network.NewAddress("0.1.2.3") 346 ipv6Address := network.NewAddress("2001:db8::1") 347 if preferIPv6 { 348 addr = net.JoinHostPort(ipv6Address.Value, "1234") 349 apiAddrs = append(apiAddrs, ipv6Address, ipv4Address) 350 } else { 351 addr = net.JoinHostPort(ipv4Address.Value, "1234") 352 apiAddrs = append(apiAddrs, ipv4Address, ipv6Address) 353 } 354 apiHostPorts = [][]network.HostPort{ 355 network.AddressesWithPort(apiAddrs, 1234), 356 } 357 } 358 environTag := "" 359 if hasEnvironTag { 360 environTag = "environment-df136476-12e9-11e4-8a70-b2227cce2b54" 361 } 362 return &mockAPIState{ 363 apiHostPorts: apiHostPorts, 364 environTag: environTag, 365 addr: addr, 366 } 367 } 368 369 func checkCommonAPIInfoAttrs(c *gc.C, apiInfo *api.Info, opts api.DialOpts) { 370 c.Check(apiInfo.Tag, gc.Equals, names.NewUserTag("foo")) 371 c.Check(string(apiInfo.CACert), gc.Equals, "certificated") 372 c.Check(apiInfo.Password, gc.Equals, "foopass") 373 c.Check(opts, gc.DeepEquals, api.DefaultDialOpts()) 374 } 375 376 func (s *NewAPIClientSuite) TestWithInfoNoEnvironTag(c *gc.C) { 377 store := newConfigStore("noconfig", noTagStoreInfo) 378 379 called := 0 380 expectState := mockedAPIState(mockedHostPort | mockedEnvironTag) 381 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 382 checkCommonAPIInfoAttrs(c, apiInfo, opts) 383 c.Check(apiInfo.EnvironTag.Id(), gc.Equals, "") 384 called++ 385 return expectState, nil 386 } 387 388 // Give NewAPIFromStore a store interface that can report when the 389 // config was written to, to check if the cache is updated. 390 mockStore := &storageWithWriteNotify{store: store} 391 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 392 c.Assert(err, jc.ErrorIsNil) 393 c.Assert(st, gc.Equals, expectState) 394 c.Assert(called, gc.Equals, 1) 395 c.Assert(mockStore.written, jc.IsTrue) 396 info, err := store.ReadInfo("noconfig") 397 c.Assert(err, jc.ErrorIsNil) 398 c.Check(info.APIEndpoint().Addresses, jc.DeepEquals, []string{ 399 "0.1.2.3:1234", "[2001:db8::1]:1234", 400 }) 401 c.Check(info.APIEndpoint().EnvironUUID, gc.Equals, fakeUUID) 402 403 // Now simulate prefer-ipv6: true 404 store = newConfigStore("noconfig", noTagStoreInfo) 405 mockStore = &storageWithWriteNotify{store: store} 406 s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool { 407 return true 408 }) 409 expectState = mockedAPIState(mockedHostPort | mockedEnvironTag | mockedPreferIPv6) 410 st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 411 c.Assert(err, jc.ErrorIsNil) 412 c.Assert(st, gc.Equals, expectState) 413 c.Assert(called, gc.Equals, 2) 414 c.Assert(mockStore.written, jc.IsTrue) 415 info, err = store.ReadInfo("noconfig") 416 c.Assert(err, jc.ErrorIsNil) 417 c.Check(info.APIEndpoint().Addresses, jc.DeepEquals, []string{ 418 "[2001:db8::1]:1234", "0.1.2.3:1234", 419 }) 420 c.Check(info.APIEndpoint().EnvironUUID, gc.Equals, fakeUUID) 421 } 422 423 func (s *NewAPIClientSuite) TestWithInfoNoAPIHostports(c *gc.C) { 424 // The local cache doesn't have an EnvironTag, which the API does 425 // return. However, the API doesn't have apiHostPorts, we don't want to 426 // override the local cache with bad endpoints. 427 store := newConfigStore("noconfig", noTagStoreInfo) 428 429 called := 0 430 expectState := mockedAPIState(mockedEnvironTag | mockedPreferIPv6) 431 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 432 checkCommonAPIInfoAttrs(c, apiInfo, opts) 433 c.Check(apiInfo.EnvironTag.Id(), gc.Equals, "") 434 called++ 435 return expectState, nil 436 } 437 438 mockStore := &storageWithWriteNotify{store: store} 439 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 440 c.Assert(err, jc.ErrorIsNil) 441 c.Assert(st, gc.Equals, expectState) 442 c.Assert(called, gc.Equals, 1) 443 c.Assert(mockStore.written, jc.IsTrue) 444 info, err := store.ReadInfo("noconfig") 445 c.Assert(err, jc.ErrorIsNil) 446 ep := info.APIEndpoint() 447 // We should have cached the environ tag, but not disturbed the 448 // Addresses 449 c.Check(ep.Addresses, gc.HasLen, 1) 450 c.Check(ep.Addresses[0], gc.Matches, `foo\.invalid`) 451 c.Check(ep.EnvironUUID, gc.Equals, fakeUUID) 452 } 453 454 func (s *NewAPIClientSuite) TestNoEnvironTagDoesntOverwriteCached(c *gc.C) { 455 store := newConfigStore("noconfig", dummyStoreInfo) 456 called := 0 457 // State returns a new set of APIHostPorts but not a new EnvironTag. We 458 // shouldn't override the cached value with environ tag of "". 459 expectState := mockedAPIState(mockedHostPort) 460 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 461 checkCommonAPIInfoAttrs(c, apiInfo, opts) 462 c.Check(apiInfo.EnvironTag, gc.Equals, names.NewEnvironTag(fakeUUID)) 463 called++ 464 return expectState, nil 465 } 466 467 mockStore := &storageWithWriteNotify{store: store} 468 st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 469 c.Assert(err, jc.ErrorIsNil) 470 c.Assert(st, gc.Equals, expectState) 471 c.Assert(called, gc.Equals, 1) 472 c.Assert(mockStore.written, jc.IsTrue) 473 info, err := store.ReadInfo("noconfig") 474 c.Assert(err, jc.ErrorIsNil) 475 ep := info.APIEndpoint() 476 c.Check(ep.Addresses, gc.DeepEquals, []string{ 477 "0.1.2.3:1234", "[2001:db8::1]:1234", 478 }) 479 c.Check(ep.EnvironUUID, gc.Equals, fakeUUID) 480 481 // Now simulate prefer-ipv6: true 482 s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool { 483 return true 484 }) 485 expectState = mockedAPIState(mockedHostPort | mockedPreferIPv6) 486 st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen) 487 c.Assert(err, jc.ErrorIsNil) 488 c.Assert(st, gc.Equals, expectState) 489 c.Assert(called, gc.Equals, 2) 490 c.Assert(mockStore.written, jc.IsTrue) 491 info, err = store.ReadInfo("noconfig") 492 c.Assert(err, jc.ErrorIsNil) 493 ep = info.APIEndpoint() 494 c.Check(ep.Addresses, gc.DeepEquals, []string{ 495 "[2001:db8::1]:1234", "0.1.2.3:1234", 496 }) 497 c.Check(ep.EnvironUUID, gc.Equals, fakeUUID) 498 } 499 500 func (s *NewAPIClientSuite) TestWithInfoAPIOpenError(c *gc.C) { 501 store := newConfigStore("noconfig", &environInfo{ 502 endpoint: configstore.APIEndpoint{ 503 Addresses: []string{"foo.invalid"}, 504 }, 505 }) 506 507 apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) { 508 return nil, errors.Errorf("an error") 509 } 510 st, err := juju.NewAPIFromStore("noconfig", store, apiOpen) 511 // We expect to get the isNotFound error as it is more important than the 512 // infoConnectError "an error" 513 c.Assert(err, gc.ErrorMatches, "environment \"noconfig\" not found") 514 c.Assert(st, gc.IsNil) 515 } 516 517 func (s *NewAPIClientSuite) TestWithSlowInfoConnect(c *gc.C) { 518 coretesting.MakeSampleJujuHome(c) 519 store := configstore.NewMem() 520 s.bootstrapEnv(c, coretesting.SampleEnvName, store) 521 setEndpointAddressAndHostname(c, store, coretesting.SampleEnvName, "0.1.2.3", "infoapi.invalid") 522 523 infoOpenedState := mockedAPIState(noFlags) 524 infoEndpointOpened := make(chan struct{}) 525 cfgOpenedState := mockedAPIState(noFlags) 526 // On a sample run with no delay, the logic took 45ms to run, so 527 // we make the delay slightly more than that, so that if the 528 // logic doesn't delay at all, the test will fail reasonably consistently. 529 s.PatchValue(juju.ProviderConnectDelay, 50*time.Millisecond) 530 apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) { 531 if info.Addrs[0] == "0.1.2.3" { 532 infoEndpointOpened <- struct{}{} 533 return infoOpenedState, nil 534 } 535 return cfgOpenedState, nil 536 } 537 538 stateClosed := make(chan juju.APIState) 539 infoOpenedState.close = func(st juju.APIState) error { 540 stateClosed <- st 541 return nil 542 } 543 cfgOpenedState.close = infoOpenedState.close 544 545 startTime := time.Now() 546 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 547 c.Assert(err, jc.ErrorIsNil) 548 // The connection logic should wait for some time before opening 549 // the API from the configuration. 550 c.Assert(time.Since(startTime), jc.GreaterThan, *juju.ProviderConnectDelay) 551 c.Assert(st, gc.Equals, cfgOpenedState) 552 553 select { 554 case <-infoEndpointOpened: 555 case <-time.After(coretesting.LongWait): 556 c.Errorf("api never opened via info") 557 } 558 559 // Check that the ignored state was closed. 560 select { 561 case st := <-stateClosed: 562 c.Assert(st, gc.Equals, infoOpenedState) 563 case <-time.After(coretesting.LongWait): 564 c.Errorf("timed out waiting for state to be closed") 565 } 566 } 567 568 type badBootstrapInfo struct { 569 configstore.EnvironInfo 570 } 571 572 // BootstrapConfig is returned as a map with real content, but the content 573 // isn't actually valid configuration, causing config.New to fail 574 func (m *badBootstrapInfo) BootstrapConfig() map[string]interface{} { 575 return map[string]interface{}{"something": "else"} 576 } 577 578 func (s *NewAPIClientSuite) TestBadConfigDoesntPanic(c *gc.C) { 579 badInfo := &badBootstrapInfo{} 580 cfg, err := juju.GetConfig(badInfo, nil, "test") 581 // The specific error we get depends on what key is invalid, which is a 582 // bit spurious, but what we care about is that we didn't get a panic, 583 // but instead got an error 584 c.Assert(err, gc.ErrorMatches, ".*expected.*got nothing") 585 c.Assert(cfg, gc.IsNil) 586 } 587 588 func setEndpointAddressAndHostname(c *gc.C, store configstore.Storage, envName string, addr, host string) { 589 // Populate the environment's info with an endpoint 590 // with a known address and hostname. 591 info, err := store.ReadInfo(coretesting.SampleEnvName) 592 c.Assert(err, jc.ErrorIsNil) 593 info.SetAPIEndpoint(configstore.APIEndpoint{ 594 Addresses: []string{addr}, 595 Hostnames: []string{host}, 596 CACert: "certificated", 597 }) 598 err = info.Write() 599 c.Assert(err, jc.ErrorIsNil) 600 } 601 602 func (s *NewAPIClientSuite) TestWithSlowConfigConnect(c *gc.C) { 603 coretesting.MakeSampleJujuHome(c) 604 605 store := configstore.NewMem() 606 s.bootstrapEnv(c, coretesting.SampleEnvName, store) 607 setEndpointAddressAndHostname(c, store, coretesting.SampleEnvName, "0.1.2.3", "infoapi.invalid") 608 609 infoOpenedState := mockedAPIState(noFlags) 610 infoEndpointOpened := make(chan struct{}) 611 cfgOpenedState := mockedAPIState(noFlags) 612 cfgEndpointOpened := make(chan struct{}) 613 614 s.PatchValue(juju.ProviderConnectDelay, 0*time.Second) 615 apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) { 616 if info.Addrs[0] == "0.1.2.3" { 617 infoEndpointOpened <- struct{}{} 618 <-infoEndpointOpened 619 return infoOpenedState, nil 620 } 621 cfgEndpointOpened <- struct{}{} 622 <-cfgEndpointOpened 623 return cfgOpenedState, nil 624 } 625 626 stateClosed := make(chan juju.APIState) 627 infoOpenedState.close = func(st juju.APIState) error { 628 stateClosed <- st 629 return nil 630 } 631 cfgOpenedState.close = infoOpenedState.close 632 633 done := make(chan struct{}) 634 go func() { 635 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 636 c.Check(err, jc.ErrorIsNil) 637 c.Check(st, gc.Equals, infoOpenedState) 638 close(done) 639 }() 640 641 // Check that we're trying to connect to both endpoints: 642 select { 643 case <-infoEndpointOpened: 644 case <-time.After(coretesting.LongWait): 645 c.Fatalf("api never opened via info") 646 } 647 select { 648 case <-cfgEndpointOpened: 649 case <-time.After(coretesting.LongWait): 650 c.Fatalf("api never opened via config") 651 } 652 // Let the info endpoint open go ahead and 653 // check that the NewAPIFromStore call returns. 654 infoEndpointOpened <- struct{}{} 655 select { 656 case <-done: 657 case <-time.After(coretesting.LongWait): 658 c.Errorf("timed out opening API") 659 } 660 661 // Let the config endpoint open go ahead and 662 // check that its state is closed. 663 cfgEndpointOpened <- struct{}{} 664 select { 665 case st := <-stateClosed: 666 c.Assert(st, gc.Equals, cfgOpenedState) 667 case <-time.After(coretesting.LongWait): 668 c.Errorf("timed out waiting for state to be closed") 669 } 670 } 671 672 func (s *NewAPIClientSuite) TestBothError(c *gc.C) { 673 coretesting.MakeSampleJujuHome(c) 674 store := configstore.NewMem() 675 s.bootstrapEnv(c, coretesting.SampleEnvName, store) 676 setEndpointAddressAndHostname(c, store, coretesting.SampleEnvName, "0.1.2.3", "infoapi.invalid") 677 678 s.PatchValue(juju.ProviderConnectDelay, 0*time.Second) 679 apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) { 680 if info.Addrs[0] == "infoapi.invalid" { 681 return nil, fmt.Errorf("info connect failed") 682 } 683 return nil, fmt.Errorf("config connect failed") 684 } 685 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 686 c.Check(err, gc.ErrorMatches, "config connect failed") 687 c.Check(st, gc.IsNil) 688 } 689 690 func defaultConfigStore(c *gc.C) configstore.Storage { 691 store, err := configstore.Default() 692 c.Assert(err, jc.ErrorIsNil) 693 return store 694 } 695 696 func (s *NewAPIClientSuite) TestWithBootstrapConfigAndNoEnvironmentsFile(c *gc.C) { 697 coretesting.MakeSampleJujuHome(c) 698 store := configstore.NewMem() 699 s.bootstrapEnv(c, coretesting.SampleEnvName, store) 700 info, err := store.ReadInfo(coretesting.SampleEnvName) 701 c.Assert(err, jc.ErrorIsNil) 702 c.Assert(info.BootstrapConfig(), gc.NotNil) 703 c.Assert(info.APIEndpoint().Addresses, gc.HasLen, 0) 704 705 err = os.Remove(osenv.JujuHomePath("environments.yaml")) 706 c.Assert(err, jc.ErrorIsNil) 707 708 apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) { 709 return mockedAPIState(noFlags), nil 710 } 711 st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen) 712 c.Check(err, jc.ErrorIsNil) 713 st.Close() 714 } 715 716 func (s *NewAPIClientSuite) TestWithBootstrapConfigTakesPrecedence(c *gc.C) { 717 // We want to make sure that the code is using the bootstrap 718 // config rather than information from environments.yaml, 719 // even when there is an entry in environments.yaml 720 // We can do that by changing the info bootstrap config 721 // so it has a different environment name. 722 coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig) 723 724 store := configstore.NewMem() 725 s.bootstrapEnv(c, coretesting.SampleEnvName, store) 726 info, err := store.ReadInfo(coretesting.SampleEnvName) 727 c.Assert(err, jc.ErrorIsNil) 728 729 envName2 := coretesting.SampleCertName + "-2" 730 info2 := store.CreateInfo(envName2) 731 info2.SetBootstrapConfig(info.BootstrapConfig()) 732 err = info2.Write() 733 c.Assert(err, jc.ErrorIsNil) 734 735 // Now we have info for envName2 which will actually 736 // cause a connection to the originally bootstrapped 737 // state. 738 apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) { 739 return mockedAPIState(noFlags), nil 740 } 741 st, err := juju.NewAPIFromStore(envName2, store, apiOpen) 742 c.Check(err, jc.ErrorIsNil) 743 st.Close() 744 745 // Sanity check that connecting to the envName2 746 // but with no info fails. 747 // Currently this panics with an "environment not prepared" error. 748 // Disable for now until an upcoming branch fixes it. 749 // err = info2.Destroy() 750 // c.Assert(err, jc.ErrorIsNil) 751 // st, err = juju.NewAPIFromStore(envName2, store) 752 // if err == nil { 753 // st.Close() 754 // } 755 // c.Assert(err, gc.ErrorMatches, "fooobie") 756 } 757 758 func assertEnvironmentName(c *gc.C, client *api.Client, expectName string) { 759 envInfo, err := client.EnvironmentInfo() 760 c.Assert(err, jc.ErrorIsNil) 761 c.Assert(envInfo.Name, gc.Equals, expectName) 762 } 763 764 // newConfigStoreWithError that will return the given 765 // error from ReadInfo. 766 func newConfigStoreWithError(err error) configstore.Storage { 767 return &errorConfigStorage{ 768 Storage: configstore.NewMem(), 769 err: err, 770 } 771 } 772 773 type errorConfigStorage struct { 774 configstore.Storage 775 err error 776 } 777 778 func (store *errorConfigStorage) ReadInfo(envName string) (configstore.EnvironInfo, error) { 779 return nil, store.err 780 } 781 782 type environInfo struct { 783 creds configstore.APICredentials 784 endpoint configstore.APIEndpoint 785 bootstrapConfig map[string]interface{} 786 } 787 788 // newConfigStore returns a storage that contains information 789 // for the environment name. 790 func newConfigStore(envName string, info *environInfo) configstore.Storage { 791 store := configstore.NewMem() 792 newInfo := store.CreateInfo(envName) 793 newInfo.SetAPICredentials(info.creds) 794 newInfo.SetAPIEndpoint(info.endpoint) 795 newInfo.SetBootstrapConfig(info.bootstrapConfig) 796 err := newInfo.Write() 797 if err != nil { 798 panic(err) 799 } 800 return store 801 } 802 803 type storageWithWriteNotify struct { 804 written bool 805 store configstore.Storage 806 } 807 808 func (*storageWithWriteNotify) CreateInfo(envName string) configstore.EnvironInfo { 809 panic("CreateInfo not implemented") 810 } 811 812 func (*storageWithWriteNotify) List() ([]string, error) { 813 panic("List not implemented") 814 } 815 816 func (s *storageWithWriteNotify) ReadInfo(envName string) (configstore.EnvironInfo, error) { 817 info, err := s.store.ReadInfo(envName) 818 if err != nil { 819 return nil, err 820 } 821 return &infoWithWriteNotify{ 822 written: &s.written, 823 EnvironInfo: info, 824 }, nil 825 } 826 827 type infoWithWriteNotify struct { 828 configstore.EnvironInfo 829 written *bool 830 } 831 832 func (info *infoWithWriteNotify) Write() error { 833 *info.written = true 834 return info.EnvironInfo.Write() 835 } 836 837 type CacheAPIEndpointsSuite struct { 838 jujutesting.JujuConnSuite 839 840 hostPorts [][]network.HostPort 841 envTag names.EnvironTag 842 apiHostPort network.HostPort 843 store configstore.Storage 844 845 resolveSeq int 846 resolveNumCalls int 847 numResolved int 848 gocheckC *gc.C 849 } 850 851 var _ = gc.Suite(&CacheAPIEndpointsSuite{}) 852 853 func (s *CacheAPIEndpointsSuite) SetUpTest(c *gc.C) { 854 s.PatchValue(juju.ResolveOrDropHostnames, s.mockResolveOrDropHostnames) 855 856 s.hostPorts = [][]network.HostPort{ 857 network.NewHostPorts(1234, 858 "1.0.0.1", 859 "192.0.0.1", 860 "127.0.0.1", 861 "ipv4+6.example.com", 862 "localhost", 863 "169.254.1.1", 864 "ipv4.example.com", 865 "invalid host", 866 "ipv6+6.example.com", 867 "ipv4+4.example.com", 868 "::1", 869 "fe80::1", 870 "ipv6.example.com", 871 "fc00::111", 872 "2001:db8::1", 873 ), 874 network.NewHostPorts(1235, 875 "1.0.0.2", 876 "2001:db8::2", 877 "::1", 878 "127.0.0.1", 879 "ipv6+4.example.com", 880 "localhost", 881 ), 882 } 883 s.gocheckC = c 884 s.resolveSeq = 1 885 s.resolveNumCalls = 0 886 s.numResolved = 0 887 s.envTag = names.NewEnvironTag(fakeUUID) 888 s.store = configstore.NewMem() 889 890 s.JujuConnSuite.SetUpTest(c) 891 892 apiHostPort, err := network.ParseHostPorts(s.APIState.Addr()) 893 c.Assert(err, jc.ErrorIsNil) 894 s.apiHostPort = apiHostPort[0] 895 } 896 897 func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6True(c *gc.C) { 898 info := s.store.CreateInfo("env-name1") 899 s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool { 900 return true 901 }) 902 // First test cacheChangedAPIInfo behaves as expected. 903 err := juju.CacheChangedAPIInfo(info, s.hostPorts, s.apiHostPort, s.envTag.Id(), "") 904 c.Assert(err, jc.ErrorIsNil) 905 s.assertEndpointsPreferIPv6True(c, info) 906 907 // Now test cacheAPIInfo behaves the same way. 908 s.resolveSeq = 1 909 s.resolveNumCalls = 0 910 s.numResolved = 0 911 info = s.store.CreateInfo("env-name2") 912 mockAPIInfo := s.APIInfo(c) 913 mockAPIInfo.EnvironTag = s.envTag 914 hps := network.CollapseHostPorts(s.hostPorts) 915 mockAPIInfo.Addrs = network.HostPortsToStrings(hps) 916 err = juju.CacheAPIInfo(s.APIState, info, mockAPIInfo) 917 c.Assert(err, jc.ErrorIsNil) 918 s.assertEndpointsPreferIPv6True(c, info) 919 } 920 921 func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6False(c *gc.C) { 922 info := s.store.CreateInfo("env-name1") 923 s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool { 924 return false 925 }) 926 // First test cacheChangedAPIInfo behaves as expected. 927 err := juju.CacheChangedAPIInfo(info, s.hostPorts, s.apiHostPort, s.envTag.Id(), "") 928 c.Assert(err, jc.ErrorIsNil) 929 s.assertEndpointsPreferIPv6False(c, info) 930 931 // Now test cacheAPIInfo behaves the same way. 932 s.resolveSeq = 1 933 s.resolveNumCalls = 0 934 s.numResolved = 0 935 info = s.store.CreateInfo("env-name2") 936 mockAPIInfo := s.APIInfo(c) 937 mockAPIInfo.EnvironTag = s.envTag 938 hps := network.CollapseHostPorts(s.hostPorts) 939 mockAPIInfo.Addrs = network.HostPortsToStrings(hps) 940 err = juju.CacheAPIInfo(s.APIState, info, mockAPIInfo) 941 c.Assert(err, jc.ErrorIsNil) 942 s.assertEndpointsPreferIPv6False(c, info) 943 } 944 945 func (s *CacheAPIEndpointsSuite) TestResolveSkippedWhenHostnamesUnchanged(c *gc.C) { 946 // Test that if new endpoints hostnames are the same as the 947 // cached, no DNS resolution happens (i.e. we don't resolve on 948 // every connection, but as needed). 949 info := s.store.CreateInfo("env-name") 950 hps := network.NewHostPorts(1234, 951 "8.8.8.8", 952 "example.com", 953 "10.0.0.1", 954 ) 955 info.SetAPIEndpoint(configstore.APIEndpoint{ 956 Hostnames: network.HostPortsToStrings(hps), 957 }) 958 err := info.Write() 959 c.Assert(err, jc.ErrorIsNil) 960 961 addrs, hosts, changed := juju.PrepareEndpointsForCaching( 962 info, [][]network.HostPort{hps}, network.HostPort{}, 963 ) 964 c.Assert(addrs, gc.IsNil) 965 c.Assert(hosts, gc.IsNil) 966 c.Assert(changed, jc.IsFalse) 967 c.Assert(s.resolveNumCalls, gc.Equals, 0) 968 c.Assert( 969 c.GetTestLog(), 970 jc.Contains, 971 "DEBUG juju.api API hostnames unchanged - not resolving", 972 ) 973 } 974 975 func (s *CacheAPIEndpointsSuite) TestResolveCalledWithChangedHostnames(c *gc.C) { 976 // Test that if new endpoints hostnames are different than the 977 // cached hostnames DNS resolution happens and we compare resolved 978 // addresses. 979 info := s.store.CreateInfo("env-name") 980 // Because Hostnames are sorted before caching, reordering them 981 // will simulate they have changed. 982 unsortedHPs := network.NewHostPorts(1234, 983 "ipv4.example.com", 984 "8.8.8.8", 985 "ipv6.example.com", 986 "10.0.0.1", 987 ) 988 strUnsorted := network.HostPortsToStrings(unsortedHPs) 989 sortedHPs := network.NewHostPorts(1234, 990 "8.8.8.8", 991 "ipv4.example.com", 992 "ipv6.example.com", 993 "10.0.0.1", 994 ) 995 strSorted := network.HostPortsToStrings(sortedHPs) 996 resolvedHPs := network.NewHostPorts(1234, 997 "0.1.2.1", // from ipv4.example.com 998 "8.8.8.8", 999 "10.0.0.1", 1000 "fc00::2", // from ipv6.example.com 1001 ) 1002 strResolved := network.HostPortsToStrings(resolvedHPs) 1003 info.SetAPIEndpoint(configstore.APIEndpoint{ 1004 Hostnames: strUnsorted, 1005 }) 1006 err := info.Write() 1007 c.Assert(err, jc.ErrorIsNil) 1008 1009 addrs, hosts, changed := juju.PrepareEndpointsForCaching( 1010 info, [][]network.HostPort{unsortedHPs}, network.HostPort{}, 1011 ) 1012 c.Assert(addrs, jc.DeepEquals, strResolved) 1013 c.Assert(hosts, jc.DeepEquals, strSorted) 1014 c.Assert(changed, jc.IsTrue) 1015 c.Assert(s.resolveNumCalls, gc.Equals, 1) 1016 c.Assert(s.numResolved, gc.Equals, 2) 1017 expectLog := fmt.Sprintf("DEBUG juju.api API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs) 1018 c.Assert(c.GetTestLog(), jc.Contains, expectLog) 1019 expectLog = fmt.Sprintf("INFO juju.api new API addresses to cache %v", resolvedHPs) 1020 c.Assert(c.GetTestLog(), jc.Contains, expectLog) 1021 } 1022 1023 func (s *CacheAPIEndpointsSuite) TestAfterResolvingUnchangedAddressesNotCached(c *gc.C) { 1024 // Test that if new endpoints hostnames are different than the 1025 // cached hostnames, but after resolving the addresses match the 1026 // cached addresses, the cache is not changed. 1027 info := s.store.CreateInfo("env-name") 1028 // Because Hostnames are sorted before caching, reordering them 1029 // will simulate they have changed. 1030 unsortedHPs := network.NewHostPorts(1234, 1031 "ipv4.example.com", 1032 "8.8.8.8", 1033 "ipv6.example.com", 1034 "10.0.0.1", 1035 ) 1036 strUnsorted := network.HostPortsToStrings(unsortedHPs) 1037 sortedHPs := network.NewHostPorts(1234, 1038 "8.8.8.8", 1039 "ipv4.example.com", 1040 "ipv6.example.com", 1041 "10.0.0.1", 1042 ) 1043 resolvedHPs := network.NewHostPorts(1234, 1044 "0.1.2.1", // from ipv4.example.com 1045 "8.8.8.8", 1046 "10.0.0.1", 1047 "fc00::2", // from ipv6.example.com 1048 ) 1049 strResolved := network.HostPortsToStrings(resolvedHPs) 1050 info.SetAPIEndpoint(configstore.APIEndpoint{ 1051 Hostnames: strUnsorted, 1052 Addresses: strResolved, 1053 }) 1054 err := info.Write() 1055 c.Assert(err, jc.ErrorIsNil) 1056 1057 addrs, hosts, changed := juju.PrepareEndpointsForCaching( 1058 info, [][]network.HostPort{unsortedHPs}, network.HostPort{}, 1059 ) 1060 c.Assert(addrs, gc.IsNil) 1061 c.Assert(hosts, gc.IsNil) 1062 c.Assert(changed, jc.IsFalse) 1063 c.Assert(s.resolveNumCalls, gc.Equals, 1) 1064 c.Assert(s.numResolved, gc.Equals, 2) 1065 expectLog := fmt.Sprintf("DEBUG juju.api API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs) 1066 c.Assert(c.GetTestLog(), jc.Contains, expectLog) 1067 expectLog = "DEBUG juju.api API addresses unchanged" 1068 c.Assert(c.GetTestLog(), jc.Contains, expectLog) 1069 } 1070 1071 func (s *CacheAPIEndpointsSuite) TestResolveCalledWithInitialEndpoints(c *gc.C) { 1072 // Test that if no hostnames exist cached we call resolve (i.e. 1073 // simulate the behavior right after bootstrap) 1074 info := s.store.CreateInfo("env-name") 1075 // Because Hostnames are sorted before caching, reordering them 1076 // will simulate they have changed. 1077 unsortedHPs := network.NewHostPorts(1234, 1078 "ipv4.example.com", 1079 "8.8.8.8", 1080 "ipv6.example.com", 1081 "10.0.0.1", 1082 ) 1083 sortedHPs := network.NewHostPorts(1234, 1084 "8.8.8.8", 1085 "ipv4.example.com", 1086 "ipv6.example.com", 1087 "10.0.0.1", 1088 ) 1089 strSorted := network.HostPortsToStrings(sortedHPs) 1090 resolvedHPs := network.NewHostPorts(1234, 1091 "0.1.2.1", // from ipv4.example.com 1092 "8.8.8.8", 1093 "10.0.0.1", 1094 "fc00::2", // from ipv6.example.com 1095 ) 1096 strResolved := network.HostPortsToStrings(resolvedHPs) 1097 info.SetAPIEndpoint(configstore.APIEndpoint{}) 1098 err := info.Write() 1099 c.Assert(err, jc.ErrorIsNil) 1100 1101 addrs, hosts, changed := juju.PrepareEndpointsForCaching( 1102 info, [][]network.HostPort{unsortedHPs}, network.HostPort{}, 1103 ) 1104 c.Assert(addrs, jc.DeepEquals, strResolved) 1105 c.Assert(hosts, jc.DeepEquals, strSorted) 1106 c.Assert(changed, jc.IsTrue) 1107 c.Assert(s.resolveNumCalls, gc.Equals, 1) 1108 c.Assert(s.numResolved, gc.Equals, 2) 1109 expectLog := fmt.Sprintf("DEBUG juju.api API hostnames %v - resolving hostnames", sortedHPs) 1110 c.Assert(c.GetTestLog(), jc.Contains, expectLog) 1111 expectLog = fmt.Sprintf("INFO juju.api new API addresses to cache %v", resolvedHPs) 1112 c.Assert(c.GetTestLog(), jc.Contains, expectLog) 1113 } 1114 1115 func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6False(c *gc.C, info configstore.EnvironInfo) { 1116 c.Assert(s.resolveNumCalls, gc.Equals, 1) 1117 c.Assert(s.numResolved, gc.Equals, 10) 1118 endpoint := info.APIEndpoint() 1119 // Check Addresses after resolving. 1120 c.Check(endpoint.Addresses, jc.DeepEquals, []string{ 1121 s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. 1122 "0.1.2.1:1234", // From ipv4+4.example.com 1123 "0.1.2.2:1234", // From ipv4+4.example.com 1124 "0.1.2.3:1234", // From ipv4+6.example.com 1125 "0.1.2.5:1234", // From ipv4.example.com 1126 "0.1.2.6:1234", // From ipv6+4.example.com 1127 "1.0.0.1:1234", 1128 "1.0.0.2:1235", 1129 "192.0.0.1:1234", 1130 "[2001:db8::1]:1234", 1131 "[2001:db8::2]:1235", 1132 "localhost:1234", // Left intact on purpose. 1133 "localhost:1235", // Left intact on purpose. 1134 "[fc00::10]:1234", // From ipv6.example.com 1135 "[fc00::111]:1234", 1136 "[fc00::3]:1234", // From ipv4+6.example.com 1137 "[fc00::6]:1234", // From ipv6+4.example.com 1138 "[fc00::8]:1234", // From ipv6+6.example.com 1139 "[fc00::9]:1234", // From ipv6+6.example.com 1140 }) 1141 // Check Hostnames before resolving 1142 c.Check(endpoint.Hostnames, jc.DeepEquals, []string{ 1143 s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. 1144 "1.0.0.1:1234", 1145 "1.0.0.2:1235", 1146 "192.0.0.1:1234", 1147 "[2001:db8::1]:1234", 1148 "[2001:db8::2]:1235", 1149 "invalid host:1234", 1150 "ipv4+4.example.com:1234", 1151 "ipv4+6.example.com:1234", 1152 "ipv4.example.com:1234", 1153 "ipv6+4.example.com:1235", 1154 "ipv6+6.example.com:1234", 1155 "ipv6.example.com:1234", 1156 "localhost:1234", 1157 "localhost:1235", 1158 "[fc00::111]:1234", 1159 }) 1160 } 1161 1162 func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6True(c *gc.C, info configstore.EnvironInfo) { 1163 c.Assert(s.resolveNumCalls, gc.Equals, 1) 1164 c.Assert(s.numResolved, gc.Equals, 10) 1165 endpoint := info.APIEndpoint() 1166 // Check Addresses after resolving. 1167 c.Check(endpoint.Addresses, jc.DeepEquals, []string{ 1168 s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. 1169 "[2001:db8::1]:1234", 1170 "[2001:db8::2]:1235", 1171 "0.1.2.1:1234", // From ipv4+4.example.com 1172 "0.1.2.2:1234", // From ipv4+4.example.com 1173 "0.1.2.3:1234", // From ipv4+6.example.com 1174 "0.1.2.5:1234", // From ipv4.example.com 1175 "0.1.2.6:1234", // From ipv6+4.example.com 1176 "1.0.0.1:1234", 1177 "1.0.0.2:1235", 1178 "192.0.0.1:1234", 1179 "localhost:1234", // Left intact on purpose. 1180 "localhost:1235", // Left intact on purpose. 1181 "[fc00::10]:1234", // From ipv6.example.com 1182 "[fc00::111]:1234", 1183 "[fc00::3]:1234", // From ipv4+6.example.com 1184 "[fc00::6]:1234", // From ipv6+4.example.com 1185 "[fc00::8]:1234", // From ipv6+6.example.com 1186 "[fc00::9]:1234", // From ipv6+6.example.com 1187 }) 1188 // Check Hostnames before resolving 1189 c.Check(endpoint.Hostnames, jc.DeepEquals, []string{ 1190 s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. 1191 "[2001:db8::1]:1234", 1192 "[2001:db8::2]:1235", 1193 "1.0.0.1:1234", 1194 "1.0.0.2:1235", 1195 "192.0.0.1:1234", 1196 "invalid host:1234", 1197 "ipv4+4.example.com:1234", 1198 "ipv4+6.example.com:1234", 1199 "ipv4.example.com:1234", 1200 "ipv6+4.example.com:1235", 1201 "ipv6+6.example.com:1234", 1202 "ipv6.example.com:1234", 1203 "localhost:1234", 1204 "localhost:1235", 1205 "[fc00::111]:1234", 1206 }) 1207 } 1208 1209 func (s *CacheAPIEndpointsSuite) nextHostPorts(host string, types ...network.AddressType) []network.HostPort { 1210 result := make([]network.HostPort, len(types)) 1211 num4, num6 := 0, 0 1212 for i, tp := range types { 1213 addr := "" 1214 switch tp { 1215 case network.IPv4Address: 1216 addr = fmt.Sprintf("0.1.2.%d", s.resolveSeq+num4) 1217 num4++ 1218 case network.IPv6Address: 1219 addr = fmt.Sprintf("fc00::%d", s.resolveSeq+num6) 1220 num6++ 1221 } 1222 result[i] = network.NewHostPorts(1234, addr)[0] 1223 } 1224 s.resolveSeq += num4 + num6 1225 s.gocheckC.Logf("resolving %q as %v", host, result) 1226 return result 1227 } 1228 1229 func (s *CacheAPIEndpointsSuite) mockResolveOrDropHostnames(hps []network.HostPort) []network.HostPort { 1230 s.resolveNumCalls++ 1231 var result []network.HostPort 1232 for _, hp := range hps { 1233 if hp.Value == "invalid host" || hp.Scope == network.ScopeLinkLocal { 1234 // Simulate we dropped this. 1235 continue 1236 } else if hp.Value == "localhost" || hp.Type != network.HostName { 1237 // Leave localhost and IPs alone. 1238 result = append(result, hp) 1239 continue 1240 } 1241 var types []network.AddressType 1242 switch strings.TrimSuffix(hp.Value, ".example.com") { 1243 case "ipv4": 1244 // Simulate it resolves to an IPv4 address. 1245 types = append(types, network.IPv4Address) 1246 case "ipv6": 1247 // Simulate it resolves to an IPv6 address. 1248 types = append(types, network.IPv6Address) 1249 case "ipv4+6": 1250 // Simulate it resolves to both IPv4 and IPv6 addresses. 1251 types = append(types, network.IPv4Address, network.IPv6Address) 1252 case "ipv6+6": 1253 // Simulate it resolves to two IPv6 addresses. 1254 types = append(types, network.IPv6Address, network.IPv6Address) 1255 case "ipv4+4": 1256 // Simulate it resolves to two IPv4 addresses. 1257 types = append(types, network.IPv4Address, network.IPv4Address) 1258 case "ipv6+4": 1259 // Simulate it resolves to both IPv4 and IPv6 addresses. 1260 types = append(types, network.IPv6Address, network.IPv4Address) 1261 } 1262 result = append(result, s.nextHostPorts(hp.Value, types...)...) 1263 s.numResolved += len(types) 1264 } 1265 return result 1266 } 1267 1268 var fakeUUID = "df136476-12e9-11e4-8a70-b2227cce2b54" 1269 1270 var dummyStoreInfo = &environInfo{ 1271 creds: configstore.APICredentials{ 1272 User: "foo", 1273 Password: "foopass", 1274 }, 1275 endpoint: configstore.APIEndpoint{ 1276 Addresses: []string{"foo.invalid"}, 1277 CACert: "certificated", 1278 EnvironUUID: fakeUUID, 1279 }, 1280 } 1281 1282 type EnvironInfoTest struct { 1283 coretesting.BaseSuite 1284 } 1285 1286 var _ = gc.Suite(&EnvironInfoTest{}) 1287 1288 func (*EnvironInfoTest) TestNullInfo(c *gc.C) { 1289 c.Assert(juju.EnvironInfoUserTag(nil), gc.Equals, names.NewUserTag(configstore.DefaultAdminUsername)) 1290 } 1291 1292 type fakeEnvironInfo struct { 1293 configstore.EnvironInfo 1294 user string 1295 } 1296 1297 func (fake *fakeEnvironInfo) APICredentials() configstore.APICredentials { 1298 return configstore.APICredentials{User: fake.user} 1299 } 1300 1301 func (*EnvironInfoTest) TestEmptyUser(c *gc.C) { 1302 info := &fakeEnvironInfo{} 1303 c.Assert(juju.EnvironInfoUserTag(info), gc.Equals, names.NewUserTag(configstore.DefaultAdminUsername)) 1304 } 1305 1306 func (*EnvironInfoTest) TestRealUser(c *gc.C) { 1307 info := &fakeEnvironInfo{user: "eric"} 1308 c.Assert(juju.EnvironInfoUserTag(info), gc.Equals, names.NewUserTag("eric")) 1309 }