2021-04-18 15:41:13 -04:00
|
|
|
// 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/>.
|
2017-04-11 18:44:27 -04:00
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2020-07-02 13:56:22 -04:00
|
|
|
"net/http"
|
2017-04-11 18:44:27 -04:00
|
|
|
"net/url"
|
|
|
|
"path"
|
2017-08-15 18:10:50 -04:00
|
|
|
"path/filepath"
|
2020-09-04 20:09:02 -04:00
|
|
|
"reflect"
|
2017-11-11 21:39:53 -05:00
|
|
|
"runtime"
|
2020-10-28 12:18:35 -04:00
|
|
|
"sort"
|
2017-04-11 18:44:27 -04:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-04-19 13:26:44 -04:00
|
|
|
"time"
|
2017-04-11 18:44:27 -04:00
|
|
|
|
2020-10-28 12:18:35 -04:00
|
|
|
"github.com/dustin/go-humanize"
|
2020-07-14 12:38:05 -04:00
|
|
|
"github.com/minio/minio-go/v7/pkg/set"
|
2021-06-01 17:59:40 -04:00
|
|
|
"github.com/minio/minio/internal/config"
|
|
|
|
"github.com/minio/minio/internal/logger"
|
|
|
|
"github.com/minio/minio/internal/mountinfo"
|
2023-09-04 15:57:37 -04:00
|
|
|
"github.com/minio/pkg/v2/env"
|
|
|
|
xnet "github.com/minio/pkg/v2/net"
|
2023-06-23 14:48:23 -04:00
|
|
|
"golang.org/x/exp/slices"
|
2017-04-11 18:44:27 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
)
|
|
|
|
|
2020-07-03 14:53:03 -04:00
|
|
|
// ProxyEndpoint - endpoint used for proxy redirects
|
|
|
|
// See proxyRequest() for details.
|
|
|
|
type ProxyEndpoint struct {
|
|
|
|
Endpoint
|
2020-12-22 00:42:38 -05:00
|
|
|
Transport http.RoundTripper
|
2020-07-02 13:56:22 -04:00
|
|
|
}
|
|
|
|
|
2023-06-23 14:48:23 -04:00
|
|
|
// Node holds information about a node in this cluster
|
|
|
|
type Node struct {
|
|
|
|
*url.URL
|
|
|
|
Pools []int
|
|
|
|
IsLocal bool
|
|
|
|
}
|
|
|
|
|
2017-04-11 18:44:27 -04:00
|
|
|
// Endpoint - any type of endpoint.
|
|
|
|
type Endpoint struct {
|
|
|
|
*url.URL
|
2020-01-10 05:35:06 -05:00
|
|
|
IsLocal bool
|
2023-07-20 10:48:21 -04:00
|
|
|
|
|
|
|
PoolIdx, SetIdx, DiskIdx int
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-19 20:42:27 -05:00
|
|
|
// HTTPS - returns true if secure for URLEndpointType.
|
|
|
|
func (endpoint Endpoint) HTTPS() bool {
|
2017-12-04 01:47:12 -05:00
|
|
|
return endpoint.Scheme == "https"
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
perf: websocket grid connectivity for all internode communication (#18461)
This PR adds a WebSocket grid feature that allows servers to communicate via
a single two-way connection.
There are two request types:
* Single requests, which are `[]byte => ([]byte, error)`. This is for efficient small
roundtrips with small payloads.
* Streaming requests which are `[]byte, chan []byte => chan []byte (and error)`,
which allows for different combinations of full two-way streams with an initial payload.
Only a single stream is created between two machines - and there is, as such, no
server/client relation since both sides can initiate and handle requests. Which server
initiates the request is decided deterministically on the server names.
Requests are made through a mux client and server, which handles message
passing, congestion, cancelation, timeouts, etc.
If a connection is lost, all requests are canceled, and the calling server will try
to reconnect. Registered handlers can operate directly on byte
slices or use a higher-level generics abstraction.
There is no versioning of handlers/clients, and incompatible changes should
be handled by adding new handlers.
The request path can be changed to a new one for any protocol changes.
First, all servers create a "Manager." The manager must know its address
as well as all remote addresses. This will manage all connections.
To get a connection to any remote, ask the manager to provide it given
the remote address using.
```
func (m *Manager) Connection(host string) *Connection
```
All serverside handlers must also be registered on the manager. This will
make sure that all incoming requests are served. The number of in-flight
requests and responses must also be given for streaming requests.
The "Connection" returned manages the mux-clients. Requests issued
to the connection will be sent to the remote.
* `func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error)`
performs a single request and returns the result. Any deadline provided on the request is
forwarded to the server, and canceling the context will make the function return at once.
* `func (c *Connection) NewStream(ctx context.Context, h HandlerID, payload []byte) (st *Stream, err error)`
will initiate a remote call and send the initial payload.
```Go
// A Stream is a two-way stream.
// All responses *must* be read by the caller.
// If the call is canceled through the context,
//The appropriate error will be returned.
type Stream struct {
// Responses from the remote server.
// Channel will be closed after an error or when the remote closes.
// All responses *must* be read by the caller until either an error is returned or the channel is closed.
// Canceling the context will cause the context cancellation error to be returned.
Responses <-chan Response
// Requests sent to the server.
// If the handler is defined with 0 incoming capacity this will be nil.
// Channel *must* be closed to signal the end of the stream.
// If the request context is canceled, the stream will no longer process requests.
Requests chan<- []byte
}
type Response struct {
Msg []byte
Err error
}
```
There are generic versions of the server/client handlers that allow the use of type
safe implementations for data types that support msgpack marshal/unmarshal.
2023-11-20 20:09:35 -05:00
|
|
|
// GridHost returns the host to be used for grid connections.
|
|
|
|
func (endpoint Endpoint) GridHost() string {
|
|
|
|
return fmt.Sprintf("%s://%s", endpoint.Scheme, endpoint.Host)
|
|
|
|
}
|
|
|
|
|
2019-04-19 13:26:44 -04:00
|
|
|
// UpdateIsLocal - resolves the host and updates if it is local or not.
|
2019-11-26 14:42:10 -05:00
|
|
|
func (endpoint *Endpoint) UpdateIsLocal() (err error) {
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoint.Host != "" {
|
2019-11-26 14:42:10 -05:00
|
|
|
endpoint.IsLocal, err = isLocalHost(endpoint.Hostname(), endpoint.Port(), globalMinioPort)
|
2019-04-19 13:26:44 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-20 10:48:21 -04:00
|
|
|
// SetPoolIndex sets a specific pool number to this node
|
|
|
|
func (endpoint *Endpoint) SetPoolIndex(i int) {
|
|
|
|
endpoint.PoolIdx = i
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSetIndex sets a specific set number to this node
|
|
|
|
func (endpoint *Endpoint) SetSetIndex(i int) {
|
|
|
|
endpoint.SetIdx = i
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetDiskIndex sets a specific disk number to this node
|
|
|
|
func (endpoint *Endpoint) SetDiskIndex(i int) {
|
|
|
|
endpoint.DiskIdx = i
|
2023-06-23 14:48:23 -04:00
|
|
|
}
|
|
|
|
|
2017-04-11 18:44:27 -04:00
|
|
|
// NewEndpoint - returns new endpoint based on given arguments.
|
2017-06-21 22:53:09 -04:00
|
|
|
func NewEndpoint(arg string) (ep Endpoint, e error) {
|
2017-04-11 18:44:27 -04:00
|
|
|
// isEmptyPath - check whether given path is not empty.
|
|
|
|
isEmptyPath := func(path string) bool {
|
2019-08-06 15:08:58 -04:00
|
|
|
return path == "" || path == SlashSeparator || path == `\`
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if isEmptyPath(arg) {
|
2017-06-21 22:53:09 -04:00
|
|
|
return ep, fmt.Errorf("empty or root endpoint is not supported")
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var isLocal bool
|
2019-04-19 13:26:44 -04:00
|
|
|
var host string
|
2017-04-11 18:44:27 -04:00
|
|
|
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") &&
|
2019-02-13 07:59:36 -05:00
|
|
|
u.User == nil && u.Opaque == "" && !u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
|
2017-06-21 22:53:09 -04:00
|
|
|
return ep, fmt.Errorf("invalid URL endpoint format")
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
2019-04-19 13:26:44 -04:00
|
|
|
var port string
|
2017-08-15 18:10:50 -04:00
|
|
|
host, port, err = net.SplitHostPort(u.Host)
|
2017-04-11 18:44:27 -04:00
|
|
|
if err != nil {
|
|
|
|
if !strings.Contains(err.Error(), "missing port in address") {
|
2019-12-02 12:28:01 -05:00
|
|
|
return ep, fmt.Errorf("invalid URL endpoint format: %w", err)
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
host = u.Host
|
|
|
|
} else {
|
|
|
|
var p int
|
|
|
|
p, err = strconv.Atoi(port)
|
|
|
|
if err != nil {
|
2017-06-21 22:53:09 -04:00
|
|
|
return ep, fmt.Errorf("invalid URL endpoint format: invalid port number")
|
2017-04-11 18:44:27 -04:00
|
|
|
} else if p < 1 || p > 65535 {
|
2017-06-21 22:53:09 -04:00
|
|
|
return ep, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535")
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
}
|
2018-12-14 02:37:46 -05:00
|
|
|
if i := strings.Index(host, "%"); i > -1 {
|
|
|
|
host = host[:i]
|
|
|
|
}
|
2017-04-11 18:44:27 -04:00
|
|
|
|
|
|
|
if host == "" {
|
2017-06-21 22:53:09 -04:00
|
|
|
return ep, fmt.Errorf("invalid URL endpoint format: empty host name")
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2017-06-21 22:53:09 -04:00
|
|
|
return ep, fmt.Errorf("empty or root path is not supported in URL endpoint")
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
2019-08-06 15:08:58 -04:00
|
|
|
// On windows having a preceding SlashSeparator will cause problems, if the
|
2017-11-11 21:39:53 -05:00
|
|
|
// 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:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-11 18:44:27 -04:00
|
|
|
} else {
|
2017-08-10 19:54:19 -04:00
|
|
|
// 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.
|
2018-12-14 02:37:46 -05:00
|
|
|
if isHostIP(arg) {
|
2017-08-10 19:54:19 -04:00
|
|
|
return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https")
|
|
|
|
}
|
2020-03-24 21:53:24 -04:00
|
|
|
absArg, err := filepath.Abs(arg)
|
|
|
|
if err != nil {
|
|
|
|
return Endpoint{}, fmt.Errorf("absolute path failed %s", err)
|
|
|
|
}
|
|
|
|
u = &url.URL{Path: path.Clean(absArg)}
|
2017-04-11 18:44:27 -04:00
|
|
|
isLocal = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return Endpoint{
|
2019-11-19 20:42:27 -05:00
|
|
|
URL: u,
|
|
|
|
IsLocal: isLocal,
|
2023-07-20 10:48:21 -04:00
|
|
|
PoolIdx: -1,
|
|
|
|
SetIdx: -1,
|
|
|
|
DiskIdx: -1,
|
2017-04-11 18:44:27 -04:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:47:42 -05:00
|
|
|
// PoolEndpoints represent endpoints in a given pool
|
2020-08-26 22:29:35 -04:00
|
|
|
// along with its setCount and setDriveCount.
|
2021-01-26 23:47:42 -05:00
|
|
|
type PoolEndpoints struct {
|
2022-01-10 12:07:49 -05:00
|
|
|
// indicates if endpoints are provided in non-ellipses style
|
|
|
|
Legacy bool
|
2019-11-19 20:42:27 -05:00
|
|
|
SetCount int
|
|
|
|
DrivesPerSet int
|
|
|
|
Endpoints Endpoints
|
2022-01-10 12:07:49 -05:00
|
|
|
CmdLine string
|
2023-01-23 05:54:50 -05:00
|
|
|
Platform string
|
2019-11-19 20:42:27 -05:00
|
|
|
}
|
2017-04-11 18:44:27 -04:00
|
|
|
|
2020-12-01 16:50:33 -05:00
|
|
|
// EndpointServerPools - list of list of endpoints
|
2021-01-26 23:47:42 -05:00
|
|
|
type EndpointServerPools []PoolEndpoints
|
2019-11-19 20:42:27 -05:00
|
|
|
|
2023-06-23 14:48:23 -04:00
|
|
|
// GetNodes returns a sorted list of nodes in this cluster
|
|
|
|
func (l EndpointServerPools) GetNodes() (nodes []Node) {
|
|
|
|
nodesMap := make(map[string]Node)
|
|
|
|
for _, pool := range l {
|
|
|
|
for _, ep := range pool.Endpoints {
|
|
|
|
node, ok := nodesMap[ep.Host]
|
|
|
|
if !ok {
|
|
|
|
node.IsLocal = ep.IsLocal
|
|
|
|
node.URL = &url.URL{
|
|
|
|
Scheme: ep.Scheme,
|
|
|
|
Host: ep.Host,
|
|
|
|
}
|
|
|
|
}
|
2023-07-20 10:48:21 -04:00
|
|
|
if !slices.Contains(node.Pools, ep.PoolIdx) {
|
|
|
|
node.Pools = append(node.Pools, ep.PoolIdx)
|
2023-06-23 14:48:23 -04:00
|
|
|
}
|
|
|
|
nodesMap[ep.Host] = node
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nodes = make([]Node, 0, len(nodesMap))
|
|
|
|
for _, v := range nodesMap {
|
|
|
|
nodes = append(nodes, v)
|
|
|
|
}
|
|
|
|
sort.Slice(nodes, func(i, j int) bool {
|
|
|
|
return nodes[i].Host < nodes[j].Host
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-10 12:07:49 -05:00
|
|
|
// GetPoolIdx return pool index
|
|
|
|
func (l EndpointServerPools) GetPoolIdx(pool string) int {
|
|
|
|
for id, ep := range globalEndpoints {
|
|
|
|
if ep.CmdLine != pool {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return id
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:47:42 -05:00
|
|
|
// GetLocalPoolIdx returns the pool which endpoint belongs to locally.
|
2021-01-06 12:35:47 -05:00
|
|
|
// if ep is remote this code will return -1 poolIndex
|
2021-01-26 23:47:42 -05:00
|
|
|
func (l EndpointServerPools) GetLocalPoolIdx(ep Endpoint) int {
|
2020-09-04 20:09:02 -04:00
|
|
|
for i, zep := range l {
|
|
|
|
for _, cep := range zep.Endpoints {
|
|
|
|
if cep.IsLocal && ep.IsLocal {
|
|
|
|
if reflect.DeepEqual(cep, ep) {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
2022-01-10 12:07:49 -05:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2020-09-04 20:09:02 -04:00
|
|
|
|
2021-01-06 12:35:47 -05:00
|
|
|
// Add add pool endpoints
|
2021-01-26 23:47:42 -05:00
|
|
|
func (l *EndpointServerPools) Add(zeps PoolEndpoints) error {
|
2020-01-07 12:13:44 -05:00
|
|
|
existSet := set.NewStringSet()
|
|
|
|
for _, zep := range *l {
|
|
|
|
for _, ep := range zep.Endpoints {
|
|
|
|
existSet.Add(ep.String())
|
|
|
|
}
|
|
|
|
}
|
2020-12-01 16:50:33 -05:00
|
|
|
// Validate if there are duplicate endpoints across serverPools
|
2020-01-07 12:13:44 -05:00
|
|
|
for _, ep := range zeps.Endpoints {
|
|
|
|
if existSet.Contains(ep.String()) {
|
|
|
|
return fmt.Errorf("duplicate endpoints found")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*l = append(*l, zeps)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-12-10 10:28:37 -05:00
|
|
|
// Localhost - returns the local hostname from list of endpoints
|
|
|
|
func (l EndpointServerPools) Localhost() string {
|
|
|
|
for _, ep := range l {
|
|
|
|
for _, endpoint := range ep.Endpoints {
|
2021-07-21 17:51:16 -04:00
|
|
|
if endpoint.IsLocal && endpoint.Host != "" {
|
2020-12-10 10:28:37 -05:00
|
|
|
u := &url.URL{
|
|
|
|
Scheme: endpoint.Scheme,
|
|
|
|
Host: endpoint.Host,
|
|
|
|
}
|
|
|
|
return u.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-21 17:51:16 -04:00
|
|
|
host := globalMinioHost
|
|
|
|
if host == "" {
|
|
|
|
host = sortIPs(localIP4.ToSlice())[0]
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))
|
2020-12-10 10:28:37 -05:00
|
|
|
}
|
|
|
|
|
2021-06-05 12:10:32 -04:00
|
|
|
// LocalDisksPaths returns the disk paths of the local disks
|
|
|
|
func (l EndpointServerPools) LocalDisksPaths() []string {
|
|
|
|
var disks []string
|
2021-04-19 13:30:42 -04:00
|
|
|
for _, ep := range l {
|
|
|
|
for _, endpoint := range ep.Endpoints {
|
|
|
|
if endpoint.IsLocal {
|
2021-06-05 12:10:32 -04:00
|
|
|
disks = append(disks, endpoint.Path)
|
2021-04-19 13:30:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-05 12:10:32 -04:00
|
|
|
return disks
|
2021-04-19 13:30:42 -04:00
|
|
|
}
|
|
|
|
|
2022-07-29 03:00:06 -04:00
|
|
|
// NLocalDisksPathsPerPool returns the disk paths of the local disks per pool
|
|
|
|
func (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
|
|
|
|
}
|
|
|
|
|
2019-11-22 15:45:13 -05:00
|
|
|
// FirstLocal returns true if the first endpoint is local.
|
2020-12-01 16:50:33 -05:00
|
|
|
func (l EndpointServerPools) FirstLocal() bool {
|
2019-11-21 07:24:51 -05:00
|
|
|
return l[0].Endpoints[0].IsLocal
|
|
|
|
}
|
|
|
|
|
2019-11-19 20:42:27 -05:00
|
|
|
// HTTPS - returns true if secure for URLEndpointType.
|
2020-12-01 16:50:33 -05:00
|
|
|
func (l EndpointServerPools) HTTPS() bool {
|
2019-11-19 20:42:27 -05:00
|
|
|
return l[0].Endpoints.HTTPS()
|
|
|
|
}
|
|
|
|
|
2022-07-30 01:21:40 -04:00
|
|
|
// NEndpoints - returns number of endpoints
|
2020-12-01 16:50:33 -05:00
|
|
|
func (l EndpointServerPools) NEndpoints() (count int) {
|
2019-11-19 20:42:27 -05:00
|
|
|
for _, ep := range l {
|
|
|
|
count += len(ep.Endpoints)
|
2018-09-17 21:28:34 -04:00
|
|
|
}
|
2019-11-19 20:42:27 -05:00
|
|
|
return count
|
2018-09-17 21:28:34 -04:00
|
|
|
}
|
|
|
|
|
2020-07-20 15:28:48 -04:00
|
|
|
// Hostnames - returns list of unique hostnames
|
2020-12-01 16:50:33 -05:00
|
|
|
func (l EndpointServerPools) Hostnames() []string {
|
2020-03-24 15:43:40 -04:00
|
|
|
foundSet := set.NewStringSet()
|
|
|
|
for _, ep := range l {
|
|
|
|
for _, endpoint := range ep.Endpoints {
|
2020-07-20 15:28:48 -04:00
|
|
|
if foundSet.Contains(endpoint.Hostname()) {
|
2020-03-24 15:43:40 -04:00
|
|
|
continue
|
|
|
|
}
|
2020-07-20 15:28:48 -04:00
|
|
|
foundSet.Add(endpoint.Hostname())
|
2020-03-24 15:43:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return foundSet.ToSlice()
|
|
|
|
}
|
|
|
|
|
2020-10-28 12:18:35 -04:00
|
|
|
// hostsSorted will return all hosts found.
|
|
|
|
// The LOCAL host will be nil, but the indexes of all hosts should
|
|
|
|
// remain consistent across the cluster.
|
2020-12-01 16:50:33 -05:00
|
|
|
func (l EndpointServerPools) hostsSorted() []*xnet.Host {
|
2020-10-28 12:18:35 -04:00
|
|
|
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.
|
2020-12-01 16:50:33 -05:00
|
|
|
func (l EndpointServerPools) peers() (peers []string, local string) {
|
2020-10-28 12:18:35 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-19 20:42:27 -05:00
|
|
|
// 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()
|
2017-04-11 18:44:27 -04:00
|
|
|
}
|
|
|
|
|
2018-01-22 17:54:55 -05:00
|
|
|
// GetString - returns endpoint string of i-th endpoint (0-based),
|
|
|
|
// and empty string for invalid indexes.
|
2019-11-19 20:42:27 -05:00
|
|
|
func (endpoints Endpoints) GetString(i int) string {
|
2018-01-22 17:54:55 -05:00
|
|
|
if i < 0 || i >= len(endpoints) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return endpoints[i].String()
|
|
|
|
}
|
|
|
|
|
2020-12-21 12:35:19 -05:00
|
|
|
// GetAllStrings - returns allstring of all endpoints
|
|
|
|
func (endpoints Endpoints) GetAllStrings() (all []string) {
|
|
|
|
for _, e := range endpoints {
|
|
|
|
all = append(all, e.String())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-05 05:32:43 -05:00
|
|
|
func hostResolveToLocalhost(endpoint Endpoint) bool {
|
|
|
|
hostIPs, err := getHostIP(endpoint.Hostname())
|
|
|
|
if err != nil {
|
|
|
|
// Log the message to console about the host resolving
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags(
|
|
|
|
"host",
|
|
|
|
endpoint.Hostname(),
|
|
|
|
)
|
|
|
|
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
|
|
|
|
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
var loopback int
|
|
|
|
for _, hostIP := range hostIPs.ToSlice() {
|
|
|
|
if net.ParseIP(hostIP).IsLoopback() {
|
|
|
|
loopback++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return loopback == len(hostIPs)
|
|
|
|
}
|
|
|
|
|
2019-04-19 13:26:44 -04:00
|
|
|
// UpdateIsLocal - resolves the host and discovers the local host.
|
2023-05-11 20:41:33 -04:00
|
|
|
func (endpoints Endpoints) UpdateIsLocal() error {
|
2019-12-10 23:28:22 -05:00
|
|
|
orchestrated := IsDocker() || IsKubernetes()
|
|
|
|
|
2019-04-19 13:26:44 -04:00
|
|
|
var epsResolved int
|
|
|
|
var foundLocal bool
|
|
|
|
resolvedList := make([]bool, len(endpoints))
|
|
|
|
// Mark the starting time
|
|
|
|
startTime := time.Now()
|
2023-05-11 20:41:33 -04:00
|
|
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
2019-04-19 13:26:44 -04:00
|
|
|
defer keepAliveTicker.Stop()
|
|
|
|
for {
|
2019-10-10 00:44:17 -04:00
|
|
|
// Break if the local endpoint is found already Or all the endpoints are resolved.
|
2019-04-19 13:26:44 -04:00
|
|
|
if foundLocal || (epsResolved == len(endpoints)) {
|
|
|
|
break
|
|
|
|
}
|
2023-05-11 20:41:33 -04:00
|
|
|
|
2019-04-19 13:26:44 -04:00
|
|
|
// 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 {
|
2019-10-10 00:44:17 -04:00
|
|
|
// Continue if host is already resolved.
|
2019-04-19 13:26:44 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoints[i].Host == "" {
|
|
|
|
resolvedList[i] = true
|
|
|
|
endpoints[i].IsLocal = true
|
|
|
|
epsResolved++
|
|
|
|
if !foundLocal {
|
|
|
|
foundLocal = true
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-12-10 23:28:22 -05:00
|
|
|
// Log the message to console about the host resolving
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags(
|
|
|
|
"host",
|
|
|
|
endpoints[i].Hostname(),
|
|
|
|
)
|
|
|
|
|
2023-01-05 05:32:43 -05:00
|
|
|
if orchestrated && hostResolveToLocalhost(endpoints[i]) {
|
|
|
|
// time elapsed
|
|
|
|
timeElapsed := time.Since(startTime)
|
|
|
|
// log error only if more than a second has elapsed
|
|
|
|
if timeElapsed > time.Second {
|
|
|
|
reqInfo.AppendTags("elapsedTime",
|
|
|
|
humanize.RelTime(startTime,
|
|
|
|
startTime.Add(timeElapsed),
|
|
|
|
"elapsed",
|
|
|
|
"",
|
|
|
|
))
|
|
|
|
ctx := logger.SetReqInfo(GlobalContext,
|
|
|
|
reqInfo)
|
|
|
|
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
|
|
|
|
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.Application)
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-04-19 13:26:44 -04:00
|
|
|
// return err if not Docker or Kubernetes
|
2019-10-10 00:44:17 -04:00
|
|
|
// We use IsDocker() to check for Docker environment
|
|
|
|
// We use IsKubernetes() to check for Kubernetes environment
|
2019-12-10 23:28:22 -05:00
|
|
|
isLocal, err := isLocalHost(endpoints[i].Hostname(),
|
|
|
|
endpoints[i].Port(),
|
|
|
|
globalMinioPort,
|
|
|
|
)
|
|
|
|
if err != nil && !orchestrated {
|
|
|
|
return err
|
|
|
|
}
|
2019-04-19 13:26:44 -04:00
|
|
|
if err != nil {
|
|
|
|
// time elapsed
|
|
|
|
timeElapsed := time.Since(startTime)
|
2023-01-05 05:32:43 -05:00
|
|
|
// log error only if more than a second has elapsed
|
2019-04-19 13:26:44 -04:00
|
|
|
if timeElapsed > time.Second {
|
2019-11-19 20:42:27 -05:00
|
|
|
reqInfo.AppendTags("elapsedTime",
|
2019-12-10 23:28:22 -05:00
|
|
|
humanize.RelTime(startTime,
|
|
|
|
startTime.Add(timeElapsed),
|
|
|
|
"elapsed",
|
|
|
|
"",
|
|
|
|
))
|
2020-04-09 12:30:02 -04:00
|
|
|
ctx := logger.SetReqInfo(GlobalContext,
|
2019-12-10 23:28:22 -05:00
|
|
|
reqInfo)
|
2023-01-05 05:32:43 -05:00
|
|
|
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.Application)
|
2019-04-19 13:26:44 -04:00
|
|
|
}
|
|
|
|
} 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.
|
2019-10-10 00:44:17 -04:00
|
|
|
// Non docker/kubernetes environment we do not need to wait.
|
2019-12-10 23:28:22 -05:00
|
|
|
if !foundLocal && orchestrated {
|
2019-04-19 13:26:44 -04:00
|
|
|
<-keepAliveTicker.C
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-10 00:44:17 -04:00
|
|
|
// 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
|
|
|
|
// of them are not local. This situation can never happen and
|
|
|
|
// its only a possibility in orchestrated deployments with dynamic
|
|
|
|
// DNS. Following code ensures that we treat if one of the endpoint
|
|
|
|
// says its local for a given host - it is true for all endpoints
|
|
|
|
// for the same host. Following code ensures that this assumption
|
|
|
|
// is true and it works in all scenarios and it is safe to assume
|
|
|
|
// for a given host.
|
|
|
|
endpointLocalMap := make(map[string]bool)
|
|
|
|
for _, ep := range endpoints {
|
|
|
|
if ep.IsLocal {
|
|
|
|
endpointLocalMap[ep.Host] = ep.IsLocal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := range endpoints {
|
|
|
|
endpoints[i].IsLocal = endpointLocalMap[endpoints[i].Host]
|
|
|
|
}
|
2019-04-19 13:26:44 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-19 20:42:27 -05:00
|
|
|
// NewEndpoints - returns new endpoint list based on input args.
|
|
|
|
func NewEndpoints(args ...string) (endpoints Endpoints, err error) {
|
2017-04-11 18:44:27 -04:00
|
|
|
var endpointType EndpointType
|
|
|
|
var scheme string
|
|
|
|
|
|
|
|
uniqueArgs := set.NewStringSet()
|
|
|
|
// Loop through args and adds to endpoint list.
|
|
|
|
for i, arg := range args {
|
|
|
|
endpoint, err := NewEndpoint(arg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("'%s': %s", arg, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// All endpoints have to be same type and scheme if applicable.
|
2021-11-16 12:28:29 -05:00
|
|
|
//nolint:gocritic
|
2017-04-11 18:44:27 -04:00
|
|
|
if i == 0 {
|
|
|
|
endpointType = endpoint.Type()
|
|
|
|
scheme = endpoint.Scheme
|
|
|
|
} else if endpoint.Type() != endpointType {
|
|
|
|
return nil, fmt.Errorf("mixed style endpoints are not supported")
|
|
|
|
} else if endpoint.Scheme != scheme {
|
|
|
|
return nil, fmt.Errorf("mixed scheme is not supported")
|
|
|
|
}
|
|
|
|
|
|
|
|
arg = endpoint.String()
|
|
|
|
if uniqueArgs.Contains(arg) {
|
|
|
|
return nil, fmt.Errorf("duplicate endpoints found")
|
|
|
|
}
|
|
|
|
uniqueArgs.Add(arg)
|
|
|
|
endpoints = append(endpoints, endpoint)
|
|
|
|
}
|
2019-04-19 13:26:44 -04:00
|
|
|
|
2017-04-11 18:44:27 -04:00
|
|
|
return endpoints, nil
|
|
|
|
}
|
|
|
|
|
2017-08-15 18:10:50 -04:00
|
|
|
// Checks if there are any cross device mounts.
|
2019-11-19 20:42:27 -05:00
|
|
|
func checkCrossDeviceMounts(endpoints Endpoints) (err error) {
|
2017-08-15 18:10:50 -04:00
|
|
|
var absPaths []string
|
|
|
|
for _, endpoint := range endpoints {
|
|
|
|
if endpoint.IsLocal {
|
|
|
|
var absPath string
|
|
|
|
absPath, err = filepath.Abs(endpoint.Path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
absPaths = append(absPaths, absPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mountinfo.CheckCrossDevice(absPaths)
|
|
|
|
}
|
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
// PoolEndpointList is a temporary type to holds the list of endpoints
|
|
|
|
type PoolEndpointList []Endpoints
|
|
|
|
|
|
|
|
// UpdateIsLocal - resolves all hosts and discovers which are local
|
|
|
|
func (p PoolEndpointList) UpdateIsLocal() error {
|
|
|
|
orchestrated := IsDocker() || IsKubernetes()
|
|
|
|
|
|
|
|
var epsResolved int
|
|
|
|
var epCount int
|
|
|
|
|
|
|
|
for _, endpoints := range p {
|
|
|
|
epCount += len(endpoints)
|
|
|
|
}
|
|
|
|
|
|
|
|
var foundLocal bool
|
|
|
|
resolvedList := make(map[Endpoint]bool)
|
|
|
|
|
|
|
|
// Mark the starting time
|
|
|
|
startTime := time.Now()
|
|
|
|
keepAliveTicker := time.NewTicker(1 * time.Second)
|
|
|
|
defer keepAliveTicker.Stop()
|
|
|
|
for {
|
|
|
|
// Break if the local endpoint is found already Or all the endpoints are resolved.
|
|
|
|
if foundLocal || (epsResolved == epCount) {
|
|
|
|
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, endpoints := range p {
|
|
|
|
for j, endpoint := range endpoints {
|
|
|
|
if resolvedList[endpoint] {
|
|
|
|
// Continue if host is already resolved.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoint.Host == "" {
|
|
|
|
if !foundLocal {
|
|
|
|
foundLocal = true
|
|
|
|
}
|
|
|
|
endpoint.IsLocal = true
|
|
|
|
endpoints[j] = endpoint
|
|
|
|
epsResolved++
|
|
|
|
resolvedList[endpoint] = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
// Log the message to console about the host resolving
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags(
|
|
|
|
"host",
|
|
|
|
endpoint.Hostname(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if orchestrated && hostResolveToLocalhost(endpoint) {
|
|
|
|
// time elapsed
|
|
|
|
timeElapsed := time.Since(startTime)
|
|
|
|
// log error only if more than a second has elapsed
|
|
|
|
if timeElapsed > time.Second {
|
|
|
|
reqInfo.AppendTags("elapsedTime",
|
|
|
|
humanize.RelTime(startTime,
|
|
|
|
startTime.Add(timeElapsed),
|
|
|
|
"elapsed",
|
|
|
|
"",
|
|
|
|
))
|
|
|
|
ctx := logger.SetReqInfo(GlobalContext,
|
|
|
|
reqInfo)
|
|
|
|
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
|
|
|
|
endpoint.Hostname()), endpoint.Hostname(), logger.Application)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(endpoint.Hostname(),
|
|
|
|
endpoint.Port(),
|
|
|
|
globalMinioPort,
|
|
|
|
)
|
|
|
|
if err != nil && !orchestrated {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
// time elapsed
|
|
|
|
timeElapsed := time.Since(startTime)
|
|
|
|
// log error only if more than a second has elapsed
|
|
|
|
if timeElapsed > time.Second {
|
|
|
|
reqInfo.AppendTags("elapsedTime",
|
|
|
|
humanize.RelTime(startTime,
|
|
|
|
startTime.Add(timeElapsed),
|
|
|
|
"elapsed",
|
|
|
|
"",
|
|
|
|
))
|
|
|
|
ctx := logger.SetReqInfo(GlobalContext,
|
|
|
|
reqInfo)
|
|
|
|
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
resolvedList[endpoint] = true
|
|
|
|
endpoint.IsLocal = isLocal
|
|
|
|
epsResolved++
|
|
|
|
if !foundLocal {
|
|
|
|
foundLocal = isLocal
|
|
|
|
}
|
|
|
|
endpoints[j] = endpoint
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p[i] = endpoints
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// of them are not local. This situation can never happen and
|
|
|
|
// its only a possibility in orchestrated deployments with dynamic
|
|
|
|
// DNS. Following code ensures that we treat if one of the endpoint
|
|
|
|
// says its local for a given host - it is true for all endpoints
|
|
|
|
// for the same host. Following code ensures that this assumption
|
|
|
|
// is true and it works in all scenarios and it is safe to assume
|
|
|
|
// for a given host.
|
|
|
|
for i, endpoints := range p {
|
|
|
|
endpointLocalMap := make(map[string]bool)
|
|
|
|
for _, ep := range endpoints {
|
|
|
|
if ep.IsLocal {
|
|
|
|
endpointLocalMap[ep.Host] = ep.IsLocal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := range endpoints {
|
|
|
|
endpoints[i].IsLocal = endpointLocalMap[endpoints[i].Host]
|
|
|
|
}
|
|
|
|
p[i] = endpoints
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-07 04:33:56 -05:00
|
|
|
func isEmptyLayout(poolsLayout ...poolDisksLayout) bool {
|
|
|
|
return len(poolsLayout) == 0 || len(poolsLayout[0].layout) == 0 || len(poolsLayout[0].layout[0]) == 0 || len(poolsLayout[0].layout[0][0]) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func isSingleDriveLayout(poolsLayout ...poolDisksLayout) bool {
|
|
|
|
return len(poolsLayout) == 1 && len(poolsLayout[0].layout) == 1 && len(poolsLayout[0].layout[0]) == 1
|
|
|
|
}
|
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
// CreatePoolEndpoints creates a list of endpoints per pool, resolves their relevant hostnames and
|
|
|
|
// discovers those are local or remote.
|
2023-12-07 04:33:56 -05:00
|
|
|
func CreatePoolEndpoints(serverAddr string, poolsLayout ...poolDisksLayout) ([]Endpoints, SetupType, error) {
|
2023-05-11 20:41:33 -04:00
|
|
|
var setupType SetupType
|
|
|
|
|
2023-12-07 04:33:56 -05:00
|
|
|
if isEmptyLayout(poolsLayout...) {
|
|
|
|
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg("invalid number of endpoints")
|
|
|
|
}
|
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
// Check whether serverAddr is valid for this host.
|
|
|
|
if err := CheckLocalServerAddr(serverAddr); err != nil {
|
|
|
|
return nil, setupType, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, serverAddrPort := mustSplitHostPort(serverAddr)
|
|
|
|
|
2023-12-07 04:33:56 -05:00
|
|
|
poolEndpoints := make(PoolEndpointList, len(poolsLayout))
|
2023-05-11 20:41:33 -04:00
|
|
|
|
|
|
|
// For single arg, return single drive EC setup.
|
2023-12-07 04:33:56 -05:00
|
|
|
if isSingleDriveLayout(poolsLayout...) {
|
|
|
|
endpoint, err := NewEndpoint(poolsLayout[0].layout[0][0])
|
2023-05-11 20:41:33 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, setupType, err
|
|
|
|
}
|
|
|
|
if err := endpoint.UpdateIsLocal(); err != nil {
|
|
|
|
return nil, setupType, err
|
|
|
|
}
|
|
|
|
if endpoint.Type() != PathEndpointType {
|
|
|
|
return nil, setupType, config.ErrInvalidEndpoint(nil).Msg("use path style endpoint for single node setup")
|
|
|
|
}
|
|
|
|
|
2023-07-20 10:48:21 -04:00
|
|
|
endpoint.SetPoolIndex(0)
|
|
|
|
endpoint.SetSetIndex(0)
|
|
|
|
endpoint.SetDiskIndex(0)
|
2023-06-23 14:48:23 -04:00
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
var endpoints Endpoints
|
|
|
|
endpoints = append(endpoints, endpoint)
|
|
|
|
setupType = ErasureSDSetupType
|
|
|
|
|
|
|
|
poolEndpoints[0] = endpoints
|
|
|
|
// Check for cross device mounts if any.
|
|
|
|
if err = checkCrossDeviceMounts(endpoints); err != nil {
|
|
|
|
return nil, setupType, config.ErrInvalidEndpoint(nil).Msg(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return poolEndpoints, setupType, nil
|
|
|
|
}
|
|
|
|
|
2024-01-13 02:53:03 -05:00
|
|
|
uniqueArgs := set.NewStringSet()
|
|
|
|
|
2023-12-07 04:33:56 -05:00
|
|
|
for poolIdx, pool := range poolsLayout {
|
2023-05-11 20:41:33 -04:00
|
|
|
var endpoints Endpoints
|
2023-12-07 04:33:56 -05:00
|
|
|
for setIdx, setLayout := range pool.layout {
|
2023-05-11 20:41:33 -04:00
|
|
|
// Convert args to endpoints
|
2023-12-07 04:33:56 -05:00
|
|
|
eps, err := NewEndpoints(setLayout...)
|
2023-05-11 20:41:33 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for cross device mounts if any.
|
|
|
|
if err = checkCrossDeviceMounts(eps); err != nil {
|
|
|
|
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
|
|
|
}
|
|
|
|
|
2023-07-20 10:48:21 -04:00
|
|
|
for diskIdx := range eps {
|
|
|
|
eps[diskIdx].SetPoolIndex(poolIdx)
|
|
|
|
eps[diskIdx].SetSetIndex(setIdx)
|
|
|
|
eps[diskIdx].SetDiskIndex(diskIdx)
|
2023-06-23 14:48:23 -04:00
|
|
|
}
|
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
endpoints = append(endpoints, eps...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(endpoints) == 0 {
|
|
|
|
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg("invalid number of endpoints")
|
|
|
|
}
|
|
|
|
|
2023-06-23 14:48:23 -04:00
|
|
|
poolEndpoints[poolIdx] = endpoints
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := poolEndpoints.UpdateIsLocal(); err != nil {
|
|
|
|
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, endpoints := range poolEndpoints {
|
|
|
|
// Here all endpoints are URL style.
|
|
|
|
endpointPathSet := set.NewStringSet()
|
|
|
|
localEndpointCount := 0
|
|
|
|
localServerHostSet := set.NewStringSet()
|
|
|
|
localPortSet := set.NewStringSet()
|
|
|
|
|
|
|
|
for _, endpoint := range endpoints {
|
|
|
|
endpointPathSet.Add(endpoint.Path)
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoint.IsLocal && endpoint.Host != "" {
|
2023-05-11 20:41:33 -04:00
|
|
|
localServerHostSet.Add(endpoint.Hostname())
|
|
|
|
|
|
|
|
_, port, err := net.SplitHostPort(endpoint.Host)
|
|
|
|
if err != nil {
|
|
|
|
port = serverAddrPort
|
|
|
|
}
|
|
|
|
localPortSet.Add(port)
|
|
|
|
|
|
|
|
localEndpointCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
orchestrated := IsKubernetes() || IsDocker()
|
2023-10-30 13:49:04 -04:00
|
|
|
reverseProxy := (env.Get("_MINIO_REVERSE_PROXY", "") != "") && ((env.Get("MINIO_CI_CD", "") != "") || (env.Get("CI", "") != ""))
|
|
|
|
// If not orchestrated
|
2024-01-13 02:53:03 -05:00
|
|
|
// and not setup in reverse proxy
|
|
|
|
if !orchestrated && !reverseProxy {
|
2023-05-11 20:41:33 -04:00
|
|
|
// Check whether same path is not used in endpoints of a host on different port.
|
|
|
|
// Only verify this on baremetal setups, DNS is not available in orchestrated
|
|
|
|
// environments so we can't do much here.
|
2023-10-30 13:49:04 -04:00
|
|
|
pathIPMap := make(map[string]set.StringSet)
|
|
|
|
hostIPCache := make(map[string]set.StringSet)
|
|
|
|
for _, endpoint := range endpoints {
|
|
|
|
host := endpoint.Hostname()
|
2024-01-13 02:53:03 -05:00
|
|
|
var hostIPSet set.StringSet
|
|
|
|
if host != "" {
|
|
|
|
var ok bool
|
|
|
|
hostIPSet, ok = hostIPCache[host]
|
|
|
|
if !ok {
|
|
|
|
var err error
|
|
|
|
hostIPSet, err = getHostIP(host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("host '%s' cannot resolve: %s", host, err))
|
|
|
|
}
|
|
|
|
hostIPCache[host] = hostIPSet
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
2023-10-30 13:49:04 -04:00
|
|
|
}
|
|
|
|
if IPSet, ok := pathIPMap[endpoint.Path]; ok {
|
|
|
|
if !IPSet.Intersection(hostIPSet).IsEmpty() {
|
|
|
|
return nil, setupType,
|
|
|
|
config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("same path '%s' can not be served by different port on same address", endpoint.Path))
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
2023-10-30 13:49:04 -04:00
|
|
|
pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet)
|
|
|
|
} else {
|
|
|
|
pathIPMap[endpoint.Path] = hostIPSet
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether same path is used for more than 1 local endpoints.
|
|
|
|
{
|
|
|
|
localPathSet := set.CreateStringSet()
|
|
|
|
for _, endpoint := range endpoints {
|
|
|
|
if !endpoint.IsLocal {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if localPathSet.Contains(endpoint.Path) {
|
|
|
|
return nil, setupType,
|
|
|
|
config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' cannot be served by different address on same server", endpoint.Path))
|
|
|
|
}
|
|
|
|
localPathSet.Add(endpoint.Path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add missing port in all endpoints.
|
|
|
|
for i := range endpoints {
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoints[i].Host != "" {
|
|
|
|
_, port, err := net.SplitHostPort(endpoints[i].Host)
|
|
|
|
if err != nil {
|
|
|
|
endpoints[i].Host = net.JoinHostPort(endpoints[i].Host, serverAddrPort)
|
|
|
|
} else if endpoints[i].IsLocal && serverAddrPort != port {
|
|
|
|
// If endpoint is local, but port is different than serverAddrPort, then make it as remote.
|
|
|
|
endpoints[i].IsLocal = false
|
|
|
|
}
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All endpoints are pointing to local host
|
|
|
|
if len(endpoints) == localEndpointCount {
|
|
|
|
// If all endpoints have same port number, Just treat it as local erasure setup
|
|
|
|
// using URL style endpoints.
|
|
|
|
if len(localPortSet) == 1 {
|
|
|
|
if len(localServerHostSet) > 1 {
|
|
|
|
return nil, setupType,
|
|
|
|
config.ErrInvalidErasureEndpoints(nil).Msg("all local endpoints should not have different hostnames/ips")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Even though all endpoints are local, but those endpoints use different ports.
|
|
|
|
// This means it is DistErasure setup.
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, endpoint := range endpoints {
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoint.Host != "" {
|
|
|
|
uniqueArgs.Add(endpoint.Host)
|
|
|
|
} else {
|
|
|
|
uniqueArgs.Add(net.JoinHostPort("localhost", serverAddrPort))
|
|
|
|
}
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
poolEndpoints[i] = endpoints
|
|
|
|
}
|
|
|
|
|
|
|
|
publicIPs := env.Get(config.EnvPublicIPs, "")
|
|
|
|
if len(publicIPs) == 0 {
|
|
|
|
updateDomainIPs(uniqueArgs)
|
|
|
|
}
|
|
|
|
|
2024-01-13 02:53:03 -05:00
|
|
|
erasureType := len(uniqueArgs.ToSlice()) == 1
|
|
|
|
|
2023-05-11 20:41:33 -04:00
|
|
|
for _, endpoints := range poolEndpoints {
|
|
|
|
// Return Erasure setup when all endpoints are path style.
|
|
|
|
if endpoints[0].Type() == PathEndpointType {
|
|
|
|
setupType = ErasureSetupType
|
2024-01-13 02:53:03 -05:00
|
|
|
break
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
2024-01-13 02:53:03 -05:00
|
|
|
if endpoints[0].Type() == URLEndpointType {
|
|
|
|
if erasureType {
|
|
|
|
setupType = ErasureSetupType
|
|
|
|
} else {
|
|
|
|
setupType = DistErasureSetupType
|
|
|
|
}
|
|
|
|
break
|
2023-05-11 20:41:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return poolEndpoints, setupType, nil
|
|
|
|
}
|
|
|
|
|
2017-10-08 23:23:42 -04:00
|
|
|
// GetLocalPeer - returns local peer value, returns globalMinioAddr
|
|
|
|
// for FS and Erasure mode. In case of distributed server return
|
|
|
|
// the first element from the set of peers which indicate that
|
|
|
|
// they are local. There is always one entry that is local
|
|
|
|
// even with repeated server endpoints.
|
2021-03-26 14:37:58 -04:00
|
|
|
func GetLocalPeer(endpointServerPools EndpointServerPools, host, port string) (localPeer string) {
|
2017-10-08 23:23:42 -04:00
|
|
|
peerSet := set.NewStringSet()
|
2020-12-01 16:50:33 -05:00
|
|
|
for _, ep := range endpointServerPools {
|
2019-11-19 20:42:27 -05:00
|
|
|
for _, endpoint := range ep.Endpoints {
|
|
|
|
if endpoint.Type() != URLEndpointType {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if endpoint.IsLocal && endpoint.Host != "" {
|
|
|
|
peerSet.Add(endpoint.Host)
|
|
|
|
}
|
2017-10-08 23:23:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if peerSet.IsEmpty() {
|
2018-03-15 16:03:41 -04:00
|
|
|
// Local peer can be empty in FS or Erasure coded mode.
|
|
|
|
// If so, return globalMinioHost + globalMinioPort value.
|
2021-03-26 14:37:58 -04:00
|
|
|
if host != "" {
|
|
|
|
return net.JoinHostPort(host, port)
|
2018-03-15 16:03:41 -04:00
|
|
|
}
|
|
|
|
|
2021-03-26 14:37:58 -04:00
|
|
|
return net.JoinHostPort("127.0.0.1", port)
|
2017-10-08 23:23:42 -04:00
|
|
|
}
|
|
|
|
return peerSet.ToSlice()[0]
|
|
|
|
}
|
|
|
|
|
2020-07-03 14:53:03 -04:00
|
|
|
// GetProxyEndpointLocalIndex returns index of the local proxy endpoint
|
|
|
|
func GetProxyEndpointLocalIndex(proxyEps []ProxyEndpoint) int {
|
|
|
|
for i, pep := range proxyEps {
|
|
|
|
if pep.IsLocal {
|
|
|
|
return i
|
2020-07-02 13:56:22 -04:00
|
|
|
}
|
|
|
|
}
|
2020-07-03 14:53:03 -04:00
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProxyEndpoints - get all endpoints that can be used to proxy list request.
|
2020-12-01 16:50:33 -05:00
|
|
|
func GetProxyEndpoints(endpointServerPools EndpointServerPools) []ProxyEndpoint {
|
2020-07-03 14:53:03 -04:00
|
|
|
var proxyEps []ProxyEndpoint
|
|
|
|
|
|
|
|
proxyEpSet := set.NewStringSet()
|
2020-07-02 13:56:22 -04:00
|
|
|
|
2020-12-01 16:50:33 -05:00
|
|
|
for _, ep := range endpointServerPools {
|
2020-07-02 13:56:22 -04:00
|
|
|
for _, endpoint := range ep.Endpoints {
|
|
|
|
if endpoint.Type() != URLEndpointType {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
host := endpoint.Host
|
2020-07-03 14:53:03 -04:00
|
|
|
if proxyEpSet.Contains(host) {
|
2020-07-02 13:56:22 -04:00
|
|
|
continue
|
|
|
|
}
|
2020-07-03 14:53:03 -04:00
|
|
|
proxyEpSet.Add(host)
|
|
|
|
|
|
|
|
proxyEps = append(proxyEps, ProxyEndpoint{
|
|
|
|
Endpoint: endpoint,
|
2020-12-22 00:42:38 -05:00
|
|
|
Transport: globalProxyTransport,
|
2020-07-02 13:56:22 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-11-02 10:43:11 -05:00
|
|
|
return proxyEps
|
2020-07-02 13:56:22 -04:00
|
|
|
}
|
|
|
|
|
2018-05-15 21:20:22 -04:00
|
|
|
func updateDomainIPs(endPoints set.StringSet) {
|
2018-12-04 20:35:22 -05:00
|
|
|
ipList := set.NewStringSet()
|
|
|
|
for e := range endPoints {
|
2019-08-02 15:40:51 -04:00
|
|
|
host, port, err := net.SplitHostPort(e)
|
2018-12-04 20:35:22 -05:00
|
|
|
if err != nil {
|
|
|
|
if strings.Contains(err.Error(), "missing port in address") {
|
|
|
|
host = e
|
2019-08-02 15:40:51 -04:00
|
|
|
port = globalMinioPort
|
2018-12-04 20:35:22 -05:00
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
2018-05-15 21:20:22 -04:00
|
|
|
}
|
2019-08-02 15:40:51 -04:00
|
|
|
|
2019-12-18 00:32:45 -05:00
|
|
|
if net.ParseIP(host) == nil {
|
|
|
|
IPs, err := getHostIP(host)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
IPsWithPort := IPs.ApplyFunc(func(ip string) string {
|
|
|
|
return net.JoinHostPort(ip, port)
|
|
|
|
})
|
2019-08-02 15:40:51 -04:00
|
|
|
|
2019-12-18 00:32:45 -05:00
|
|
|
ipList = ipList.Union(IPsWithPort)
|
|
|
|
}
|
|
|
|
|
|
|
|
ipList.Add(net.JoinHostPort(host, port))
|
2018-05-15 21:20:22 -04:00
|
|
|
}
|
2019-12-18 00:32:45 -05:00
|
|
|
|
2018-12-04 20:35:22 -05:00
|
|
|
globalDomainIPs = ipList.FuncMatch(func(ip string, matchString string) bool {
|
2019-11-19 20:42:27 -05:00
|
|
|
host, _, err := net.SplitHostPort(ip)
|
|
|
|
if err != nil {
|
|
|
|
host = ip
|
|
|
|
}
|
2019-12-19 16:45:56 -05:00
|
|
|
return !net.ParseIP(host).IsLoopback() && host != "localhost"
|
2018-12-04 20:35:22 -05:00
|
|
|
}, "")
|
2018-05-15 21:20:22 -04:00
|
|
|
}
|