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

     1  /*
     2   * MinIO Cloud Storage, (C) 2018-2020 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  	"fmt"
    21  	"sort"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/minio/minio-go/v7/pkg/set"
    26  
    27  	"storj.io/minio/cmd/config"
    28  	"storj.io/minio/pkg/ellipses"
    29  	"storj.io/minio/pkg/env"
    30  )
    31  
    32  // This file implements and supports ellipses pattern for
    33  // `minio server` command line arguments.
    34  
    35  // Endpoint set represents parsed ellipses values, also provides
    36  // methods to get the sets of endpoints.
    37  type endpointSet struct {
    38  	argPatterns []ellipses.ArgPattern
    39  	endpoints   []string   // Endpoints saved from previous GetEndpoints().
    40  	setIndexes  [][]uint64 // All the sets.
    41  }
    42  
    43  // Supported set sizes this is used to find the optimal
    44  // single set size.
    45  var setSizes = []uint64{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
    46  
    47  // getDivisibleSize - returns a greatest common divisor of
    48  // all the ellipses sizes.
    49  func getDivisibleSize(totalSizes []uint64) (result uint64) {
    50  	gcd := func(x, y uint64) uint64 {
    51  		for y != 0 {
    52  			x, y = y, x%y
    53  		}
    54  		return x
    55  	}
    56  	result = totalSizes[0]
    57  	for i := 1; i < len(totalSizes); i++ {
    58  		result = gcd(result, totalSizes[i])
    59  	}
    60  	return result
    61  }
    62  
    63  // isValidSetSize - checks whether given count is a valid set size for erasure coding.
    64  var isValidSetSize = func(count uint64) bool {
    65  	return (count >= setSizes[0] && count <= setSizes[len(setSizes)-1])
    66  }
    67  
    68  func commonSetDriveCount(divisibleSize uint64, setCounts []uint64) (setSize uint64) {
    69  	// prefers setCounts to be sorted for optimal behavior.
    70  	if divisibleSize < setCounts[len(setCounts)-1] {
    71  		return divisibleSize
    72  	}
    73  
    74  	// Figure out largest value of total_drives_in_erasure_set which results
    75  	// in least number of total_drives/total_drives_erasure_set ratio.
    76  	prevD := divisibleSize / setCounts[0]
    77  	for _, cnt := range setCounts {
    78  		if divisibleSize%cnt == 0 {
    79  			d := divisibleSize / cnt
    80  			if d <= prevD {
    81  				prevD = d
    82  				setSize = cnt
    83  			}
    84  		}
    85  	}
    86  	return setSize
    87  }
    88  
    89  // possibleSetCountsWithSymmetry returns symmetrical setCounts based on the
    90  // input argument patterns, the symmetry calculation is to ensure that
    91  // we also use uniform number of drives common across all ellipses patterns.
    92  func possibleSetCountsWithSymmetry(setCounts []uint64, argPatterns []ellipses.ArgPattern) []uint64 {
    93  	var newSetCounts = make(map[uint64]struct{})
    94  	for _, ss := range setCounts {
    95  		var symmetry bool
    96  		for _, argPattern := range argPatterns {
    97  			for _, p := range argPattern {
    98  				if uint64(len(p.Seq)) > ss {
    99  					symmetry = uint64(len(p.Seq))%ss == 0
   100  				} else {
   101  					symmetry = ss%uint64(len(p.Seq)) == 0
   102  				}
   103  			}
   104  		}
   105  		// With no arg patterns, it is expected that user knows
   106  		// the right symmetry, so either ellipses patterns are
   107  		// provided (recommended) or no ellipses patterns.
   108  		if _, ok := newSetCounts[ss]; !ok && (symmetry || argPatterns == nil) {
   109  			newSetCounts[ss] = struct{}{}
   110  		}
   111  	}
   112  
   113  	setCounts = []uint64{}
   114  	for setCount := range newSetCounts {
   115  		setCounts = append(setCounts, setCount)
   116  	}
   117  
   118  	// Not necessarily needed but it ensures to the readers
   119  	// eyes that we prefer a sorted setCount slice for the
   120  	// subsequent function to figure out the right common
   121  	// divisor, it avoids loops.
   122  	sort.Slice(setCounts, func(i, j int) bool {
   123  		return setCounts[i] < setCounts[j]
   124  	})
   125  
   126  	return setCounts
   127  }
   128  
   129  // getSetIndexes returns list of indexes which provides the set size
   130  // on each index, this function also determines the final set size
   131  // The final set size has the affinity towards choosing smaller
   132  // indexes (total sets)
   133  func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint64, argPatterns []ellipses.ArgPattern) (setIndexes [][]uint64, err error) {
   134  	if len(totalSizes) == 0 || len(args) == 0 {
   135  		return nil, errInvalidArgument
   136  	}
   137  
   138  	setIndexes = make([][]uint64, len(totalSizes))
   139  	for _, totalSize := range totalSizes {
   140  		// Check if totalSize has minimum range upto setSize
   141  		if totalSize < setSizes[0] || totalSize < customSetDriveCount {
   142  			msg := fmt.Sprintf("Incorrect number of endpoints provided %s", args)
   143  			return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
   144  		}
   145  	}
   146  
   147  	commonSize := getDivisibleSize(totalSizes)
   148  	possibleSetCounts := func(setSize uint64) (ss []uint64) {
   149  		for _, s := range setSizes {
   150  			if setSize%s == 0 {
   151  				ss = append(ss, s)
   152  			}
   153  		}
   154  		return ss
   155  	}
   156  
   157  	setCounts := possibleSetCounts(commonSize)
   158  	if len(setCounts) == 0 {
   159  		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)
   160  		return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
   161  	}
   162  
   163  	var setSize uint64
   164  	// Custom set drive count allows to override automatic distribution.
   165  	// only meant if you want to further optimize drive distribution.
   166  	if customSetDriveCount > 0 {
   167  		msg := fmt.Sprintf("Invalid set drive count. Acceptable values for %d number drives are %d", commonSize, setCounts)
   168  		var found bool
   169  		for _, ss := range setCounts {
   170  			if ss == customSetDriveCount {
   171  				found = true
   172  			}
   173  		}
   174  		if !found {
   175  			return nil, config.ErrInvalidErasureSetSize(nil).Msg(msg)
   176  		}
   177  
   178  		// No automatic symmetry calculation expected, user is on their own
   179  		setSize = customSetDriveCount
   180  		globalCustomErasureDriveCount = true
   181  	} else {
   182  		// Returns possible set counts with symmetry.
   183  		setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
   184  
   185  		if len(setCounts) == 0 {
   186  			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)
   187  			return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
   188  		}
   189  
   190  		// Final set size with all the symmetry accounted for.
   191  		setSize = commonSetDriveCount(commonSize, setCounts)
   192  	}
   193  
   194  	// Check whether setSize is with the supported range.
   195  	if !isValidSetSize(setSize) {
   196  		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)
   197  		return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
   198  	}
   199  
   200  	for i := range totalSizes {
   201  		for j := uint64(0); j < totalSizes[i]/setSize; j++ {
   202  			setIndexes[i] = append(setIndexes[i], setSize)
   203  		}
   204  	}
   205  
   206  	return setIndexes, nil
   207  }
   208  
   209  // Returns all the expanded endpoints, each argument is expanded separately.
   210  func (s endpointSet) getEndpoints() (endpoints []string) {
   211  	if len(s.endpoints) != 0 {
   212  		return s.endpoints
   213  	}
   214  	for _, argPattern := range s.argPatterns {
   215  		for _, lbls := range argPattern.Expand() {
   216  			endpoints = append(endpoints, strings.Join(lbls, ""))
   217  		}
   218  	}
   219  	s.endpoints = endpoints
   220  	return endpoints
   221  }
   222  
   223  // Get returns the sets representation of the endpoints
   224  // this function also intelligently decides on what will
   225  // be the right set size etc.
   226  func (s endpointSet) Get() (sets [][]string) {
   227  	var k = uint64(0)
   228  	endpoints := s.getEndpoints()
   229  	for i := range s.setIndexes {
   230  		for j := range s.setIndexes[i] {
   231  			sets = append(sets, endpoints[k:s.setIndexes[i][j]+k])
   232  			k = s.setIndexes[i][j] + k
   233  		}
   234  	}
   235  
   236  	return sets
   237  }
   238  
   239  // Return the total size for each argument patterns.
   240  func getTotalSizes(argPatterns []ellipses.ArgPattern) []uint64 {
   241  	var totalSizes []uint64
   242  	for _, argPattern := range argPatterns {
   243  		var totalSize uint64 = 1
   244  		for _, p := range argPattern {
   245  			totalSize = totalSize * uint64(len(p.Seq))
   246  		}
   247  		totalSizes = append(totalSizes, totalSize)
   248  	}
   249  	return totalSizes
   250  }
   251  
   252  // Parses all arguments and returns an endpointSet which is a collection
   253  // of endpoints following the ellipses pattern, this is what is used
   254  // by the object layer for initializing itself.
   255  func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSet, err error) {
   256  	var argPatterns = make([]ellipses.ArgPattern, len(args))
   257  	for i, arg := range args {
   258  		patterns, perr := ellipses.FindEllipsesPatterns(arg)
   259  		if perr != nil {
   260  			return endpointSet{}, config.ErrInvalidErasureEndpoints(nil).Msg(perr.Error())
   261  		}
   262  		argPatterns[i] = patterns
   263  	}
   264  
   265  	ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns), customSetDriveCount, argPatterns)
   266  	if err != nil {
   267  		return endpointSet{}, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
   268  	}
   269  
   270  	ep.argPatterns = argPatterns
   271  
   272  	return ep, nil
   273  }
   274  
   275  // GetAllSets - parses all ellipses input arguments, expands them into
   276  // corresponding list of endpoints chunked evenly in accordance with a
   277  // specific set size.
   278  // For example: {1...64} is divided into 4 sets each of size 16.
   279  // This applies to even distributed setup syntax as well.
   280  func GetAllSets(args ...string) ([][]string, error) {
   281  	var customSetDriveCount uint64
   282  	if v := env.Get(EnvErasureSetDriveCount, ""); v != "" {
   283  		driveCount, err := strconv.Atoi(v)
   284  		if err != nil {
   285  			return nil, config.ErrInvalidErasureSetSize(err)
   286  		}
   287  		customSetDriveCount = uint64(driveCount)
   288  	}
   289  
   290  	var setArgs [][]string
   291  	if !ellipses.HasEllipses(args...) {
   292  		var setIndexes [][]uint64
   293  		// Check if we have more one args.
   294  		if len(args) > 1 {
   295  			var err error
   296  			setIndexes, err = getSetIndexes(args, []uint64{uint64(len(args))}, customSetDriveCount, nil)
   297  			if err != nil {
   298  				return nil, err
   299  			}
   300  		} else {
   301  			// We are in FS setup, proceed forward.
   302  			setIndexes = [][]uint64{{uint64(len(args))}}
   303  		}
   304  		s := endpointSet{
   305  			endpoints:  args,
   306  			setIndexes: setIndexes,
   307  		}
   308  		setArgs = s.Get()
   309  	} else {
   310  		s, err := parseEndpointSet(customSetDriveCount, args...)
   311  		if err != nil {
   312  			return nil, err
   313  		}
   314  		setArgs = s.Get()
   315  	}
   316  
   317  	uniqueArgs := set.NewStringSet()
   318  	for _, sargs := range setArgs {
   319  		for _, arg := range sargs {
   320  			if uniqueArgs.Contains(arg) {
   321  				return nil, config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("Input args (%s) has duplicate ellipses", args))
   322  			}
   323  			uniqueArgs.Add(arg)
   324  		}
   325  	}
   326  
   327  	return setArgs, nil
   328  }
   329  
   330  // Override set drive count for manual distribution.
   331  const (
   332  	EnvErasureSetDriveCount = "MINIO_ERASURE_SET_DRIVE_COUNT"
   333  )
   334  
   335  var (
   336  	globalCustomErasureDriveCount = false
   337  )
   338  
   339  // CreateServerEndpoints - validates and creates new endpoints from input args, supports
   340  // both ellipses and without ellipses transparently.
   341  func createServerEndpoints(serverAddr string, args ...string) (
   342  	endpointServerPools EndpointServerPools, setupType SetupType, err error) {
   343  
   344  	if len(args) == 0 {
   345  		return nil, -1, errInvalidArgument
   346  	}
   347  
   348  	if !ellipses.HasEllipses(args...) {
   349  		setArgs, err := GetAllSets(args...)
   350  		if err != nil {
   351  			return nil, -1, err
   352  		}
   353  		endpointList, newSetupType, err := CreateEndpoints(serverAddr, false, setArgs...)
   354  		if err != nil {
   355  			return nil, -1, err
   356  		}
   357  		endpointServerPools = append(endpointServerPools, PoolEndpoints{
   358  			SetCount:     len(setArgs),
   359  			DrivesPerSet: len(setArgs[0]),
   360  			Endpoints:    endpointList,
   361  		})
   362  		setupType = newSetupType
   363  		return endpointServerPools, setupType, nil
   364  	}
   365  
   366  	var foundPrevLocal bool
   367  	for _, arg := range args {
   368  		setArgs, err := GetAllSets(arg)
   369  		if err != nil {
   370  			return nil, -1, err
   371  		}
   372  
   373  		endpointList, gotSetupType, err := CreateEndpoints(serverAddr, foundPrevLocal, setArgs...)
   374  		if err != nil {
   375  			return nil, -1, err
   376  		}
   377  		if err = endpointServerPools.Add(PoolEndpoints{
   378  			SetCount:     len(setArgs),
   379  			DrivesPerSet: len(setArgs[0]),
   380  			Endpoints:    endpointList,
   381  		}); err != nil {
   382  			return nil, -1, err
   383  		}
   384  		foundPrevLocal = endpointList.atleastOneEndpointLocal()
   385  		if setupType == UnknownSetupType {
   386  			setupType = gotSetupType
   387  		}
   388  		if setupType == ErasureSetupType && gotSetupType == DistErasureSetupType {
   389  			setupType = DistErasureSetupType
   390  		}
   391  	}
   392  
   393  	return endpointServerPools, setupType, nil
   394  }