func (*HostPortSuite) TestCollapseHostPorts(c *gc.C) { servers := [][]network.HostPort{ network.NewHostPorts(1234, "0.1.2.3", "10.0.1.2", "fc00::1", "2001:db8::1", "::1", "127.0.0.1", "localhost", "fe80::123", "example.com", ), network.NewHostPorts(4321, "8.8.8.8", "1.2.3.4", "fc00::2", "127.0.0.1", "foo", ), network.NewHostPorts(9999, "localhost", "127.0.0.1", ), } expected := append(servers[0], append(servers[1], servers[2]...)...) result := network.CollapseHostPorts(servers) c.Assert(result, gc.HasLen, len(servers[0])+len(servers[1])+len(servers[2])) c.Assert(result, jc.DeepEquals, expected) }
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) }
// usableHostPorts returns hps with unusable and non-unique // host-ports filtered out. func usableHostPorts(hps [][]network.HostPort) []network.HostPort { collapsed := network.CollapseHostPorts(hps) usable := network.FilterUnusableHostPorts(collapsed) unique := network.DropDuplicatedHostPorts(usable) return unique }
// 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 }
// 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. func PrepareEndpointsForCaching( controllerDetails jujuclient.ControllerDetails, 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) network.SortHostPorts(uniqueHPs, false) 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 }