mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			398 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			398 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * MinIO Cloud Storage, (C) 2018-2020 MinIO, Inc.
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/minio/minio-go/v7/pkg/set"
 | 
						|
	"github.com/minio/minio/cmd/config"
 | 
						|
	"github.com/minio/minio/pkg/ellipses"
 | 
						|
	"github.com/minio/minio/pkg/env"
 | 
						|
)
 | 
						|
 | 
						|
// This file implements and supports ellipses pattern for
 | 
						|
// `minio server` command line arguments.
 | 
						|
 | 
						|
// Endpoint set represents parsed ellipses values, also provides
 | 
						|
// methods to get the sets of endpoints.
 | 
						|
type endpointSet struct {
 | 
						|
	argPatterns []ellipses.ArgPattern
 | 
						|
	endpoints   []string   // Endpoints saved from previous GetEndpoints().
 | 
						|
	setIndexes  [][]uint64 // All the sets.
 | 
						|
}
 | 
						|
 | 
						|
// Supported set sizes this is used to find the optimal
 | 
						|
// single set size.
 | 
						|
var setSizes = []uint64{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
 | 
						|
 | 
						|
// getDivisibleSize - returns a greatest common divisor of
 | 
						|
// all the ellipses sizes.
 | 
						|
func getDivisibleSize(totalSizes []uint64) (result uint64) {
 | 
						|
	gcd := func(x, y uint64) uint64 {
 | 
						|
		for y != 0 {
 | 
						|
			x, y = y, x%y
 | 
						|
		}
 | 
						|
		return x
 | 
						|
	}
 | 
						|
	result = totalSizes[0]
 | 
						|
	for i := 1; i < len(totalSizes); i++ {
 | 
						|
		result = gcd(result, totalSizes[i])
 | 
						|
	}
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
// isValidSetSize - checks whether given count is a valid set size for erasure coding.
 | 
						|
var isValidSetSize = func(count uint64) bool {
 | 
						|
	return (count >= setSizes[0] && count <= setSizes[len(setSizes)-1])
 | 
						|
}
 | 
						|
 | 
						|
func commonSetDriveCount(divisibleSize uint64, setCounts []uint64) (setSize uint64) {
 | 
						|
	// prefers setCounts to be sorted for optimal behavior.
 | 
						|
	if divisibleSize < setCounts[len(setCounts)-1] {
 | 
						|
		return divisibleSize
 | 
						|
	}
 | 
						|
 | 
						|
	// Figure out largest value of total_drives_in_erasure_set which results
 | 
						|
	// in least number of total_drives/total_drives_erasure_set ratio.
 | 
						|
	prevD := divisibleSize / setCounts[0]
 | 
						|
	for _, cnt := range setCounts {
 | 
						|
		if divisibleSize%cnt == 0 {
 | 
						|
			d := divisibleSize / cnt
 | 
						|
			if d <= prevD {
 | 
						|
				prevD = d
 | 
						|
				setSize = cnt
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return setSize
 | 
						|
}
 | 
						|
 | 
						|
// possibleSetCountsWithSymmetry returns symmetrical setCounts based on the
 | 
						|
// input argument patterns, the symmetry calculation is to ensure that
 | 
						|
// we also use uniform number of drives common across all ellipses patterns.
 | 
						|
func possibleSetCountsWithSymmetry(setCounts []uint64, argPatterns []ellipses.ArgPattern) []uint64 {
 | 
						|
	var newSetCounts = make(map[uint64]struct{})
 | 
						|
	for _, ss := range setCounts {
 | 
						|
		var symmetry bool
 | 
						|
		for _, argPattern := range argPatterns {
 | 
						|
			for _, p := range argPattern {
 | 
						|
				if uint64(len(p.Seq)) > ss {
 | 
						|
					symmetry = uint64(len(p.Seq))%ss == 0
 | 
						|
				} else {
 | 
						|
					symmetry = ss%uint64(len(p.Seq)) == 0
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// With no arg patterns, it is expected that user knows
 | 
						|
		// the right symmetry, so either ellipses patterns are
 | 
						|
		// provided (recommended) or no ellipses patterns.
 | 
						|
		if _, ok := newSetCounts[ss]; !ok && (symmetry || argPatterns == nil) {
 | 
						|
			newSetCounts[ss] = struct{}{}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	setCounts = []uint64{}
 | 
						|
	for setCount := range newSetCounts {
 | 
						|
		setCounts = append(setCounts, setCount)
 | 
						|
	}
 | 
						|
 | 
						|
	// Not necessarily needed but it ensures to the readers
 | 
						|
	// eyes that we prefer a sorted setCount slice for the
 | 
						|
	// subsequent function to figure out the right common
 | 
						|
	// divisor, it avoids loops.
 | 
						|
	sort.Slice(setCounts, func(i, j int) bool {
 | 
						|
		return setCounts[i] < setCounts[j]
 | 
						|
	})
 | 
						|
 | 
						|
	return setCounts
 | 
						|
}
 | 
						|
 | 
						|
// getSetIndexes returns list of indexes which provides the set size
 | 
						|
// on each index, this function also determines the final set size
 | 
						|
// The final set size has the affinity towards choosing smaller
 | 
						|
// indexes (total sets)
 | 
						|
func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint64, argPatterns []ellipses.ArgPattern) (setIndexes [][]uint64, err error) {
 | 
						|
	if len(totalSizes) == 0 || len(args) == 0 {
 | 
						|
		return nil, errInvalidArgument
 | 
						|
	}
 | 
						|
 | 
						|
	setIndexes = make([][]uint64, len(totalSizes))
 | 
						|
	for _, totalSize := range totalSizes {
 | 
						|
		// Check if totalSize has minimum range upto setSize
 | 
						|
		if totalSize < setSizes[0] || totalSize < customSetDriveCount {
 | 
						|
			msg := fmt.Sprintf("Incorrect number of endpoints provided %s", args)
 | 
						|
			return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	commonSize := getDivisibleSize(totalSizes)
 | 
						|
	possibleSetCounts := func(setSize uint64) (ss []uint64) {
 | 
						|
		for _, s := range setSizes {
 | 
						|
			if setSize%s == 0 {
 | 
						|
				ss = append(ss, s)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return ss
 | 
						|
	}
 | 
						|
 | 
						|
	setCounts := possibleSetCounts(commonSize)
 | 
						|
	if len(setCounts) == 0 {
 | 
						|
		msg := fmt.Sprintf("Incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
 | 
						|
		return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
 | 
						|
	}
 | 
						|
 | 
						|
	var setSize uint64
 | 
						|
	// Custom set drive count allows to override automatic distribution.
 | 
						|
	// only meant if you want to further optimize drive distribution.
 | 
						|
	if customSetDriveCount > 0 {
 | 
						|
		msg := fmt.Sprintf("Invalid set drive count. Acceptable values for %d number drives are %d", commonSize, setCounts)
 | 
						|
		var found bool
 | 
						|
		for _, ss := range setCounts {
 | 
						|
			if ss == customSetDriveCount {
 | 
						|
				found = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			return nil, config.ErrInvalidErasureSetSize(nil).Msg(msg)
 | 
						|
		}
 | 
						|
 | 
						|
		// No automatic symmetry calculation expected, user is on their own
 | 
						|
		setSize = customSetDriveCount
 | 
						|
		globalCustomErasureDriveCount = true
 | 
						|
	} else {
 | 
						|
		// Returns possible set counts with symmetry.
 | 
						|
		setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
 | 
						|
 | 
						|
		if len(setCounts) == 0 {
 | 
						|
			msg := fmt.Sprintf("No symmetric distribution detected with input endpoints provided %s, disks %d cannot be spread symmetrically by any supported erasure set sizes %d", args, commonSize, setSizes)
 | 
						|
			return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
 | 
						|
		}
 | 
						|
 | 
						|
		// Final set size with all the symmetry accounted for.
 | 
						|
		setSize = commonSetDriveCount(commonSize, setCounts)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check whether setSize is with the supported range.
 | 
						|
	if !isValidSetSize(setSize) {
 | 
						|
		msg := fmt.Sprintf("Incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
 | 
						|
		return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
 | 
						|
	}
 | 
						|
 | 
						|
	for i := range totalSizes {
 | 
						|
		for j := uint64(0); j < totalSizes[i]/setSize; j++ {
 | 
						|
			setIndexes[i] = append(setIndexes[i], setSize)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return setIndexes, nil
 | 
						|
}
 | 
						|
 | 
						|
// Returns all the expanded endpoints, each argument is expanded separately.
 | 
						|
func (s endpointSet) getEndpoints() (endpoints []string) {
 | 
						|
	if len(s.endpoints) != 0 {
 | 
						|
		return s.endpoints
 | 
						|
	}
 | 
						|
	for _, argPattern := range s.argPatterns {
 | 
						|
		for _, lbls := range argPattern.Expand() {
 | 
						|
			endpoints = append(endpoints, strings.Join(lbls, ""))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	s.endpoints = endpoints
 | 
						|
	return endpoints
 | 
						|
}
 | 
						|
 | 
						|
// Get returns the sets representation of the endpoints
 | 
						|
// this function also intelligently decides on what will
 | 
						|
// be the right set size etc.
 | 
						|
func (s endpointSet) Get() (sets [][]string) {
 | 
						|
	var k = uint64(0)
 | 
						|
	endpoints := s.getEndpoints()
 | 
						|
	for i := range s.setIndexes {
 | 
						|
		for j := range s.setIndexes[i] {
 | 
						|
			sets = append(sets, endpoints[k:s.setIndexes[i][j]+k])
 | 
						|
			k = s.setIndexes[i][j] + k
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return sets
 | 
						|
}
 | 
						|
 | 
						|
// Return the total size for each argument patterns.
 | 
						|
func getTotalSizes(argPatterns []ellipses.ArgPattern) []uint64 {
 | 
						|
	var totalSizes []uint64
 | 
						|
	for _, argPattern := range argPatterns {
 | 
						|
		var totalSize uint64 = 1
 | 
						|
		for _, p := range argPattern {
 | 
						|
			totalSize = totalSize * uint64(len(p.Seq))
 | 
						|
		}
 | 
						|
		totalSizes = append(totalSizes, totalSize)
 | 
						|
	}
 | 
						|
	return totalSizes
 | 
						|
}
 | 
						|
 | 
						|
// Parses all arguments and returns an endpointSet which is a collection
 | 
						|
// of endpoints following the ellipses pattern, this is what is used
 | 
						|
// by the object layer for initializing itself.
 | 
						|
func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSet, err error) {
 | 
						|
	var argPatterns = make([]ellipses.ArgPattern, len(args))
 | 
						|
	for i, arg := range args {
 | 
						|
		patterns, perr := ellipses.FindEllipsesPatterns(arg)
 | 
						|
		if perr != nil {
 | 
						|
			return endpointSet{}, config.ErrInvalidErasureEndpoints(nil).Msg(perr.Error())
 | 
						|
		}
 | 
						|
		argPatterns[i] = patterns
 | 
						|
	}
 | 
						|
 | 
						|
	ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns), customSetDriveCount, argPatterns)
 | 
						|
	if err != nil {
 | 
						|
		return endpointSet{}, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
 | 
						|
	}
 | 
						|
 | 
						|
	ep.argPatterns = argPatterns
 | 
						|
 | 
						|
	return ep, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetAllSets - parses all ellipses input arguments, expands them into
 | 
						|
// corresponding list of endpoints chunked evenly in accordance with a
 | 
						|
// specific set size.
 | 
						|
// For example: {1...64} is divided into 4 sets each of size 16.
 | 
						|
// This applies to even distributed setup syntax as well.
 | 
						|
func GetAllSets(customSetDriveCount uint64, args ...string) ([][]string, error) {
 | 
						|
	var setArgs [][]string
 | 
						|
	if !ellipses.HasEllipses(args...) {
 | 
						|
		var setIndexes [][]uint64
 | 
						|
		// Check if we have more one args.
 | 
						|
		if len(args) > 1 {
 | 
						|
			var err error
 | 
						|
			setIndexes, err = getSetIndexes(args, []uint64{uint64(len(args))}, customSetDriveCount, nil)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			// We are in FS setup, proceed forward.
 | 
						|
			setIndexes = [][]uint64{{uint64(len(args))}}
 | 
						|
		}
 | 
						|
		s := endpointSet{
 | 
						|
			endpoints:  args,
 | 
						|
			setIndexes: setIndexes,
 | 
						|
		}
 | 
						|
		setArgs = s.Get()
 | 
						|
	} else {
 | 
						|
		s, err := parseEndpointSet(customSetDriveCount, args...)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		setArgs = s.Get()
 | 
						|
	}
 | 
						|
 | 
						|
	uniqueArgs := set.NewStringSet()
 | 
						|
	for _, sargs := range setArgs {
 | 
						|
		for _, arg := range sargs {
 | 
						|
			if uniqueArgs.Contains(arg) {
 | 
						|
				return nil, config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("Input args (%s) has duplicate ellipses", args))
 | 
						|
			}
 | 
						|
			uniqueArgs.Add(arg)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return setArgs, nil
 | 
						|
}
 | 
						|
 | 
						|
// Override set drive count for manual distribution.
 | 
						|
const (
 | 
						|
	EnvErasureSetDriveCount = "MINIO_ERASURE_SET_DRIVE_COUNT"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	globalCustomErasureDriveCount = false
 | 
						|
)
 | 
						|
 | 
						|
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
 | 
						|
// both ellipses and without ellipses transparently.
 | 
						|
func createServerEndpoints(serverAddr string, args ...string) (
 | 
						|
	endpointServerPools EndpointServerPools, setupType SetupType, err error) {
 | 
						|
 | 
						|
	if len(args) == 0 {
 | 
						|
		return nil, -1, errInvalidArgument
 | 
						|
	}
 | 
						|
 | 
						|
	var setDriveCount int
 | 
						|
	if v := env.Get(EnvErasureSetDriveCount, ""); v != "" {
 | 
						|
		setDriveCount, err = strconv.Atoi(v)
 | 
						|
		if err != nil {
 | 
						|
			return nil, -1, config.ErrInvalidErasureSetSize(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !ellipses.HasEllipses(args...) {
 | 
						|
		setArgs, err := GetAllSets(uint64(setDriveCount), args...)
 | 
						|
		if err != nil {
 | 
						|
			return nil, -1, err
 | 
						|
		}
 | 
						|
		endpointList, newSetupType, err := CreateEndpoints(serverAddr, false, setArgs...)
 | 
						|
		if err != nil {
 | 
						|
			return nil, -1, err
 | 
						|
		}
 | 
						|
		endpointServerPools = append(endpointServerPools, ZoneEndpoints{
 | 
						|
			SetCount:     len(setArgs),
 | 
						|
			DrivesPerSet: len(setArgs[0]),
 | 
						|
			Endpoints:    endpointList,
 | 
						|
		})
 | 
						|
		setupType = newSetupType
 | 
						|
		return endpointServerPools, setupType, nil
 | 
						|
	}
 | 
						|
 | 
						|
	var foundPrevLocal bool
 | 
						|
	for _, arg := range args {
 | 
						|
		setArgs, err := GetAllSets(uint64(setDriveCount), arg)
 | 
						|
		if err != nil {
 | 
						|
			return nil, -1, err
 | 
						|
		}
 | 
						|
		endpointList, gotSetupType, err := CreateEndpoints(serverAddr, foundPrevLocal, setArgs...)
 | 
						|
		if err != nil {
 | 
						|
			return nil, -1, err
 | 
						|
		}
 | 
						|
		if setDriveCount != 0 && setDriveCount != len(setArgs[0]) {
 | 
						|
			return nil, -1, fmt.Errorf("All serverPools should have same drive per set ratio - expected %d, got %d", setDriveCount, len(setArgs[0]))
 | 
						|
		}
 | 
						|
		if err = endpointServerPools.Add(ZoneEndpoints{
 | 
						|
			SetCount:     len(setArgs),
 | 
						|
			DrivesPerSet: len(setArgs[0]),
 | 
						|
			Endpoints:    endpointList,
 | 
						|
		}); err != nil {
 | 
						|
			return nil, -1, err
 | 
						|
		}
 | 
						|
		foundPrevLocal = endpointList.atleastOneEndpointLocal()
 | 
						|
		if setDriveCount == 0 {
 | 
						|
			setDriveCount = len(setArgs[0])
 | 
						|
		}
 | 
						|
		if setupType == UnknownSetupType {
 | 
						|
			setupType = gotSetupType
 | 
						|
		}
 | 
						|
		if setupType == ErasureSetupType && gotSetupType == DistErasureSetupType {
 | 
						|
			setupType = DistErasureSetupType
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return endpointServerPools, setupType, nil
 | 
						|
}
 |