vitess.io/vitess@v0.16.2/go/vt/vtorc/inst/resolve.go (about)

     1  /*
     2     Copyright 2014 Outbrain Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package inst
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/patrickmn/go-cache"
    28  
    29  	"vitess.io/vitess/go/vt/log"
    30  	"vitess.io/vitess/go/vt/vtorc/config"
    31  )
    32  
    33  type HostnameResolve struct {
    34  	hostname         string
    35  	resolvedHostname string
    36  }
    37  
    38  func (hostnameResolve HostnameResolve) String() string {
    39  	return fmt.Sprintf("%s %s", hostnameResolve.hostname, hostnameResolve.resolvedHostname)
    40  }
    41  
    42  type HostnameUnresolve struct {
    43  	hostname           string
    44  	unresolvedHostname string
    45  }
    46  
    47  func (hostnameUnresolve HostnameUnresolve) String() string {
    48  	return fmt.Sprintf("%s %s", hostnameUnresolve.hostname, hostnameUnresolve.unresolvedHostname)
    49  }
    50  
    51  type HostnameRegistration struct {
    52  	CreatedAt time.Time
    53  	Key       InstanceKey
    54  	Hostname  string
    55  }
    56  
    57  func NewHostnameRegistration(instanceKey *InstanceKey, hostname string) *HostnameRegistration {
    58  	return &HostnameRegistration{
    59  		CreatedAt: time.Now(),
    60  		Key:       *instanceKey,
    61  		Hostname:  hostname,
    62  	}
    63  }
    64  
    65  func NewHostnameDeregistration(instanceKey *InstanceKey) *HostnameRegistration {
    66  	return &HostnameRegistration{
    67  		CreatedAt: time.Now(),
    68  		Key:       *instanceKey,
    69  		Hostname:  "",
    70  	}
    71  }
    72  
    73  var hostnameResolvesLightweightCache *cache.Cache
    74  var hostnameResolvesLightweightCacheInit = &sync.Mutex{}
    75  var hostnameResolvesLightweightCacheLoadedOnceFromDB = false
    76  var hostnameIPsCache = cache.New(10*time.Minute, time.Minute)
    77  
    78  func getHostnameResolvesLightweightCache() *cache.Cache {
    79  	hostnameResolvesLightweightCacheInit.Lock()
    80  	defer hostnameResolvesLightweightCacheInit.Unlock()
    81  	if hostnameResolvesLightweightCache == nil {
    82  		hostnameResolvesLightweightCache = cache.New(time.Duration(config.ExpiryHostnameResolvesMinutes)*time.Minute, time.Minute)
    83  	}
    84  	return hostnameResolvesLightweightCache
    85  }
    86  
    87  func HostnameResolveMethodIsNone() bool {
    88  	return strings.ToLower(config.HostnameResolveMethod) == "none"
    89  }
    90  
    91  // GetCNAME resolves an IP or hostname into a normalized valid CNAME
    92  func GetCNAME(hostname string) (string, error) {
    93  	res, err := net.LookupCNAME(hostname)
    94  	if err != nil {
    95  		return hostname, err
    96  	}
    97  	res = strings.TrimRight(res, ".")
    98  	return res, nil
    99  }
   100  
   101  func resolveHostname(hostname string) (string, error) {
   102  	switch strings.ToLower(config.HostnameResolveMethod) {
   103  	case "none":
   104  		return hostname, nil
   105  	case "default":
   106  		return hostname, nil
   107  	case "cname":
   108  		return GetCNAME(hostname)
   109  	case "ip":
   110  		return getHostnameIP(hostname)
   111  	}
   112  	return hostname, nil
   113  }
   114  
   115  // Attempt to resolve a hostname. This may return a database cached hostname or otherwise
   116  // it may resolve the hostname via CNAME
   117  func ResolveHostname(hostname string) (string, error) {
   118  	hostname = strings.TrimSpace(hostname)
   119  	if hostname == "" {
   120  		return hostname, errors.New("Will not resolve empty hostname")
   121  	}
   122  	if strings.Contains(hostname, ",") {
   123  		return hostname, fmt.Errorf("Will not resolve multi-hostname: %+v", hostname)
   124  	}
   125  	if (&InstanceKey{Hostname: hostname}).IsDetached() {
   126  		// quietly abort. Nothing to do. The hostname is detached for a reason: it
   127  		// will not be resolved, for sure.
   128  		return hostname, nil
   129  	}
   130  
   131  	// First go to lightweight cache
   132  	if resolvedHostname, found := getHostnameResolvesLightweightCache().Get(hostname); found {
   133  		return resolvedHostname.(string), nil
   134  	}
   135  
   136  	if !hostnameResolvesLightweightCacheLoadedOnceFromDB {
   137  		// A continuous-discovery will first make sure to load all resolves from DB.
   138  		// However cli does not do so.
   139  		// Anyway, it seems like the cache was not loaded from DB. Before doing real resolves,
   140  		// let's try and get the resolved hostname from database.
   141  		if !HostnameResolveMethodIsNone() {
   142  			go func() {
   143  				if resolvedHostname, err := ReadResolvedHostname(hostname); err == nil && resolvedHostname != "" {
   144  					getHostnameResolvesLightweightCache().Set(hostname, resolvedHostname, 0)
   145  				}
   146  			}()
   147  		}
   148  	}
   149  
   150  	// Unfound: resolve!
   151  	log.Infof("Hostname unresolved yet: %s", hostname)
   152  	resolvedHostname, err := resolveHostname(hostname)
   153  	if err != nil {
   154  		// Problem. What we'll do is cache the hostname for just one minute, so as to avoid flooding requests
   155  		// on one hand, yet make it refresh shortly on the other hand. Anyway do not write to database.
   156  		getHostnameResolvesLightweightCache().Set(hostname, resolvedHostname, time.Minute)
   157  		return hostname, err
   158  	}
   159  	// Good result! Cache it, also to DB
   160  	log.Infof("Cache hostname resolve %s as %s", hostname, resolvedHostname)
   161  	go UpdateResolvedHostname(hostname, resolvedHostname)
   162  	return resolvedHostname, nil
   163  }
   164  
   165  // UpdateResolvedHostname will store the given resolved hostname in cache
   166  // Returns false when the key already existed with same resolved value (similar
   167  // to AFFECTED_ROWS() in mysql)
   168  func UpdateResolvedHostname(hostname string, resolvedHostname string) bool {
   169  	if resolvedHostname == "" {
   170  		return false
   171  	}
   172  	if existingResolvedHostname, found := getHostnameResolvesLightweightCache().Get(hostname); found && (existingResolvedHostname == resolvedHostname) {
   173  		return false
   174  	}
   175  	getHostnameResolvesLightweightCache().Set(hostname, resolvedHostname, 0)
   176  	if !HostnameResolveMethodIsNone() {
   177  		_ = WriteResolvedHostname(hostname, resolvedHostname)
   178  	}
   179  	return true
   180  }
   181  
   182  func LoadHostnameResolveCache() error {
   183  	if !HostnameResolveMethodIsNone() {
   184  		return loadHostnameResolveCacheFromDatabase()
   185  	}
   186  	return nil
   187  }
   188  
   189  func loadHostnameResolveCacheFromDatabase() error {
   190  	allHostnamesResolves, err := ReadAllHostnameResolves()
   191  	if err != nil {
   192  		return err
   193  	}
   194  	for _, hostnameResolve := range allHostnamesResolves {
   195  		getHostnameResolvesLightweightCache().Set(hostnameResolve.hostname, hostnameResolve.resolvedHostname, 0)
   196  	}
   197  	hostnameResolvesLightweightCacheLoadedOnceFromDB = true
   198  	return nil
   199  }
   200  
   201  func FlushNontrivialResolveCacheToDatabase() error {
   202  	if HostnameResolveMethodIsNone() {
   203  		return nil
   204  	}
   205  	items, _ := HostnameResolveCache()
   206  	for hostname := range items {
   207  		resolvedHostname, found := getHostnameResolvesLightweightCache().Get(hostname)
   208  		if found && (resolvedHostname.(string) != hostname) {
   209  			_ = WriteResolvedHostname(hostname, resolvedHostname.(string))
   210  		}
   211  	}
   212  	return nil
   213  }
   214  
   215  func HostnameResolveCache() (map[string]cache.Item, error) {
   216  	return getHostnameResolvesLightweightCache().Items(), nil
   217  }
   218  
   219  func extractIPs(ips []net.IP) (ipv4String string, ipv6String string) {
   220  	for _, ip := range ips {
   221  		if ip4 := ip.To4(); ip4 != nil {
   222  			ipv4String = ip.String()
   223  		} else {
   224  			ipv6String = ip.String()
   225  		}
   226  	}
   227  	return ipv4String, ipv6String
   228  }
   229  
   230  func getHostnameIPs(hostname string) (ips []net.IP, fromCache bool, err error) {
   231  	if ips, found := hostnameIPsCache.Get(hostname); found {
   232  		return ips.([]net.IP), true, nil
   233  	}
   234  	ips, err = net.LookupIP(hostname)
   235  	if err != nil {
   236  		log.Error(err)
   237  		return ips, false, err
   238  	}
   239  	hostnameIPsCache.Set(hostname, ips, cache.DefaultExpiration)
   240  	return ips, false, nil
   241  }
   242  
   243  func getHostnameIP(hostname string) (ipString string, err error) {
   244  	ips, _, err := getHostnameIPs(hostname)
   245  	if err != nil {
   246  		return ipString, err
   247  	}
   248  	ipv4String, ipv6String := extractIPs(ips)
   249  	if ipv4String != "" {
   250  		return ipv4String, nil
   251  	}
   252  	return ipv6String, nil
   253  }
   254  
   255  func ResolveHostnameIPs(hostname string) error {
   256  	ips, fromCache, err := getHostnameIPs(hostname)
   257  	if err != nil {
   258  		return err
   259  	}
   260  	if fromCache {
   261  		return nil
   262  	}
   263  	ipv4String, ipv6String := extractIPs(ips)
   264  	return writeHostnameIPs(hostname, ipv4String, ipv6String)
   265  }