github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/utils.go (about) 1 // Package ais provides core functionality for the AIStore object storage. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package ais 6 7 import ( 8 "errors" 9 "fmt" 10 "net" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strconv" 15 "strings" 16 17 "github.com/NVIDIA/aistore/api/apc" 18 "github.com/NVIDIA/aistore/cmn" 19 "github.com/NVIDIA/aistore/cmn/cos" 20 "github.com/NVIDIA/aistore/cmn/k8s" 21 "github.com/NVIDIA/aistore/cmn/nlog" 22 "github.com/NVIDIA/aistore/core/meta" 23 ) 24 25 const ( 26 accessNetPublic = netAccess(1 << iota) 27 accessNetIntraControl 28 accessNetIntraData 29 30 accessNetPublicControl = accessNetPublic | accessNetIntraControl 31 accessNetPublicData = accessNetPublic | accessNetIntraData 32 accessControlData = accessNetIntraControl | accessNetIntraData 33 accessNetAll = accessNetPublic | accessNetIntraData | accessNetIntraControl 34 ) 35 36 // Network access of handlers (Public, IntraControl, & IntraData) 37 type ( 38 netAccess int 39 40 // HTTP Range (aka Read Range) 41 htrange struct { 42 Start, Length int64 43 } 44 45 // Local unicast IP info 46 localIPv4Info struct { 47 ipv4 string 48 mtu int 49 } 50 ) 51 52 func (na netAccess) isSet(flag netAccess) bool { return na&flag == flag } 53 54 // 55 // IPV4 56 // 57 58 // returns a list of local unicast (IPv4, MTU) 59 func getLocalIPv4s(config *cmn.Config) (addrlist []*localIPv4Info, err error) { 60 addrlist = make([]*localIPv4Info, 0, 4) 61 62 addrs, e := net.InterfaceAddrs() 63 if e != nil { 64 err = fmt.Errorf("failed to get host unicast IPs: %w", e) 65 return 66 } 67 iflist, e := net.Interfaces() 68 if e != nil { 69 err = fmt.Errorf("failed to get network interfaces: %w", e) 70 return 71 } 72 73 for _, addr := range addrs { 74 curr := &localIPv4Info{} 75 if ipnet, ok := addr.(*net.IPNet); ok { 76 // production or K8s: skip loopbacks 77 if ipnet.IP.IsLoopback() && (!config.TestingEnv() || k8s.IsK8s()) { 78 continue 79 } 80 if ipnet.IP.To4() == nil { 81 continue 82 } 83 curr.ipv4 = ipnet.IP.String() 84 } 85 86 for _, intf := range iflist { 87 ifAddrs, e := intf.Addrs() 88 // skip invalid interfaces 89 if e != nil { 90 continue 91 } 92 for _, ifAddr := range ifAddrs { 93 if ipnet, ok := ifAddr.(*net.IPNet); ok && ipnet.IP.To4() != nil && ipnet.IP.String() == curr.ipv4 { 94 curr.mtu = intf.MTU 95 addrlist = append(addrlist, curr) 96 break 97 } 98 } 99 if curr.mtu != 0 { 100 break 101 } 102 } 103 } 104 if len(addrlist) == 0 { 105 return addrlist, errors.New("the host does not have any IPv4 addresses") 106 } 107 return addrlist, nil 108 } 109 110 // given configured list of hostnames, return the first one matching local unicast IPv4 111 func _selectHost(locIPs []*localIPv4Info, hostnames []string) (string, error) { 112 sb := &strings.Builder{} 113 sb.WriteByte('[') 114 for i, lip := range locIPs { 115 sb.WriteString(lip.ipv4) 116 sb.WriteString("(MTU=") 117 sb.WriteString(strconv.Itoa(lip.mtu)) 118 sb.WriteByte(')') 119 if i < len(locIPs)-1 { 120 sb.WriteByte(' ') 121 } 122 } 123 sb.WriteByte(']') 124 nlog.Infoln("local IPv4:", sb.String()) 125 nlog.Infoln("configured:", hostnames) 126 127 for i, host := range hostnames { 128 host = strings.TrimSpace(host) 129 var ipv4 string 130 if net.ParseIP(host) != nil { // parses as IP 131 ipv4 = host 132 } else { 133 ip, err := cmn.Host2IP(host) 134 if err != nil { 135 nlog.Errorln("failed to resolve hostname(?)", host, "err:", err, "[idx:", i, len(hostnames)) 136 continue 137 } 138 ipv4 = ip.String() 139 nlog.Infoln("resolved hostname", host, "to IP addr", ipv4) 140 } 141 for _, addr := range locIPs { 142 if addr.ipv4 == ipv4 { 143 nlog.Infoln("selected: hostname", host, "IP", ipv4) 144 return host, nil 145 } 146 } 147 } 148 149 err := fmt.Errorf("failed to select hostname from: (%s, %v)", sb.String(), hostnames) 150 nlog.Errorln(err) 151 return "", err 152 } 153 154 // _localIP takes a list of local IPv4s and returns the best fit for a daemon to listen on it 155 func _localIP(addrList []*localIPv4Info) (ip net.IP, err error) { 156 if len(addrList) == 0 { 157 return nil, errors.New("no addresses to choose from") 158 } 159 if len(addrList) == 1 { 160 nlog.Infof("Found only one IPv4: %s, MTU %d", addrList[0].ipv4, addrList[0].mtu) 161 if addrList[0].mtu <= 1500 { 162 nlog.Warningf("IPv4 %s MTU size is small: %d\n", addrList[0].ipv4, addrList[0].mtu) 163 } 164 if ip = net.ParseIP(addrList[0].ipv4); ip == nil { 165 return nil, fmt.Errorf("failed to parse IP address: %s", addrList[0].ipv4) 166 } 167 return ip, nil 168 } 169 if cmn.Rom.FastV(4, cos.SmoduleAIS) { 170 nlog.Infof("%d IPv4s:", len(addrList)) 171 for _, addr := range addrList { 172 nlog.Infof(" %#v\n", *addr) 173 } 174 } 175 if ip = net.ParseIP(addrList[0].ipv4); ip == nil { 176 return nil, fmt.Errorf("failed to parse IP address: %s", addrList[0].ipv4) 177 } 178 return ip, nil 179 } 180 181 func multihome(configuredIPv4s string) (pub string, extra []string) { 182 if i := strings.IndexByte(configuredIPv4s, cmn.HostnameListSepa[0]); i <= 0 { 183 cos.ExitAssertLog(i < 0, "invalid format:", configuredIPv4s) 184 return configuredIPv4s, nil 185 } 186 187 // trim + validation 188 lst := strings.Split(configuredIPv4s, cmn.HostnameListSepa) 189 pub, extra = strings.TrimSpace(lst[0]), lst[1:] 190 for i := range extra { 191 extra[i] = strings.TrimSpace(extra[i]) 192 cos.ExitAssertLog(extra[i] != "", "invalid format (empty value):", configuredIPv4s) 193 cos.ExitAssertLog(extra[i] != pub, "duplicated addr or hostname:", configuredIPv4s) 194 for j := range i { 195 cos.ExitAssertLog(extra[i] != extra[j], "duplicated addr or hostname:", configuredIPv4s) 196 } 197 } 198 nlog.Infof("multihome: %s and %v", pub, extra) 199 return pub, extra 200 } 201 202 // choose one of the local IPv4s if local config doesn't contain (explicitly) specified 203 func initNetInfo(ni *meta.NetInfo, addrList []*localIPv4Info, proto, configuredIPv4s, port string) (err error) { 204 var ( 205 ip net.IP 206 host string 207 ) 208 if configuredIPv4s == "" { 209 if ip, err = _localIP(addrList); err == nil { 210 ni.Init(proto, ip.String(), port) 211 } 212 return 213 } 214 215 lst := strings.Split(configuredIPv4s, cmn.HostnameListSepa) 216 if host, err = _selectHost(addrList, lst); err == nil { 217 ni.Init(proto, host, port) 218 } 219 return 220 } 221 222 ///////////// 223 // htrange // 224 ///////////// 225 226 // (compare w/ cmn.MakeRangeHdr) 227 func (r htrange) contentRange(size int64) string { 228 return fmt.Sprintf("%s%d-%d/%d", cos.HdrContentRangeValPrefix, r.Start, r.Start+r.Length-1, size) 229 } 230 231 // ParseMultiRange parses a Range Header string as per RFC 7233. 232 // ErrNoOverlap is returned if none of the ranges overlap with the [0, size) content. 233 func parseMultiRange(s string, size int64) (ranges []htrange, err error) { 234 var noOverlap bool 235 if !strings.HasPrefix(s, cos.HdrRangeValPrefix) { 236 return nil, fmt.Errorf("read range %q is invalid (prefix)", s) 237 } 238 allRanges := strings.Split(s[len(cos.HdrRangeValPrefix):], ",") 239 for _, ra := range allRanges { 240 ra = strings.TrimSpace(ra) 241 if ra == "" { 242 continue 243 } 244 i := strings.Index(ra, "-") 245 if i < 0 { 246 return nil, fmt.Errorf("read range %q is invalid (-)", s) 247 } 248 var ( 249 r htrange 250 start = strings.TrimSpace(ra[:i]) 251 end = strings.TrimSpace(ra[i+1:]) 252 ) 253 if start == "" { 254 // If no start is specified, end specifies the range start relative 255 // to the end of the file, and we are dealing with <suffix-length> 256 // which has to be a non-negative integer as per RFC 7233 Section 2.1 "Byte-Ranges". 257 if end == "" || end[0] == '-' { 258 return nil, fmt.Errorf("read range %q is invalid as per RFC 7233 Section 2.1", ra) 259 } 260 i, err := strconv.ParseInt(end, 10, 64) 261 if i < 0 || err != nil { 262 return nil, fmt.Errorf("read range %q is invalid (see RFC 7233 Section 2.1)", ra) 263 } 264 if i > size { 265 i = size 266 } 267 r.Start = size - i 268 r.Length = size - r.Start 269 } else { 270 i, err := strconv.ParseInt(start, 10, 64) 271 if err != nil || i < 0 { 272 return nil, fmt.Errorf("read range %q is invalid (start)", ra) 273 } 274 if i >= size { 275 // If the range begins after the size of the content it does not overlap. 276 noOverlap = true 277 continue 278 } 279 r.Start = i 280 if end == "" { 281 // If no end is specified, range extends to the end of the file. 282 r.Length = size - r.Start 283 } else { 284 i, err := strconv.ParseInt(end, 10, 64) 285 if err != nil || r.Start > i { 286 return nil, fmt.Errorf("read range %q is invalid (end)", ra) 287 } 288 if i >= size { 289 i = size - 1 290 } 291 r.Length = i - r.Start + 1 292 } 293 } 294 ranges = append(ranges, r) 295 } 296 297 if noOverlap && len(ranges) == 0 { 298 return nil, cmn.NewErrRangeNotSatisfiable(nil, allRanges, size) 299 } 300 return ranges, nil 301 } 302 303 // 304 // misc. helpers 305 // 306 307 func deploymentType() string { 308 switch { 309 case k8s.IsK8s(): 310 return apc.DeploymentK8s 311 case cmn.GCO.Get().TestingEnv(): 312 return apc.DeploymentDev 313 default: 314 return runtime.GOOS 315 } 316 } 317 318 // for AIS metadata filenames (constants), see `cmn/fname` package 319 func cleanupConfigDir(name string, keepInitialConfig bool) { 320 if !keepInitialConfig { 321 // remove plain-text (initial) config 322 cos.RemoveFile(daemon.cli.globalConfigPath) 323 cos.RemoveFile(daemon.cli.localConfigPath) 324 } 325 config := cmn.GCO.Get() 326 filepath.Walk(config.ConfigDir, func(path string, finfo os.FileInfo, _ error) error { 327 if strings.HasPrefix(finfo.Name(), ".ais.") { 328 if err := cos.RemoveFile(path); err != nil { 329 nlog.Errorf("%s: failed to cleanup %q, err: %v", name, path, err) 330 } 331 } 332 return nil 333 }) 334 } 335 336 // 337 // common APPEND(file(s)) pre-parser 338 // 339 340 const appendHandleSepa = "|" 341 342 func preParse(packedHdl string) (items []string, err error) { 343 items = strings.SplitN(packedHdl, appendHandleSepa, 4) 344 if len(items) != 4 { 345 err = fmt.Errorf("invalid APPEND handle: %q", packedHdl) 346 } 347 return 348 }