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