// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(store configstore.Storage, info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } else { // For backwards-compatibility, we have to allow connections // with an empty UUID. Login will work for the same reasons. logger.Warningf("ignoring invalid API endpoint environment UUID %v", endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// cacheChangedAPIInfo updates the local environment settings (.jenv file) // with the provided API server addresses if they have changed. It will also // save the environment tag if it is available. func cacheChangedAPIInfo(info configstore.EnvironInfo, hostPorts [][]network.HostPort, addrConnectedTo network.HostPort, environUUID, serverUUID string) error { addrs, hosts, addrsChanged := PrepareEndpointsForCaching(info, hostPorts, addrConnectedTo) logger.Debugf("cacheChangedAPIInfo: serverUUID=%q", serverUUID) endpoint := info.APIEndpoint() needCaching := false if endpoint.EnvironUUID != environUUID && environUUID != "" { endpoint.EnvironUUID = environUUID needCaching = true } if endpoint.ServerUUID != serverUUID && serverUUID != "" { endpoint.ServerUUID = serverUUID needCaching = true } if addrsChanged { endpoint.Addresses = addrs endpoint.Hostnames = hosts needCaching = true } if !needCaching { return nil } info.SetAPIEndpoint(endpoint) if err := info.Write(); err != nil { return err } logger.Infof("updated API connection settings cache - endpoints %v", endpoint.Addresses) return nil }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(info configstore.EnvironInfo, apiOpen api.OpenFunc, stop <-chan struct{}, bClient *httpbakery.Client) (api.Connection, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var modelTag names.ModelTag if names.IsValidModel(endpoint.ModelUUID) { modelTag = names.NewModelTag(endpoint.ModelUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, ModelTag: modelTag, } if apiInfo.Tag == nil { apiInfo.UseMacaroons = true } dialOpts := api.DefaultDialOpts() dialOpts.BakeryClient = bClient st, err := apiOpen(apiInfo, dialOpts) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(store configstore.Storage, info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var environTag names.Tag if endpoint.EnvironUUID != "" { // Note: we should be validating that EnvironUUID contains a // valid UUID. environTag = names.NewEnvironTag(endpoint.EnvironUUID) } username := info.APICredentials().User if username == "" { username = "******" } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: names.NewUserTag(username), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6True(c *gc.C, info configstore.EnvironInfo) { c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 10) endpoint := info.APIEndpoint() // Check Addresses after resolving. c.Check(endpoint.Addresses, jc.DeepEquals, []string{ s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. "[2001:db8::1]:1234", "[2001:db8::2]:1235", "0.1.2.1:1234", // From ipv4+4.example.com "0.1.2.2:1234", // From ipv4+4.example.com "0.1.2.3:1234", // From ipv4+6.example.com "0.1.2.5:1234", // From ipv4.example.com "0.1.2.6:1234", // From ipv6+4.example.com "1.0.0.1:1234", "1.0.0.2:1235", "192.0.0.1:1234", "localhost:1234", // Left intact on purpose. "localhost:1235", // Left intact on purpose. "[fc00::10]:1234", // From ipv6.example.com "[fc00::111]:1234", "[fc00::3]:1234", // From ipv4+6.example.com "[fc00::6]:1234", // From ipv6+4.example.com "[fc00::8]:1234", // From ipv6+6.example.com "[fc00::9]:1234", // From ipv6+6.example.com }) // Check Hostnames before resolving c.Check(endpoint.Hostnames, jc.DeepEquals, []string{ s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. "[2001:db8::1]:1234", "[2001:db8::2]:1235", "1.0.0.1:1234", "1.0.0.2:1235", "192.0.0.1:1234", "invalid host:1234", "ipv4+4.example.com:1234", "ipv4+6.example.com:1234", "ipv4.example.com:1234", "ipv6+4.example.com:1235", "ipv6+6.example.com:1234", "ipv6.example.com:1234", "localhost:1234", "localhost:1235", "[fc00::111]:1234", }) }
// cacheChangedAPIInfo updates the local environment settings (.jenv file) // with the provided API server addresses if they have changed. It will also // save the environment tag if it is available. func cacheChangedAPIInfo(info configstore.EnvironInfo, hostPorts [][]network.HostPort, newEnvironTag string) error { var addrs []string for _, serverHostPorts := range hostPorts { for _, hostPort := range serverHostPorts { // Only cache addresses that are likely to be usable, // exclude localhost style ones. if hostPort.Scope != network.ScopeMachineLocal && hostPort.Scope != network.ScopeLinkLocal { addrs = append(addrs, hostPort.NetAddr()) } } } endpoint := info.APIEndpoint() changed := false if newEnvironTag != "" { tag, err := names.ParseEnvironTag(newEnvironTag) if err == nil { if environUUID := tag.Id(); endpoint.EnvironUUID != environUUID { changed = true endpoint.EnvironUUID = environUUID } } else { logger.Debugf("cannot parse environ tag: %v", err) } } if len(addrs) != 0 && addrsChanged(endpoint.Addresses, addrs) { logger.Debugf("API addresses changed from %q to %q", endpoint.Addresses, addrs) changed = true endpoint.Addresses = addrs } if !changed { return nil } info.SetAPIEndpoint(endpoint) if err := info.Write(); err != nil { return err } logger.Infof("updated API connection settings cache") return nil }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{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 }