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  }