// PrepareEndpointsForCaching performs the necessary operations on the // given API hostPorts so they are suitable for saving into the // controller.yaml file, taking into account the addrConnectedTo // and the existing config store info: // // 1. Collapses hostPorts into a single slice. // 2. Filters out machine-local and link-local addresses. // 3. Removes any duplicates // 4. Call network.SortHostPorts() on the list. // 5. Puts the addrConnectedTo on top. // 6. Compares the result against info.APIEndpoint.Hostnames. // 7. If the addresses differ, call network.ResolveOrDropHostnames() // on the list and perform all steps again from step 1. // 8. Compare the list of resolved addresses against the cached info // APIEndpoint.Addresses, and if changed return both addresses and // hostnames as strings (so they can be cached on APIEndpoint) and // set haveChanged to true. // 9. If the hostnames haven't changed, return two empty slices and set // haveChanged to false. No DNS resolution is performed to save time. // // This is used right after bootstrap to saved the initial API // endpoints, as well as on each CLI connection to verify if the // saved endpoints need updating. // // TODO(rogpeppe) this function mixes too many concerns - the // logic is difficult to follow and has non-obvious properties. func PrepareEndpointsForCaching( controllerDetails jujuclient.ControllerDetails, hostPorts [][]network.HostPort, addrConnectedTo ...network.HostPort, ) (addrs, unresolvedAddrs []string, haveChanged bool) { processHostPorts := func(allHostPorts [][]network.HostPort) []network.HostPort { uniqueHPs := usableHostPorts(allHostPorts) network.SortHostPorts(uniqueHPs) for _, addr := range addrConnectedTo { uniqueHPs = network.EnsureFirstHostPort(addr, uniqueHPs) } return uniqueHPs } apiHosts := processHostPorts(hostPorts) hostsStrings := network.HostPortsToStrings(apiHosts) needResolving := false // Verify if the unresolved addresses have changed. if len(apiHosts) > 0 && len(controllerDetails.UnresolvedAPIEndpoints) > 0 { if addrsChanged(hostsStrings, controllerDetails.UnresolvedAPIEndpoints) { logger.Debugf( "API hostnames changed from %v to %v - resolving hostnames", controllerDetails.UnresolvedAPIEndpoints, hostsStrings, ) needResolving = true } } else if len(apiHosts) > 0 { // No cached hostnames, most likely right after bootstrap. logger.Debugf("API hostnames %v - resolving hostnames", hostsStrings) needResolving = true } if !needResolving { // We're done - nothing changed. logger.Debugf("API hostnames unchanged - not resolving") return nil, nil, false } // Perform DNS resolution and check against APIEndpoints.Addresses. resolved := resolveOrDropHostnames(apiHosts) apiAddrs := processHostPorts([][]network.HostPort{resolved}) addrsStrings := network.HostPortsToStrings(apiAddrs) if len(apiAddrs) > 0 && len(controllerDetails.APIEndpoints) > 0 { if addrsChanged(addrsStrings, controllerDetails.APIEndpoints) { logger.Infof( "API addresses changed from %v to %v", controllerDetails.APIEndpoints, addrsStrings, ) return addrsStrings, hostsStrings, true } } else if len(apiAddrs) > 0 { // No cached addresses, most likely right after bootstrap. logger.Infof("new API addresses to cache %v", addrsStrings) return addrsStrings, hostsStrings, true } // No changes. logger.Debugf("API addresses unchanged") return nil, nil, false }
func (s *CacheAPIEndpointsSuite) TestAfterResolvingUnchangedAddressesNotCached(c *gc.C) { // Test that if new endpoints hostnames are different than the // cached hostnames, but after resolving the addresses match the // cached addresses, the cache is not changed. // Because Hostnames are sorted before caching, reordering them // will simulate they have changed. unsortedHPs := network.NewHostPorts(1234, "ipv4.example.com", "8.8.8.8", "ipv6.example.com", "10.0.0.1", ) strUnsorted := network.HostPortsToStrings(unsortedHPs) sortedHPs := network.NewHostPorts(1234, "8.8.8.8", "ipv4.example.com", "ipv6.example.com", "10.0.0.1", ) resolvedHPs := network.NewHostPorts(1234, "0.1.2.1", // from ipv4.example.com "8.8.8.8", "10.0.0.1", "fc00::2", // from ipv6.example.com ) strResolved := network.HostPortsToStrings(resolvedHPs) controllerDetails := jujuclient.ControllerDetails{ ControllerUUID: fakeUUID, CACert: "certificate", UnresolvedAPIEndpoints: strUnsorted, APIEndpoints: strResolved, } err := s.ControllerStore.AddController("controller-name", controllerDetails) c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( controllerDetails, [][]network.HostPort{unsortedHPs}, ) c.Assert(addrs, gc.IsNil) c.Assert(hosts, gc.IsNil) c.Assert(changed, jc.IsFalse) c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 2) expectLog := fmt.Sprintf("DEBUG juju.juju API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) expectLog = "DEBUG juju.juju API addresses unchanged" c.Assert(c.GetTestLog(), jc.Contains, expectLog) }
func (s *CacheAPIEndpointsSuite) TestResolveCalledWithChangedHostnames(c *gc.C) { // Test that if new endpoints hostnames are different than the // cached hostnames DNS resolution happens and we compare resolved // addresses. // Because Hostnames are sorted before caching, reordering them // will simulate they have changed. unsortedHPs := network.NewHostPorts(1234, "ipv4.example.com", "8.8.8.8", "ipv6.example.com", "10.0.0.1", ) strUnsorted := network.HostPortsToStrings(unsortedHPs) sortedHPs := network.NewHostPorts(1234, "8.8.8.8", "ipv4.example.com", "ipv6.example.com", "10.0.0.1", ) strSorted := network.HostPortsToStrings(sortedHPs) resolvedHPs := network.NewHostPorts(1234, "0.1.2.1", // from ipv4.example.com "8.8.8.8", "10.0.0.1", "fc00::2", // from ipv6.example.com ) strResolved := network.HostPortsToStrings(resolvedHPs) controllerDetails := jujuclient.ControllerDetails{ ControllerUUID: fakeUUID, CACert: "certificate", UnresolvedAPIEndpoints: strUnsorted, } err := s.ControllerStore.AddController("controller-name", controllerDetails) c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( controllerDetails, [][]network.HostPort{unsortedHPs}, ) c.Assert(addrs, jc.DeepEquals, strResolved) c.Assert(hosts, jc.DeepEquals, strSorted) c.Assert(changed, jc.IsTrue) c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 2) expectLog := fmt.Sprintf("DEBUG juju.juju API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) expectLog = fmt.Sprintf("INFO juju.juju new API addresses to cache %v", resolvedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) }
// APIInfo returns an api.Info for the environment. The result is populated // with addresses and CA certificate, but no tag or password. func APIInfo(env Environ) (*api.Info, error) { instanceIds, err := env.ControllerInstances() if err != nil { return nil, err } logger.Debugf("ControllerInstances returned: %v", instanceIds) addrs, err := waitAnyInstanceAddresses(env, instanceIds) if err != nil { return nil, err } config := env.Config() cert, hasCert := config.CACert() if !hasCert { return nil, errors.New("config has no CACert") } apiPort := config.APIPort() apiAddrs := network.HostPortsToStrings( network.AddressesWithPort(addrs, apiPort), ) uuid, uuidSet := config.UUID() if !uuidSet { return nil, errors.New("config has no UUID") } modelTag := names.NewModelTag(uuid) apiInfo := &api.Info{Addrs: apiAddrs, CACert: cert, ModelTag: modelTag} return apiInfo, nil }
func (s *HostPortSuite) TestHostPortsToStrings(c *gc.C) { hps := s.makeHostPorts() strHPs := network.HostPortsToStrings(hps) c.Assert(strHPs, gc.HasLen, len(hps)) c.Assert(strHPs, jc.DeepEquals, []string{ "127.0.0.1:1234", "localhost:1234", "example.com:1234", "127.0.1.1:1234", "example.org:1234", "[2001:db8::2]:1234", "169.254.1.1:1234", "example.net:1234", "invalid host:1234", "[fd00::22]:1234", "127.0.0.1:1234", "[2001:db8::1]:1234", "169.254.1.2:1234", "[ff01::22]:1234", "0.1.2.0:1234", "[2001:db8::1]:1234", "localhost:1234", "10.0.0.1:1234", "[::1]:1234", "[fc00::1]:1234", "[fe80::2]:1234", "172.16.0.1:1234", "[::1]:1234", "8.8.8.8:1234", "7.8.8.8:1234", }) }
func (s *CacheAPIEndpointsSuite) TestResolveSkippedWhenHostnamesUnchanged(c *gc.C) { // Test that if new endpoints hostnames are the same as the // cached, no DNS resolution happens (i.e. we don't resolve on // every connection, but as needed). info := s.store.CreateInfo("env-name") hps := network.NewHostPorts(1234, "8.8.8.8", "example.com", "10.0.0.1", ) info.SetAPIEndpoint(configstore.APIEndpoint{ Hostnames: network.HostPortsToStrings(hps), }) err := info.Write() c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( info, [][]network.HostPort{hps}, network.HostPort{}, ) c.Assert(addrs, gc.IsNil) c.Assert(hosts, gc.IsNil) c.Assert(changed, jc.IsFalse) c.Assert(s.resolveNumCalls, gc.Equals, 0) c.Assert( c.GetTestLog(), jc.Contains, "DEBUG juju.api API hostnames unchanged - not resolving", ) }
func (s *CacheAPIEndpointsSuite) TestResolveSkippedWhenHostnamesUnchanged(c *gc.C) { // Test that if new endpoints hostnames are the same as the // cached, no DNS resolution happens (i.e. we don't resolve on // every connection, but as needed). hps := network.NewHostPorts(1234, "8.8.8.8", "example.com", "10.0.0.1", ) controllerDetails := jujuclient.ControllerDetails{ ControllerUUID: fakeUUID, CACert: "certificate", UnresolvedAPIEndpoints: network.HostPortsToStrings(hps), } err := s.ControllerStore.AddController("controller-name", controllerDetails) c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( controllerDetails, [][]network.HostPort{hps}, ) c.Assert(addrs, gc.IsNil) c.Assert(hosts, gc.IsNil) c.Assert(changed, jc.IsFalse) c.Assert(s.resolveNumCalls, gc.Equals, 0) c.Assert( c.GetTestLog(), jc.Contains, "DEBUG juju.juju API hostnames unchanged - not resolving", ) }
// APIInfo returns an api.Info for the environment. The result is populated // with addresses and CA certificate, but no tag or password. func APIInfo(env Environ) (*api.Info, error) { instanceIds, err := env.ControllerInstances() if err != nil { return nil, err } logger.Debugf("ControllerInstances returned: %v", instanceIds) addrs, err := waitAnyInstanceAddresses(env, instanceIds) if err != nil { return nil, err } defaultSpaceAddr, ok := network.SelectAddressBySpace(addrs, network.DefaultSpace) if ok { addrs = []network.Address{defaultSpaceAddr} logger.Debugf("selected %q as API address in space %q", defaultSpaceAddr.Value, network.DefaultSpace) } else { logger.Warningf("using all API addresses (cannot pick by space %q): %+v", network.DefaultSpace, addrs) } config := env.Config() cert, hasCert := config.CACert() if !hasCert { return nil, errors.New("config has no CACert") } apiPort := config.APIPort() apiAddrs := network.HostPortsToStrings( network.AddressesWithPort(addrs, apiPort), ) uuid, uuidSet := config.UUID() if !uuidSet { return nil, errors.New("config has no UUID") } modelTag := names.NewModelTag(uuid) apiInfo := &api.Info{Addrs: apiAddrs, CACert: cert, ModelTag: modelTag} return apiInfo, nil }
func (s *CacheAPIEndpointsSuite) TestResolveCalledWithInitialEndpoints(c *gc.C) { // Test that if no hostnames exist cached we call resolve (i.e. // simulate the behavior right after bootstrap) // Because Hostnames are sorted before caching, reordering them // will simulate they have changed. unsortedHPs := network.NewHostPorts(1234, "ipv4.example.com", "8.8.8.8", "ipv6.example.com", "10.0.0.1", ) sortedHPs := network.NewHostPorts(1234, "8.8.8.8", "ipv4.example.com", "ipv6.example.com", "10.0.0.1", ) strSorted := network.HostPortsToStrings(sortedHPs) resolvedHPs := network.NewHostPorts(1234, "0.1.2.1", // from ipv4.example.com "8.8.8.8", "10.0.0.1", "fc00::2", // from ipv6.example.com ) strResolved := network.HostPortsToStrings(resolvedHPs) controllerDetails := jujuclient.ControllerDetails{ ControllerUUID: fakeUUID, CACert: "certificate", } err := s.ControllerStore.AddController("controller-name", controllerDetails) c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( controllerDetails, [][]network.HostPort{unsortedHPs}, ) c.Assert(addrs, jc.DeepEquals, strResolved) c.Assert(hosts, jc.DeepEquals, strSorted) c.Assert(changed, jc.IsTrue) c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 2) expectLog := fmt.Sprintf("DEBUG juju.juju API hostnames %v - resolving hostnames", sortedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) expectLog = fmt.Sprintf("INFO juju.juju new API addresses to cache %v", resolvedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) }
func (s *CacheAPIEndpointsSuite) TestResolveCalledWithChangedHostnames(c *gc.C) { // Test that if new endpoints hostnames are different than the // cached hostnames DNS resolution happens and we compare resolved // addresses. info := s.store.CreateInfo("env-name") // Because Hostnames are sorted before caching, reordering them // will simulate they have changed. unsortedHPs := network.NewHostPorts(1234, "ipv4.example.com", "8.8.8.8", "ipv6.example.com", "10.0.0.1", ) strUnsorted := network.HostPortsToStrings(unsortedHPs) sortedHPs := network.NewHostPorts(1234, "8.8.8.8", "ipv4.example.com", "ipv6.example.com", "10.0.0.1", ) strSorted := network.HostPortsToStrings(sortedHPs) resolvedHPs := network.NewHostPorts(1234, "0.1.2.1", // from ipv4.example.com "8.8.8.8", "10.0.0.1", "fc00::2", // from ipv6.example.com ) strResolved := network.HostPortsToStrings(resolvedHPs) info.SetAPIEndpoint(configstore.APIEndpoint{ Hostnames: strUnsorted, }) err := info.Write() c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( info, [][]network.HostPort{unsortedHPs}, network.HostPort{}, ) c.Assert(addrs, jc.DeepEquals, strResolved) c.Assert(hosts, jc.DeepEquals, strSorted) c.Assert(changed, jc.IsTrue) c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 2) expectLog := fmt.Sprintf("DEBUG juju.api API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) expectLog = fmt.Sprintf("INFO juju.api new API addresses to cache %v", resolvedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) }
func (s *CacheAPIEndpointsSuite) TestAfterResolvingUnchangedAddressesNotCached(c *gc.C) { // Test that if new endpoints hostnames are different than the // cached hostnames, but after resolving the addresses match the // cached addresses, the cache is not changed. info := s.store.CreateInfo("env-name") // Because Hostnames are sorted before caching, reordering them // will simulate they have changed. unsortedHPs := network.NewHostPorts(1234, "ipv4.example.com", "8.8.8.8", "ipv6.example.com", "10.0.0.1", ) strUnsorted := network.HostPortsToStrings(unsortedHPs) sortedHPs := network.NewHostPorts(1234, "8.8.8.8", "ipv4.example.com", "ipv6.example.com", "10.0.0.1", ) resolvedHPs := network.NewHostPorts(1234, "0.1.2.1", // from ipv4.example.com "8.8.8.8", "10.0.0.1", "fc00::2", // from ipv6.example.com ) strResolved := network.HostPortsToStrings(resolvedHPs) info.SetAPIEndpoint(configstore.APIEndpoint{ Hostnames: strUnsorted, Addresses: strResolved, }) err := info.Write() c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( info, [][]network.HostPort{unsortedHPs}, network.HostPort{}, ) c.Assert(addrs, gc.IsNil) c.Assert(hosts, gc.IsNil) c.Assert(changed, jc.IsFalse) c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 2) expectLog := fmt.Sprintf("DEBUG juju.api API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) expectLog = "DEBUG juju.api API addresses unchanged" c.Assert(c.GetTestLog(), jc.Contains, expectLog) }
// setCachedAPIAddresses sets the given addresses on the cached // EnvironInfo endpoint. APIEndpoint.Hostnames are not touched, // because the interactions between Addresses and Hostnames are // separately tested in juju/api_test.go func (s *EndpointSuite) setCachedAPIAddresses(c *gc.C, addresses ...network.HostPort) { info := s.getStoreInfo(c) endpoint := info.APIEndpoint() endpoint.Addresses = network.HostPortsToStrings(addresses) info.SetAPIEndpoint(endpoint) err := info.Write() c.Assert(err, jc.ErrorIsNil) c.Logf("cached addresses set to %v", info.APIEndpoint().Addresses) }
func (s *CacheAPIEndpointsSuite) TestResolveCalledWithInitialEndpoints(c *gc.C) { // Test that if no hostnames exist cached we call resolve (i.e. // simulate the behavior right after bootstrap) info := s.store.CreateInfo("env-name") // Because Hostnames are sorted before caching, reordering them // will simulate they have changed. unsortedHPs := network.NewHostPorts(1234, "ipv4.example.com", "8.8.8.8", "ipv6.example.com", "10.0.0.1", ) sortedHPs := network.NewHostPorts(1234, "8.8.8.8", "ipv4.example.com", "ipv6.example.com", "10.0.0.1", ) strSorted := network.HostPortsToStrings(sortedHPs) resolvedHPs := network.NewHostPorts(1234, "0.1.2.1", // from ipv4.example.com "8.8.8.8", "10.0.0.1", "fc00::2", // from ipv6.example.com ) strResolved := network.HostPortsToStrings(resolvedHPs) info.SetAPIEndpoint(configstore.APIEndpoint{}) err := info.Write() c.Assert(err, jc.ErrorIsNil) addrs, hosts, changed := juju.PrepareEndpointsForCaching( info, [][]network.HostPort{unsortedHPs}, network.HostPort{}, ) c.Assert(addrs, jc.DeepEquals, strResolved) c.Assert(hosts, jc.DeepEquals, strSorted) c.Assert(changed, jc.IsTrue) c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 2) expectLog := fmt.Sprintf("DEBUG juju.api API hostnames %v - resolving hostnames", sortedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) expectLog = fmt.Sprintf("INFO juju.api new API addresses to cache %v", resolvedHPs) c.Assert(c.GetTestLog(), jc.Contains, expectLog) }
// APIInfo returns an api.Info for the environment. The result is populated // with addresses and CA certificate, but no tag or password. func APIInfo(controllerUUID, modelUUID, caCert string, apiPort int, env Environ) (*api.Info, error) { instanceIds, err := env.ControllerInstances(controllerUUID) if err != nil { return nil, err } logger.Debugf("ControllerInstances returned: %v", instanceIds) addrs, err := waitAnyInstanceAddresses(env, instanceIds) if err != nil { return nil, err } apiAddrs := network.HostPortsToStrings( network.AddressesWithPort(addrs, apiPort), ) modelTag := names.NewModelTag(modelUUID) apiInfo := &api.Info{Addrs: apiAddrs, CACert: caCert, ModelTag: modelTag} return apiInfo, nil }
func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6False(c *gc.C) { info := s.store.CreateInfo("env-name1") s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool { return false }) // First test cacheChangedAPIInfo behaves as expected. err := juju.CacheChangedAPIInfo(info, s.hostPorts, s.apiHostPort, s.envTag.Id(), "") c.Assert(err, jc.ErrorIsNil) s.assertEndpointsPreferIPv6False(c, info) // Now test cacheAPIInfo behaves the same way. s.resolveSeq = 1 s.resolveNumCalls = 0 s.numResolved = 0 info = s.store.CreateInfo("env-name2") mockAPIInfo := s.APIInfo(c) mockAPIInfo.EnvironTag = s.envTag hps := network.CollapseHostPorts(s.hostPorts) mockAPIInfo.Addrs = network.HostPortsToStrings(hps) err = juju.CacheAPIInfo(s.APIState, info, mockAPIInfo) c.Assert(err, jc.ErrorIsNil) s.assertEndpointsPreferIPv6False(c, info) }
// NewAPIConnection returns an api.Connection to the specified Juju controller, // with specified account credentials, optionally scoped to the specified model // name. func NewAPIConnection(args NewAPIConnectionParams) (api.Connection, error) { apiInfo, controller, err := connectionInfo(args) if err != nil { return nil, errors.Annotatef(err, "cannot work out how to connect") } if len(apiInfo.Addrs) == 0 { return nil, errors.New("no API addresses") } logger.Infof("connecting to API addresses: %v", apiInfo.Addrs) st, err := args.OpenAPI(apiInfo, args.DialOpts) if err != nil { redirErr, ok := errors.Cause(err).(*api.RedirectError) if !ok { return nil, errors.Trace(err) } // We've been told to connect to a different API server, // so do so. Note that we don't copy the account details // because the account on the redirected server may well // be different - we'll use macaroon authentication // directly without sending account details. // Copy the API info because it's possible that the // apiConfigConnect is still using it concurrently. apiInfo = &api.Info{ ModelTag: apiInfo.ModelTag, Addrs: network.HostPortsToStrings(usableHostPorts(redirErr.Servers)), CACert: redirErr.CACert, } st, err = args.OpenAPI(apiInfo, args.DialOpts) if err != nil { return nil, errors.Annotatef(err, "cannot connect to redirected address") } // TODO(rog) update cached model addresses. // TODO(rog) should we do something with the logged-in username? return st, nil } addrConnectedTo, err := serverAddress(st.Addr()) if err != nil { return nil, errors.Trace(err) } // Update API addresses if they've changed. Error is non-fatal. // Note that in the redirection case, we won't update the addresses // of the controller we first connected to. This shouldn't be // a problem in practice because the intended scenario for // controllers that redirect involves them having well known // public addresses that won't change over time. hostPorts := st.APIHostPorts() agentVersion := "" if v, ok := st.ServerVersion(); ok { agentVersion = v.String() } params := UpdateControllerParams{ AgentVersion: agentVersion, AddrConnectedTo: []network.HostPort{addrConnectedTo}, CurrentHostPorts: hostPorts, } err = updateControllerDetailsFromLogin(args.Store, args.ControllerName, controller, params) if err != nil { logger.Errorf("cannot cache API addresses: %v", err) } // Process the account details obtained from login. var accountDetails *jujuclient.AccountDetails user, ok := st.AuthTag().(names.UserTag) if !apiInfo.SkipLogin { if ok { if accountDetails, err = args.Store.AccountDetails(args.ControllerName); err != nil { if !errors.IsNotFound(err) { logger.Errorf("cannot load local account information: %v", err) } } else { accountDetails.LastKnownAccess = st.ControllerAccess() } } if ok && !user.IsLocal() && apiInfo.Tag == nil { // We used macaroon auth to login; save the username // that we've logged in as. accountDetails = &jujuclient.AccountDetails{ User: user.Canonical(), LastKnownAccess: st.ControllerAccess(), } } else if apiInfo.Tag == nil { logger.Errorf("unexpected logged-in username %v", st.AuthTag()) } } if accountDetails != nil { if err := args.Store.UpdateAccount(args.ControllerName, *accountDetails); err != nil { logger.Errorf("cannot update account information: %v", err) } } return st, nil }
// PrepareEndpointsForCaching performs the necessary operations on the // given API hostPorts so they are suitable for caching into the // environment's .jenv file, taking into account the addrConnectedTo // and the existing config store info: // // 1. Collapses hostPorts into a single slice. // 2. Filters out machine-local and link-local addresses. // 3. Removes any duplicates // 4. Call network.SortHostPorts() on the list, respecing prefer-ipv6 // flag. // 5. Puts the addrConnectedTo on top. // 6. Compares the result against info.APIEndpoint.Hostnames. // 7. If the addresses differ, call network.ResolveOrDropHostnames() // on the list and perform all steps again from step 1. // 8. Compare the list of resolved addresses against the cached info // APIEndpoint.Addresses, and if changed return both addresses and // hostnames as strings (so they can be cached on APIEndpoint) and // set haveChanged to true. // 9. If the hostnames haven't changed, return two empty slices and set // haveChanged to false. No DNS resolution is performed to save time. // // This is used right after bootstrap to cache the initial API // endpoints, as well as on each CLI connection to verify if the // cached endpoints need updating. func PrepareEndpointsForCaching(info configstore.EnvironInfo, hostPorts [][]network.HostPort, addrConnectedTo network.HostPort) (addresses, hostnames []string, haveChanged bool) { processHostPorts := func(allHostPorts [][]network.HostPort) []network.HostPort { collapsedHPs := network.CollapseHostPorts(allHostPorts) filteredHPs := network.FilterUnusableHostPorts(collapsedHPs) uniqueHPs := network.DropDuplicatedHostPorts(filteredHPs) // Sort the result to prefer public IPs on top (when prefer-ipv6 // is true, IPv6 addresses of the same scope will come before IPv4 // ones). preferIPv6 := maybePreferIPv6(info) network.SortHostPorts(uniqueHPs, preferIPv6) if addrConnectedTo.Value != "" { return network.EnsureFirstHostPort(addrConnectedTo, uniqueHPs) } // addrConnectedTo can be empty only right after bootstrap. return uniqueHPs } apiHosts := processHostPorts(hostPorts) hostsStrings := network.HostPortsToStrings(apiHosts) endpoint := info.APIEndpoint() needResolving := false // Verify if the unresolved addresses have changed. if len(apiHosts) > 0 && len(endpoint.Hostnames) > 0 { if addrsChanged(hostsStrings, endpoint.Hostnames) { logger.Debugf( "API hostnames changed from %v to %v - resolving hostnames", endpoint.Hostnames, hostsStrings, ) needResolving = true } } else if len(apiHosts) > 0 { // No cached hostnames, most likely right after bootstrap. logger.Debugf("API hostnames %v - resolving hostnames", hostsStrings) needResolving = true } if !needResolving { // We're done - nothing changed. logger.Debugf("API hostnames unchanged - not resolving") return nil, nil, false } // Perform DNS resolution and check against APIEndpoints.Addresses. resolved := resolveOrDropHostnames(apiHosts) apiAddrs := processHostPorts([][]network.HostPort{resolved}) addrsStrings := network.HostPortsToStrings(apiAddrs) if len(apiAddrs) > 0 && len(endpoint.Addresses) > 0 { if addrsChanged(addrsStrings, endpoint.Addresses) { logger.Infof( "API addresses changed from %v to %v", endpoint.Addresses, addrsStrings, ) return addrsStrings, hostsStrings, true } } else if len(apiAddrs) > 0 { // No cached addresses, most likely right after bootstrap. logger.Infof("new API addresses to cache %v", addrsStrings) return addrsStrings, hostsStrings, true } // No changes. logger.Debugf("API addresses unchanged") return nil, nil, false }
// assertCachedAddresses ensures the endpoint addresses (not // hostnames) stored in the store match the given ones. // APIEndpoint.Hostnames and APIEndpoint.Addresses interactions are // separately testing in juju/api_test.go. func (s *EndpointSuite) assertCachedAddresses(c *gc.C, addresses ...network.HostPort) { info := s.getStoreInfo(c) strAddresses := network.HostPortsToStrings(addresses) c.Assert(info.APIEndpoint().Addresses, jc.DeepEquals, strAddresses) }