854 lines · 23.6 KB · Go
RawDownload
// Copyright (c) 2015-2021 MinIO, Inc.//// This file is part of MinIO Object Storage stack//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program.  If not, see <http://www.gnu.org/licenses/>. package cmd import (	"fmt"	"net"	"net/http"	"net/url"	"path"	"path/filepath"	"reflect"	"runtime"	"sort"	"strconv"	"strings"	"time" 	"github.com/dustin/go-humanize"	"github.com/minio/minio-go/v7/pkg/set"	"github.com/minio/minio/internal/config"	"github.com/minio/minio/internal/logger"	"github.com/minio/minio/internal/mountinfo"	"github.com/minio/pkg/env"	xnet "github.com/minio/pkg/net") // EndpointType - enum for endpoint type.type EndpointType int const (	// PathEndpointType - path style endpoint type enum.	PathEndpointType EndpointType = iota + 1 	// URLEndpointType - URL style endpoint type enum.	URLEndpointType) // ProxyEndpoint - endpoint used for proxy redirects// See proxyRequest() for details.type ProxyEndpoint struct {	Endpoint	Transport http.RoundTripper} // Endpoint - any type of endpoint.type Endpoint struct {	*url.URL	IsLocal bool} func (endpoint Endpoint) String() string {	if endpoint.Host == "" {		return endpoint.Path	} 	return endpoint.URL.String()} // Type - returns type of endpoint.func (endpoint Endpoint) Type() EndpointType {	if endpoint.Host == "" {		return PathEndpointType	} 	return URLEndpointType} // HTTPS - returns true if secure for URLEndpointType.func (endpoint Endpoint) HTTPS() bool {	return endpoint.Scheme == "https"} // UpdateIsLocal - resolves the host and updates if it is local or not.func (endpoint *Endpoint) UpdateIsLocal() (err error) {	if !endpoint.IsLocal {		endpoint.IsLocal, err = isLocalHost(endpoint.Hostname(), endpoint.Port(), globalMinioPort)		if err != nil {			return err		}	}	return nil} // NewEndpoint - returns new endpoint based on given arguments.func NewEndpoint(arg string) (ep Endpoint, e error) {	// isEmptyPath - check whether given path is not empty.	isEmptyPath := func(path string) bool {		return path == "" || path == SlashSeparator || path == `\`	} 	if isEmptyPath(arg) {		return ep, fmt.Errorf("empty or root endpoint is not supported")	} 	var isLocal bool	var host string	u, err := url.Parse(arg)	if err == nil && u.Host != "" {		// URL style of endpoint.		// Valid URL style endpoint is		// - Scheme field must contain "http" or "https"		// - All field should be empty except Host and Path.		if !((u.Scheme == "http" || u.Scheme == "https") &&			u.User == nil && u.Opaque == "" && !u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {			return ep, fmt.Errorf("invalid URL endpoint format")		} 		var port string		host, port, err = net.SplitHostPort(u.Host)		if err != nil {			if !strings.Contains(err.Error(), "missing port in address") {				return ep, fmt.Errorf("invalid URL endpoint format: %w", err)			} 			host = u.Host		} else {			var p int			p, err = strconv.Atoi(port)			if err != nil {				return ep, fmt.Errorf("invalid URL endpoint format: invalid port number")			} else if p < 1 || p > 65535 {				return ep, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535")			}		}		if i := strings.Index(host, "%"); i > -1 {			host = host[:i]		} 		if host == "" {			return ep, fmt.Errorf("invalid URL endpoint format: empty host name")		} 		// As this is path in the URL, we should use path package, not filepath package.		// On MS Windows, filepath.Clean() converts into Windows path style ie `/foo` becomes `\foo`		u.Path = path.Clean(u.Path)		if isEmptyPath(u.Path) {			return ep, fmt.Errorf("empty or root path is not supported in URL endpoint")		} 		// On windows having a preceding SlashSeparator will cause problems, if the		// command line already has C:/<export-folder/ in it. Final resulting		// path on windows might become C:/C:/ this will cause problems		// of starting minio server properly in distributed mode on windows.		// As a special case make sure to trim the separator. 		// NOTE: It is also perfectly fine for windows users to have a path		// without C:/ since at that point we treat it as relative path		// and obtain the full filesystem path as well. Providing C:/		// style is necessary to provide paths other than C:/,		// such as F:/, D:/ etc.		//		// Another additional benefit here is that this style also		// supports providing \\host\share support as well.		if runtime.GOOS == globalWindowsOSName {			if filepath.VolumeName(u.Path[1:]) != "" {				u.Path = u.Path[1:]			}		} 	} else {		// Only check if the arg is an ip address and ask for scheme since its absent.		// localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as		// /mnt/export1. So we go ahead and start the minio server in FS modes in these cases.		if isHostIP(arg) {			return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https")		}		absArg, err := filepath.Abs(arg)		if err != nil {			return Endpoint{}, fmt.Errorf("absolute path failed %s", err)		}		u = &url.URL{Path: path.Clean(absArg)}		isLocal = true	} 	return Endpoint{		URL:     u,		IsLocal: isLocal,	}, nil} // PoolEndpoints represent endpoints in a given pool// along with its setCount and setDriveCount.type PoolEndpoints struct {	// indicates if endpoints are provided in non-ellipses style	Legacy       bool	SetCount     int	DrivesPerSet int	Endpoints    Endpoints	CmdLine      string} // EndpointServerPools - list of list of endpointstype EndpointServerPools []PoolEndpoints // GetPoolIdx return pool indexfunc (l EndpointServerPools) GetPoolIdx(pool string) int {	for id, ep := range globalEndpoints {		if ep.CmdLine != pool {			continue		}		return id	}	return -1} // GetLocalPoolIdx returns the pool which endpoint belongs to locally.// if ep is remote this code will return -1 poolIndexfunc (l EndpointServerPools) GetLocalPoolIdx(ep Endpoint) int {	for i, zep := range l {		for _, cep := range zep.Endpoints {			if cep.IsLocal && ep.IsLocal {				if reflect.DeepEqual(cep, ep) {					return i				}			}		}	}	return -1} // Legacy returns 'true' if the MinIO server commandline was// provided with no ellipses pattern, those are considered// legacy deployments.func (l EndpointServerPools) Legacy() bool {	return len(l) == 1 && l[0].Legacy} // Add add pool endpointsfunc (l *EndpointServerPools) Add(zeps PoolEndpoints) error {	existSet := set.NewStringSet()	for _, zep := range *l {		for _, ep := range zep.Endpoints {			existSet.Add(ep.String())		}	}	// Validate if there are duplicate endpoints across serverPools	for _, ep := range zeps.Endpoints {		if existSet.Contains(ep.String()) {			return fmt.Errorf("duplicate endpoints found")		}	}	*l = append(*l, zeps)	return nil} // Localhost - returns the local hostname from list of endpointsfunc (l EndpointServerPools) Localhost() string {	for _, ep := range l {		for _, endpoint := range ep.Endpoints {			if endpoint.IsLocal && endpoint.Host != "" {				u := &url.URL{					Scheme: endpoint.Scheme,					Host:   endpoint.Host,				}				return u.String()			}		}	}	host := globalMinioHost	if host == "" {		host = sortIPs(localIP4.ToSlice())[0]	}	return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))} // LocalDisksPaths returns the disk paths of the local disksfunc (l EndpointServerPools) LocalDisksPaths() []string {	var disks []string	for _, ep := range l {		for _, endpoint := range ep.Endpoints {			if endpoint.IsLocal {				disks = append(disks, endpoint.Path)			}		}	}	return disks} // NLocalDisksPathsPerPool returns the disk paths of the local disks per poolfunc (l EndpointServerPools) NLocalDisksPathsPerPool() []int {	localDisksCount := make([]int, len(l))	for i, ep := range l {		for _, endpoint := range ep.Endpoints {			if endpoint.IsLocal {				localDisksCount[i]++			}		}	}	return localDisksCount} // FirstLocal returns true if the first endpoint is local.func (l EndpointServerPools) FirstLocal() bool {	return l[0].Endpoints[0].IsLocal} // HTTPS - returns true if secure for URLEndpointType.func (l EndpointServerPools) HTTPS() bool {	return l[0].Endpoints.HTTPS()} // NEndpoints - returns number of endpointsfunc (l EndpointServerPools) NEndpoints() (count int) {	for _, ep := range l {		count += len(ep.Endpoints)	}	return count} // Hostnames - returns list of unique hostnamesfunc (l EndpointServerPools) Hostnames() []string {	foundSet := set.NewStringSet()	for _, ep := range l {		for _, endpoint := range ep.Endpoints {			if foundSet.Contains(endpoint.Hostname()) {				continue			}			foundSet.Add(endpoint.Hostname())		}	}	return foundSet.ToSlice()} // hostsSorted will return all hosts found.// The LOCAL host will be nil, but the indexes of all hosts should// remain consistent across the cluster.func (l EndpointServerPools) hostsSorted() []*xnet.Host {	peers, localPeer := l.peers()	sort.Strings(peers)	hosts := make([]*xnet.Host, len(peers))	for i, hostStr := range peers {		if hostStr == localPeer {			continue		}		host, err := xnet.ParseHost(hostStr)		if err != nil {			logger.LogIf(GlobalContext, err)			continue		}		hosts[i] = host	} 	return hosts} // peers will return all peers, including local.// The local peer is returned as a separate string.func (l EndpointServerPools) peers() (peers []string, local string) {	allSet := set.NewStringSet()	for _, ep := range l {		for _, endpoint := range ep.Endpoints {			if endpoint.Type() != URLEndpointType {				continue			} 			peer := endpoint.Host			if endpoint.IsLocal {				if _, port := mustSplitHostPort(peer); port == globalMinioPort {					local = peer				}			} 			allSet.Add(peer)		}	} 	return allSet.ToSlice(), local} // Endpoints - list of same type of endpoint.type Endpoints []Endpoint // HTTPS - returns true if secure for URLEndpointType.func (endpoints Endpoints) HTTPS() bool {	return endpoints[0].HTTPS()} // GetString - returns endpoint string of i-th endpoint (0-based),// and empty string for invalid indexes.func (endpoints Endpoints) GetString(i int) string {	if i < 0 || i >= len(endpoints) {		return ""	}	return endpoints[i].String()} // GetAllStrings - returns allstring of all endpointsfunc (endpoints Endpoints) GetAllStrings() (all []string) {	for _, e := range endpoints {		all = append(all, e.String())	}	return} func (endpoints Endpoints) atleastOneEndpointLocal() bool {	for _, endpoint := range endpoints {		if endpoint.IsLocal {			return true		}	}	return false} // UpdateIsLocal - resolves the host and discovers the local host.func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {	orchestrated := IsDocker() || IsKubernetes() 	var epsResolved int	var foundLocal bool	resolvedList := make([]bool, len(endpoints))	// Mark the starting time	startTime := time.Now()	keepAliveTicker := time.NewTicker(10 * time.Millisecond)	defer keepAliveTicker.Stop()	for {		// Break if the local endpoint is found already Or all the endpoints are resolved.		if foundLocal || (epsResolved == len(endpoints)) {			break		}		// Retry infinitely on Kubernetes and Docker swarm.		// This is needed as the remote hosts are sometime		// not available immediately.		select {		case <-globalOSSignalCh:			return fmt.Errorf("The endpoint resolution got interrupted")		default:			for i, resolved := range resolvedList {				if resolved {					// Continue if host is already resolved.					continue				} 				// Log the message to console about the host resolving				reqInfo := (&logger.ReqInfo{}).AppendTags(					"host",					endpoints[i].Hostname(),				) 				// return err if not Docker or Kubernetes				// We use IsDocker() to check for Docker environment				// We use IsKubernetes() to check for Kubernetes environment				isLocal, err := isLocalHost(endpoints[i].Hostname(),					endpoints[i].Port(),					globalMinioPort,				)				if err != nil && !orchestrated {					return err				}				if err != nil {					// time elapsed					timeElapsed := time.Since(startTime)					// log error only if more than 1s elapsed					if timeElapsed > time.Second {						reqInfo.AppendTags("elapsedTime",							humanize.RelTime(startTime,								startTime.Add(timeElapsed),								"elapsed",								"",							))						ctx := logger.SetReqInfo(GlobalContext,							reqInfo)						logger.LogIf(ctx, err, logger.Application)					}				} else {					resolvedList[i] = true					endpoints[i].IsLocal = isLocal					epsResolved++					if !foundLocal {						foundLocal = isLocal					}				}			} 			// Wait for the tick, if the there exist a local endpoint in discovery.			// Non docker/kubernetes environment we do not need to wait.			if !foundLocal && orchestrated {				<-keepAliveTicker.C			}		}	} 	// On Kubernetes/Docker setups DNS resolves inappropriately sometimes	// where there are situations same endpoints with multiple disks	// come online indicating either one of them is local and some

Lines 1–500 of 854.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t