storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/endpoint.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2017-2019 MinIO, 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 cmd
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"net/http"
    25  	"net/url"
    26  	"path"
    27  	"path/filepath"
    28  	"reflect"
    29  	"runtime"
    30  	"sort"
    31  	"strconv"
    32  	"strings"
    33  	"time"
    34  
    35  	"github.com/dustin/go-humanize"
    36  	"github.com/minio/minio-go/v7/pkg/set"
    37  
    38  	"storj.io/minio/cmd/config"
    39  	xhttp "storj.io/minio/cmd/http"
    40  	"storj.io/minio/cmd/logger"
    41  	"storj.io/minio/pkg/env"
    42  	"storj.io/minio/pkg/mountinfo"
    43  	xnet "storj.io/minio/pkg/net"
    44  )
    45  
    46  // EndpointType - enum for endpoint type.
    47  type EndpointType int
    48  
    49  const (
    50  	// PathEndpointType - path style endpoint type enum.
    51  	PathEndpointType EndpointType = iota + 1
    52  
    53  	// URLEndpointType - URL style endpoint type enum.
    54  	URLEndpointType
    55  )
    56  
    57  // ProxyEndpoint - endpoint used for proxy redirects
    58  // See proxyRequest() for details.
    59  type ProxyEndpoint struct {
    60  	Endpoint
    61  	Transport http.RoundTripper
    62  }
    63  
    64  // Endpoint - any type of endpoint.
    65  type Endpoint struct {
    66  	*url.URL
    67  	IsLocal bool
    68  }
    69  
    70  func (endpoint Endpoint) String() string {
    71  	if endpoint.Host == "" {
    72  		return endpoint.Path
    73  	}
    74  
    75  	return endpoint.URL.String()
    76  }
    77  
    78  // Type - returns type of endpoint.
    79  func (endpoint Endpoint) Type() EndpointType {
    80  	if endpoint.Host == "" {
    81  		return PathEndpointType
    82  	}
    83  
    84  	return URLEndpointType
    85  }
    86  
    87  // HTTPS - returns true if secure for URLEndpointType.
    88  func (endpoint Endpoint) HTTPS() bool {
    89  	return endpoint.Scheme == "https"
    90  }
    91  
    92  // UpdateIsLocal - resolves the host and updates if it is local or not.
    93  func (endpoint *Endpoint) UpdateIsLocal() (err error) {
    94  	if !endpoint.IsLocal {
    95  		endpoint.IsLocal, err = isLocalHost(endpoint.Hostname(), endpoint.Port(), globalMinioPort)
    96  		if err != nil {
    97  			return err
    98  		}
    99  	}
   100  	return nil
   101  }
   102  
   103  // NewEndpoint - returns new endpoint based on given arguments.
   104  func NewEndpoint(arg string) (ep Endpoint, e error) {
   105  	// isEmptyPath - check whether given path is not empty.
   106  	isEmptyPath := func(path string) bool {
   107  		return path == "" || path == SlashSeparator || path == `\`
   108  	}
   109  
   110  	if isEmptyPath(arg) {
   111  		return ep, fmt.Errorf("empty or root endpoint is not supported")
   112  	}
   113  
   114  	var isLocal bool
   115  	var host string
   116  	u, err := url.Parse(arg)
   117  	if err == nil && u.Host != "" {
   118  		// URL style of endpoint.
   119  		// Valid URL style endpoint is
   120  		// - Scheme field must contain "http" or "https"
   121  		// - All field should be empty except Host and Path.
   122  		if !((u.Scheme == "http" || u.Scheme == "https") &&
   123  			u.User == nil && u.Opaque == "" && !u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
   124  			return ep, fmt.Errorf("invalid URL endpoint format")
   125  		}
   126  
   127  		var port string
   128  		host, port, err = net.SplitHostPort(u.Host)
   129  		if err != nil {
   130  			if !strings.Contains(err.Error(), "missing port in address") {
   131  				return ep, fmt.Errorf("invalid URL endpoint format: %w", err)
   132  			}
   133  
   134  			host = u.Host
   135  		} else {
   136  			var p int
   137  			p, err = strconv.Atoi(port)
   138  			if err != nil {
   139  				return ep, fmt.Errorf("invalid URL endpoint format: invalid port number")
   140  			} else if p < 1 || p > 65535 {
   141  				return ep, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535")
   142  			}
   143  		}
   144  		if i := strings.Index(host, "%"); i > -1 {
   145  			host = host[:i]
   146  		}
   147  
   148  		if host == "" {
   149  			return ep, fmt.Errorf("invalid URL endpoint format: empty host name")
   150  		}
   151  
   152  		// As this is path in the URL, we should use path package, not filepath package.
   153  		// On MS Windows, filepath.Clean() converts into Windows path style ie `/foo` becomes `\foo`
   154  		u.Path = path.Clean(u.Path)
   155  		if isEmptyPath(u.Path) {
   156  			return ep, fmt.Errorf("empty or root path is not supported in URL endpoint")
   157  		}
   158  
   159  		// On windows having a preceding SlashSeparator will cause problems, if the
   160  		// command line already has C:/<export-folder/ in it. Final resulting
   161  		// path on windows might become C:/C:/ this will cause problems
   162  		// of starting minio server properly in distributed mode on windows.
   163  		// As a special case make sure to trim the separator.
   164  
   165  		// NOTE: It is also perfectly fine for windows users to have a path
   166  		// without C:/ since at that point we treat it as relative path
   167  		// and obtain the full filesystem path as well. Providing C:/
   168  		// style is necessary to provide paths other than C:/,
   169  		// such as F:/, D:/ etc.
   170  		//
   171  		// Another additional benefit here is that this style also
   172  		// supports providing \\host\share support as well.
   173  		if runtime.GOOS == globalWindowsOSName {
   174  			if filepath.VolumeName(u.Path[1:]) != "" {
   175  				u.Path = u.Path[1:]
   176  			}
   177  		}
   178  
   179  	} else {
   180  		// Only check if the arg is an ip address and ask for scheme since its absent.
   181  		// localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as
   182  		// /mnt/export1. So we go ahead and start the minio server in FS modes in these cases.
   183  		if isHostIP(arg) {
   184  			return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https")
   185  		}
   186  		absArg, err := filepath.Abs(arg)
   187  		if err != nil {
   188  			return Endpoint{}, fmt.Errorf("absolute path failed %s", err)
   189  		}
   190  		u = &url.URL{Path: path.Clean(absArg)}
   191  		isLocal = true
   192  	}
   193  
   194  	return Endpoint{
   195  		URL:     u,
   196  		IsLocal: isLocal,
   197  	}, nil
   198  }
   199  
   200  // PoolEndpoints represent endpoints in a given pool
   201  // along with its setCount and setDriveCount.
   202  type PoolEndpoints struct {
   203  	SetCount     int
   204  	DrivesPerSet int
   205  	Endpoints    Endpoints
   206  }
   207  
   208  // EndpointServerPools - list of list of endpoints
   209  type EndpointServerPools []PoolEndpoints
   210  
   211  // GetLocalPoolIdx returns the pool which endpoint belongs to locally.
   212  // if ep is remote this code will return -1 poolIndex
   213  func (l EndpointServerPools) GetLocalPoolIdx(ep Endpoint) int {
   214  	for i, zep := range l {
   215  		for _, cep := range zep.Endpoints {
   216  			if cep.IsLocal && ep.IsLocal {
   217  				if reflect.DeepEqual(cep, ep) {
   218  					return i
   219  				}
   220  			}
   221  		}
   222  	}
   223  	return -1
   224  }
   225  
   226  // Add add pool endpoints
   227  func (l *EndpointServerPools) Add(zeps PoolEndpoints) error {
   228  	existSet := set.NewStringSet()
   229  	for _, zep := range *l {
   230  		for _, ep := range zep.Endpoints {
   231  			existSet.Add(ep.String())
   232  		}
   233  	}
   234  	// Validate if there are duplicate endpoints across serverPools
   235  	for _, ep := range zeps.Endpoints {
   236  		if existSet.Contains(ep.String()) {
   237  			return fmt.Errorf("duplicate endpoints found")
   238  		}
   239  	}
   240  	*l = append(*l, zeps)
   241  	return nil
   242  }
   243  
   244  // Localhost - returns the local hostname from list of endpoints
   245  func (l EndpointServerPools) Localhost() string {
   246  	for _, ep := range l {
   247  		for _, endpoint := range ep.Endpoints {
   248  			if endpoint.IsLocal {
   249  				u := &url.URL{
   250  					Scheme: endpoint.Scheme,
   251  					Host:   endpoint.Host,
   252  				}
   253  				return u.String()
   254  			}
   255  		}
   256  	}
   257  	return ""
   258  }
   259  
   260  // FirstLocal returns true if the first endpoint is local.
   261  func (l EndpointServerPools) FirstLocal() bool {
   262  	return l[0].Endpoints[0].IsLocal
   263  }
   264  
   265  // HTTPS - returns true if secure for URLEndpointType.
   266  func (l EndpointServerPools) HTTPS() bool {
   267  	return l[0].Endpoints.HTTPS()
   268  }
   269  
   270  // NEndpoints - returns all nodes count
   271  func (l EndpointServerPools) NEndpoints() (count int) {
   272  	for _, ep := range l {
   273  		count += len(ep.Endpoints)
   274  	}
   275  	return count
   276  }
   277  
   278  // Hostnames - returns list of unique hostnames
   279  func (l EndpointServerPools) Hostnames() []string {
   280  	foundSet := set.NewStringSet()
   281  	for _, ep := range l {
   282  		for _, endpoint := range ep.Endpoints {
   283  			if foundSet.Contains(endpoint.Hostname()) {
   284  				continue
   285  			}
   286  			foundSet.Add(endpoint.Hostname())
   287  		}
   288  	}
   289  	return foundSet.ToSlice()
   290  }
   291  
   292  // hostsSorted will return all hosts found.
   293  // The LOCAL host will be nil, but the indexes of all hosts should
   294  // remain consistent across the cluster.
   295  func (l EndpointServerPools) hostsSorted() []*xnet.Host {
   296  	peers, localPeer := l.peers()
   297  	sort.Strings(peers)
   298  	hosts := make([]*xnet.Host, len(peers))
   299  	for i, hostStr := range peers {
   300  		if hostStr == localPeer {
   301  			continue
   302  		}
   303  		host, err := xnet.ParseHost(hostStr)
   304  		if err != nil {
   305  			logger.LogIf(GlobalContext, err)
   306  			continue
   307  		}
   308  		hosts[i] = host
   309  	}
   310  
   311  	return hosts
   312  }
   313  
   314  // peers will return all peers, including local.
   315  // The local peer is returned as a separate string.
   316  func (l EndpointServerPools) peers() (peers []string, local string) {
   317  	allSet := set.NewStringSet()
   318  	for _, ep := range l {
   319  		for _, endpoint := range ep.Endpoints {
   320  			if endpoint.Type() != URLEndpointType {
   321  				continue
   322  			}
   323  
   324  			peer := endpoint.Host
   325  			if endpoint.IsLocal {
   326  				if _, port := mustSplitHostPort(peer); port == globalMinioPort {
   327  					local = peer
   328  				}
   329  			}
   330  
   331  			allSet.Add(peer)
   332  		}
   333  	}
   334  
   335  	return allSet.ToSlice(), local
   336  }
   337  
   338  // Endpoints - list of same type of endpoint.
   339  type Endpoints []Endpoint
   340  
   341  // HTTPS - returns true if secure for URLEndpointType.
   342  func (endpoints Endpoints) HTTPS() bool {
   343  	return endpoints[0].HTTPS()
   344  }
   345  
   346  // GetString - returns endpoint string of i-th endpoint (0-based),
   347  // and empty string for invalid indexes.
   348  func (endpoints Endpoints) GetString(i int) string {
   349  	if i < 0 || i >= len(endpoints) {
   350  		return ""
   351  	}
   352  	return endpoints[i].String()
   353  }
   354  
   355  // GetAllStrings - returns allstring of all endpoints
   356  func (endpoints Endpoints) GetAllStrings() (all []string) {
   357  	for _, e := range endpoints {
   358  		all = append(all, e.String())
   359  	}
   360  	return
   361  }
   362  
   363  func hostResolveToLocalhost(endpoint Endpoint) bool {
   364  	hostIPs, err := getHostIP(endpoint.Hostname())
   365  	if err != nil {
   366  		// Log the message to console about the host resolving
   367  		reqInfo := (&logger.ReqInfo{}).AppendTags(
   368  			"host",
   369  			endpoint.Hostname(),
   370  		)
   371  		ctx := logger.SetReqInfo(GlobalContext, reqInfo)
   372  		logger.LogIf(ctx, err, logger.Application)
   373  		return false
   374  	}
   375  	var loopback int
   376  	for _, hostIP := range hostIPs.ToSlice() {
   377  		if net.ParseIP(hostIP).IsLoopback() {
   378  			loopback++
   379  		}
   380  	}
   381  	return loopback == len(hostIPs)
   382  }
   383  
   384  func (endpoints Endpoints) atleastOneEndpointLocal() bool {
   385  	for _, endpoint := range endpoints {
   386  		if endpoint.IsLocal {
   387  			return true
   388  		}
   389  	}
   390  	return false
   391  }
   392  
   393  // UpdateIsLocal - resolves the host and discovers the local host.
   394  func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
   395  	orchestrated := IsDocker() || IsKubernetes()
   396  	k8sReplicaSet := IsKubernetesReplicaSet()
   397  
   398  	var epsResolved int
   399  	var foundLocal bool
   400  	resolvedList := make([]bool, len(endpoints))
   401  	// Mark the starting time
   402  	startTime := time.Now()
   403  	keepAliveTicker := time.NewTicker(10 * time.Millisecond)
   404  	defer keepAliveTicker.Stop()
   405  	for {
   406  		// Break if the local endpoint is found already Or all the endpoints are resolved.
   407  		if foundLocal || (epsResolved == len(endpoints)) {
   408  			break
   409  		}
   410  		// Retry infinitely on Kubernetes and Docker swarm.
   411  		// This is needed as the remote hosts are sometime
   412  		// not available immediately.
   413  		select {
   414  		case <-globalOSSignalCh:
   415  			return fmt.Errorf("The endpoint resolution got interrupted")
   416  		default:
   417  			for i, resolved := range resolvedList {
   418  				if resolved {
   419  					// Continue if host is already resolved.
   420  					continue
   421  				}
   422  
   423  				// Log the message to console about the host resolving
   424  				reqInfo := (&logger.ReqInfo{}).AppendTags(
   425  					"host",
   426  					endpoints[i].Hostname(),
   427  				)
   428  
   429  				if k8sReplicaSet && hostResolveToLocalhost(endpoints[i]) {
   430  					err := fmt.Errorf("host %s resolves to 127.*, DNS incorrectly configured retrying",
   431  						endpoints[i])
   432  					// time elapsed
   433  					timeElapsed := time.Since(startTime)
   434  					// log error only if more than 1s elapsed
   435  					if timeElapsed > time.Second {
   436  						reqInfo.AppendTags("elapsedTime",
   437  							humanize.RelTime(startTime,
   438  								startTime.Add(timeElapsed),
   439  								"elapsed",
   440  								""))
   441  						ctx := logger.SetReqInfo(GlobalContext, reqInfo)
   442  						logger.LogIf(ctx, err, logger.Application)
   443  					}
   444  					continue
   445  				}
   446  
   447  				// return err if not Docker or Kubernetes
   448  				// We use IsDocker() to check for Docker environment
   449  				// We use IsKubernetes() to check for Kubernetes environment
   450  				isLocal, err := isLocalHost(endpoints[i].Hostname(),
   451  					endpoints[i].Port(),
   452  					globalMinioPort,
   453  				)
   454  				if err != nil && !orchestrated {
   455  					return err
   456  				}
   457  				if err != nil {
   458  					// time elapsed
   459  					timeElapsed := time.Since(startTime)
   460  					// log error only if more than 1s elapsed
   461  					if timeElapsed > time.Second {
   462  						reqInfo.AppendTags("elapsedTime",
   463  							humanize.RelTime(startTime,
   464  								startTime.Add(timeElapsed),
   465  								"elapsed",
   466  								"",
   467  							))
   468  						ctx := logger.SetReqInfo(GlobalContext,
   469  							reqInfo)
   470  						logger.LogIf(ctx, err, logger.Application)
   471  					}
   472  				} else {
   473  					resolvedList[i] = true
   474  					endpoints[i].IsLocal = isLocal
   475  					if k8sReplicaSet && !endpoints.atleastOneEndpointLocal() && !foundPrevLocal {
   476  						// In replicated set in k8s deployment, IPs might
   477  						// get resolved for older IPs, add this code
   478  						// to ensure that we wait for this server to
   479  						// participate atleast one disk and be local.
   480  						//
   481  						// In special cases for replica set with expanded
   482  						// pool setups we need to make sure to provide
   483  						// value of foundPrevLocal from pool1 if we already
   484  						// found a local setup. Only if we haven't found
   485  						// previous local we continue to wait to look for
   486  						// atleast one local.
   487  						resolvedList[i] = false
   488  						// time elapsed
   489  						err := fmt.Errorf("no endpoint is local to this host: %s", endpoints[i])
   490  						timeElapsed := time.Since(startTime)
   491  						// log error only if more than 1s elapsed
   492  						if timeElapsed > time.Second {
   493  							reqInfo.AppendTags("elapsedTime",
   494  								humanize.RelTime(startTime,
   495  									startTime.Add(timeElapsed),
   496  									"elapsed",
   497  									"",
   498  								))
   499  							ctx := logger.SetReqInfo(GlobalContext,
   500  								reqInfo)
   501  							logger.LogIf(ctx, err, logger.Application)
   502  						}
   503  						continue
   504  					}
   505  					epsResolved++
   506  					if !foundLocal {
   507  						foundLocal = isLocal
   508  					}
   509  				}
   510  			}
   511  
   512  			// Wait for the tick, if the there exist a local endpoint in discovery.
   513  			// Non docker/kubernetes environment we do not need to wait.
   514  			if !foundLocal && orchestrated {
   515  				<-keepAliveTicker.C
   516  			}
   517  		}
   518  	}
   519  
   520  	// On Kubernetes/Docker setups DNS resolves inappropriately sometimes
   521  	// where there are situations same endpoints with multiple disks
   522  	// come online indicating either one of them is local and some
   523  	// of them are not local. This situation can never happen and
   524  	// its only a possibility in orchestrated deployments with dynamic
   525  	// DNS. Following code ensures that we treat if one of the endpoint
   526  	// says its local for a given host - it is true for all endpoints
   527  	// for the same host. Following code ensures that this assumption
   528  	// is true and it works in all scenarios and it is safe to assume
   529  	// for a given host.
   530  	endpointLocalMap := make(map[string]bool)
   531  	for _, ep := range endpoints {
   532  		if ep.IsLocal {
   533  			endpointLocalMap[ep.Host] = ep.IsLocal
   534  		}
   535  	}
   536  	for i := range endpoints {
   537  		endpoints[i].IsLocal = endpointLocalMap[endpoints[i].Host]
   538  	}
   539  	return nil
   540  }
   541  
   542  // NewEndpoints - returns new endpoint list based on input args.
   543  func NewEndpoints(args ...string) (endpoints Endpoints, err error) {
   544  	var endpointType EndpointType
   545  	var scheme string
   546  
   547  	uniqueArgs := set.NewStringSet()
   548  	// Loop through args and adds to endpoint list.
   549  	for i, arg := range args {
   550  		endpoint, err := NewEndpoint(arg)
   551  		if err != nil {
   552  			return nil, fmt.Errorf("'%s': %s", arg, err.Error())
   553  		}
   554  
   555  		// All endpoints have to be same type and scheme if applicable.
   556  		if i == 0 {
   557  			endpointType = endpoint.Type()
   558  			scheme = endpoint.Scheme
   559  		} else if endpoint.Type() != endpointType {
   560  			return nil, fmt.Errorf("mixed style endpoints are not supported")
   561  		} else if endpoint.Scheme != scheme {
   562  			return nil, fmt.Errorf("mixed scheme is not supported")
   563  		}
   564  
   565  		arg = endpoint.String()
   566  		if uniqueArgs.Contains(arg) {
   567  			return nil, fmt.Errorf("duplicate endpoints found")
   568  		}
   569  		uniqueArgs.Add(arg)
   570  		endpoints = append(endpoints, endpoint)
   571  	}
   572  
   573  	return endpoints, nil
   574  }
   575  
   576  // Checks if there are any cross device mounts.
   577  func checkCrossDeviceMounts(endpoints Endpoints) (err error) {
   578  	var absPaths []string
   579  	for _, endpoint := range endpoints {
   580  		if endpoint.IsLocal {
   581  			var absPath string
   582  			absPath, err = filepath.Abs(endpoint.Path)
   583  			if err != nil {
   584  				return err
   585  			}
   586  			absPaths = append(absPaths, absPath)
   587  		}
   588  	}
   589  	return mountinfo.CheckCrossDevice(absPaths)
   590  }
   591  
   592  // CreateEndpoints - validates and creates new endpoints for given args.
   593  func CreateEndpoints(serverAddr string, foundLocal bool, args ...[]string) (Endpoints, SetupType, error) {
   594  	var endpoints Endpoints
   595  	var setupType SetupType
   596  	var err error
   597  
   598  	// Check whether serverAddr is valid for this host.
   599  	if err = CheckLocalServerAddr(serverAddr); err != nil {
   600  		return endpoints, setupType, err
   601  	}
   602  
   603  	_, serverAddrPort := mustSplitHostPort(serverAddr)
   604  
   605  	// For single arg, return FS setup.
   606  	if len(args) == 1 && len(args[0]) == 1 {
   607  		var endpoint Endpoint
   608  		endpoint, err = NewEndpoint(args[0][0])
   609  		if err != nil {
   610  			return endpoints, setupType, err
   611  		}
   612  		if err := endpoint.UpdateIsLocal(); err != nil {
   613  			return endpoints, setupType, err
   614  		}
   615  		if endpoint.Type() != PathEndpointType {
   616  			return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg("use path style endpoint for FS setup")
   617  		}
   618  		endpoints = append(endpoints, endpoint)
   619  		setupType = FSSetupType
   620  
   621  		// Check for cross device mounts if any.
   622  		if err = checkCrossDeviceMounts(endpoints); err != nil {
   623  			return endpoints, setupType, config.ErrInvalidFSEndpoint(nil).Msg(err.Error())
   624  		}
   625  
   626  		return endpoints, setupType, nil
   627  	}
   628  
   629  	for _, iargs := range args {
   630  		// Convert args to endpoints
   631  		eps, err := NewEndpoints(iargs...)
   632  		if err != nil {
   633  			return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
   634  		}
   635  
   636  		// Check for cross device mounts if any.
   637  		if err = checkCrossDeviceMounts(eps); err != nil {
   638  			return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
   639  		}
   640  
   641  		endpoints = append(endpoints, eps...)
   642  	}
   643  
   644  	if len(endpoints) == 0 {
   645  		return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg("invalid number of endpoints")
   646  	}
   647  
   648  	// Return Erasure setup when all endpoints are path style.
   649  	if endpoints[0].Type() == PathEndpointType {
   650  		setupType = ErasureSetupType
   651  		return endpoints, setupType, nil
   652  	}
   653  
   654  	if err = endpoints.UpdateIsLocal(foundLocal); err != nil {
   655  		return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
   656  	}
   657  
   658  	// Here all endpoints are URL style.
   659  	endpointPathSet := set.NewStringSet()
   660  	localEndpointCount := 0
   661  	localServerHostSet := set.NewStringSet()
   662  	localPortSet := set.NewStringSet()
   663  
   664  	for _, endpoint := range endpoints {
   665  		endpointPathSet.Add(endpoint.Path)
   666  		if endpoint.IsLocal {
   667  			localServerHostSet.Add(endpoint.Hostname())
   668  
   669  			var port string
   670  			_, port, err = net.SplitHostPort(endpoint.Host)
   671  			if err != nil {
   672  				port = serverAddrPort
   673  			}
   674  			localPortSet.Add(port)
   675  
   676  			localEndpointCount++
   677  		}
   678  	}
   679  
   680  	// Check whether same path is not used in endpoints of a host on different port.
   681  	{
   682  		pathIPMap := make(map[string]set.StringSet)
   683  		for _, endpoint := range endpoints {
   684  			host := endpoint.Hostname()
   685  			hostIPSet, _ := getHostIP(host)
   686  			if IPSet, ok := pathIPMap[endpoint.Path]; ok {
   687  				if !IPSet.Intersection(hostIPSet).IsEmpty() {
   688  					return endpoints, setupType,
   689  						config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' can not be served by different port on same address", endpoint.Path))
   690  				}
   691  				pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet)
   692  			} else {
   693  				pathIPMap[endpoint.Path] = hostIPSet
   694  			}
   695  		}
   696  	}
   697  
   698  	// Check whether same path is used for more than 1 local endpoints.
   699  	{
   700  		localPathSet := set.CreateStringSet()
   701  		for _, endpoint := range endpoints {
   702  			if !endpoint.IsLocal {
   703  				continue
   704  			}
   705  			if localPathSet.Contains(endpoint.Path) {
   706  				return endpoints, setupType,
   707  					config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' cannot be served by different address on same server", endpoint.Path))
   708  			}
   709  			localPathSet.Add(endpoint.Path)
   710  		}
   711  	}
   712  
   713  	// All endpoints are pointing to local host
   714  	if len(endpoints) == localEndpointCount {
   715  		// If all endpoints have same port number, Just treat it as local erasure setup
   716  		// using URL style endpoints.
   717  		if len(localPortSet) == 1 {
   718  			if len(localServerHostSet) > 1 {
   719  				return endpoints, setupType,
   720  					config.ErrInvalidErasureEndpoints(nil).Msg("all local endpoints should not have different hostnames/ips")
   721  			}
   722  			return endpoints, ErasureSetupType, nil
   723  		}
   724  
   725  		// Even though all endpoints are local, but those endpoints use different ports.
   726  		// This means it is DistErasure setup.
   727  	}
   728  
   729  	// Add missing port in all endpoints.
   730  	for i := range endpoints {
   731  		_, port, err := net.SplitHostPort(endpoints[i].Host)
   732  		if err != nil {
   733  			endpoints[i].Host = net.JoinHostPort(endpoints[i].Host, serverAddrPort)
   734  		} else if endpoints[i].IsLocal && serverAddrPort != port {
   735  			// If endpoint is local, but port is different than serverAddrPort, then make it as remote.
   736  			endpoints[i].IsLocal = false
   737  		}
   738  	}
   739  
   740  	uniqueArgs := set.NewStringSet()
   741  	for _, endpoint := range endpoints {
   742  		uniqueArgs.Add(endpoint.Host)
   743  	}
   744  
   745  	// Error out if we have less than 2 unique servers.
   746  	if len(uniqueArgs.ToSlice()) < 2 && setupType == DistErasureSetupType {
   747  		err := fmt.Errorf("Unsupported number of endpoints (%s), minimum number of servers cannot be less than 2 in distributed setup", endpoints)
   748  		return endpoints, setupType, err
   749  	}
   750  
   751  	publicIPs := env.Get(config.EnvPublicIPs, "")
   752  	if len(publicIPs) == 0 {
   753  		updateDomainIPs(uniqueArgs)
   754  	}
   755  
   756  	setupType = DistErasureSetupType
   757  	return endpoints, setupType, nil
   758  }
   759  
   760  // GetLocalPeer - returns local peer value, returns globalMinioAddr
   761  // for FS and Erasure mode. In case of distributed server return
   762  // the first element from the set of peers which indicate that
   763  // they are local. There is always one entry that is local
   764  // even with repeated server endpoints.
   765  func GetLocalPeer(endpointServerPools EndpointServerPools, host, port string) (localPeer string) {
   766  	peerSet := set.NewStringSet()
   767  	for _, ep := range endpointServerPools {
   768  		for _, endpoint := range ep.Endpoints {
   769  			if endpoint.Type() != URLEndpointType {
   770  				continue
   771  			}
   772  			if endpoint.IsLocal && endpoint.Host != "" {
   773  				peerSet.Add(endpoint.Host)
   774  			}
   775  		}
   776  	}
   777  	if peerSet.IsEmpty() {
   778  		// Local peer can be empty in FS or Erasure coded mode.
   779  		// If so, return globalMinioHost + globalMinioPort value.
   780  		if host != "" {
   781  			return net.JoinHostPort(host, port)
   782  		}
   783  
   784  		return net.JoinHostPort("127.0.0.1", port)
   785  	}
   786  	return peerSet.ToSlice()[0]
   787  }
   788  
   789  // GetProxyEndpointLocalIndex returns index of the local proxy endpoint
   790  func GetProxyEndpointLocalIndex(proxyEps []ProxyEndpoint) int {
   791  	for i, pep := range proxyEps {
   792  		if pep.IsLocal {
   793  			return i
   794  		}
   795  	}
   796  	return -1
   797  }
   798  
   799  func httpDo(clnt *http.Client, req *http.Request, f func(*http.Response, error) error) error {
   800  	ctx, cancel := context.WithTimeout(GlobalContext, 200*time.Millisecond)
   801  	defer cancel()
   802  
   803  	// Run the HTTP request in a goroutine and pass the response to f.
   804  	c := make(chan error, 1)
   805  	req = req.WithContext(ctx)
   806  	go func() { c <- f(clnt.Do(req)) }()
   807  	select {
   808  	case <-ctx.Done():
   809  		<-c // Wait for f to return.
   810  		return ctx.Err()
   811  	case err := <-c:
   812  		return err
   813  	}
   814  }
   815  
   816  func getOnlineProxyEndpointIdx() int {
   817  	type reqIndex struct {
   818  		Request *http.Request
   819  		Idx     int
   820  	}
   821  
   822  	proxyRequests := make(map[*http.Client]reqIndex, len(globalProxyEndpoints))
   823  	for i, proxyEp := range globalProxyEndpoints {
   824  		proxyEp := proxyEp
   825  		serverURL := &url.URL{
   826  			Scheme: proxyEp.Scheme,
   827  			Host:   proxyEp.Host,
   828  			Path:   pathJoin(healthCheckPathPrefix, healthCheckLivenessPath),
   829  		}
   830  
   831  		req, err := http.NewRequest(http.MethodGet, serverURL.String(), nil)
   832  		if err != nil {
   833  			continue
   834  		}
   835  
   836  		proxyRequests[&http.Client{
   837  			Transport: proxyEp.Transport,
   838  		}] = reqIndex{
   839  			Request: req,
   840  			Idx:     i,
   841  		}
   842  	}
   843  
   844  	for c, r := range proxyRequests {
   845  		if err := httpDo(c, r.Request, func(resp *http.Response, err error) error {
   846  			if err != nil {
   847  				return err
   848  			}
   849  			xhttp.DrainBody(resp.Body)
   850  			if resp.StatusCode != http.StatusOK {
   851  				return errors.New(resp.Status)
   852  			}
   853  			if v := resp.Header.Get(xhttp.MinIOServerStatus); v == unavailable {
   854  				return errors.New(v)
   855  			}
   856  			return nil
   857  		}); err != nil {
   858  			continue
   859  		}
   860  		return r.Idx
   861  	}
   862  	return -1
   863  }
   864  
   865  // GetProxyEndpoints - get all endpoints that can be used to proxy list request.
   866  func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint {
   867  	var proxyEps []ProxyEndpoint
   868  
   869  	proxyEpSet := set.NewStringSet()
   870  
   871  	for _, ep := range endpointServerPools {
   872  		for _, endpoint := range ep.Endpoints {
   873  			if endpoint.Type() != URLEndpointType {
   874  				continue
   875  			}
   876  
   877  			host := endpoint.Host
   878  			if proxyEpSet.Contains(host) {
   879  				continue
   880  			}
   881  			proxyEpSet.Add(host)
   882  
   883  			proxyEps = append(proxyEps, ProxyEndpoint{
   884  				Endpoint:  endpoint,
   885  				Transport: globalProxyTransport,
   886  			})
   887  		}
   888  	}
   889  	return proxyEps
   890  }
   891  
   892  func updateDomainIPs(endPoints set.StringSet) {
   893  	ipList := set.NewStringSet()
   894  	for e := range endPoints {
   895  		host, port, err := net.SplitHostPort(e)
   896  		if err != nil {
   897  			if strings.Contains(err.Error(), "missing port in address") {
   898  				host = e
   899  				port = globalMinioPort
   900  			} else {
   901  				continue
   902  			}
   903  		}
   904  
   905  		if net.ParseIP(host) == nil {
   906  			IPs, err := getHostIP(host)
   907  			if err != nil {
   908  				continue
   909  			}
   910  
   911  			IPsWithPort := IPs.ApplyFunc(func(ip string) string {
   912  				return net.JoinHostPort(ip, port)
   913  			})
   914  
   915  			ipList = ipList.Union(IPsWithPort)
   916  		}
   917  
   918  		ipList.Add(net.JoinHostPort(host, port))
   919  	}
   920  
   921  	globalDomainIPs = ipList.FuncMatch(func(ip string, matchString string) bool {
   922  		host, _, err := net.SplitHostPort(ip)
   923  		if err != nil {
   924  			host = ip
   925  		}
   926  		return !net.ParseIP(host).IsLoopback() && host != "localhost"
   927  	}, "")
   928  }