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 }