@ -18,6 +18,7 @@ package cmd
import (
import (
"fmt"
"fmt"
"sort"
"strconv"
"strconv"
"strings"
"strings"
@ -63,11 +64,72 @@ var isValidSetSize = func(count uint64) bool {
return ( count >= setSizes [ 0 ] && count <= setSizes [ len ( setSizes ) - 1 ] )
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
// getSetIndexes returns list of indexes which provides the set size
// on each index, this function also determines the final set size
// on each index, this function also determines the final set size
// The final set size has the affinity towards choosing smaller
// The final set size has the affinity towards choosing smaller
// indexes (total sets)
// indexes (total sets)
func getSetIndexes ( args [ ] string , totalSizes [ ] uint64 , customSetDriveCount uint64 ) ( setIndexes [ ] [ ] uint64 , err error ) {
func getSetIndexes ( args [ ] string , totalSizes [ ] uint64 , customSetDriveCount uint64 , argPatterns [ ] ellipses . ArgPattern ) ( setIndexes [ ] [ ] uint64 , err error ) {
if len ( totalSizes ) == 0 || len ( args ) == 0 {
if len ( totalSizes ) == 0 || len ( args ) == 0 {
return nil , errInvalidArgument
return nil , errInvalidArgument
}
}
@ -81,24 +143,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
}
}
}
}
var setSize uint64
commonSize := getDivisibleSize ( totalSizes )
commonSize := getDivisibleSize ( totalSizes )
if commonSize > setSizes [ len ( setSizes ) - 1 ] {
prevD := commonSize / setSizes [ 0 ]
for _ , i := range setSizes {
if commonSize % i == 0 {
d := commonSize / i
if d <= prevD {
prevD = d
setSize = i
}
}
}
} else {
setSize = commonSize
}
possibleSetCounts := func ( setSize uint64 ) ( ss [ ] uint64 ) {
possibleSetCounts := func ( setSize uint64 ) ( ss [ ] uint64 ) {
for _ , s := range setSizes {
for _ , s := range setSizes {
if setSize % s == 0 {
if setSize % s == 0 {
@ -114,11 +159,11 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
return nil , config . ErrInvalidNumberOfErasureEndpoints ( nil ) . Msg ( msg )
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 {
if customSetDriveCount > 0 {
msg := fmt . Sprintf ( "Invalid set drive count. Acceptable values for %d number drives are %d" , commonSize , setCounts )
msg := fmt . Sprintf ( "Invalid set drive count. Acceptable values for %d number drives are %d" , commonSize , setCounts )
if customSetDriveCount > setSize {
return nil , config . ErrInvalidErasureSetSize ( nil ) . Msg ( msg )
}
var found bool
var found bool
for _ , ss := range setCounts {
for _ , ss := range setCounts {
if ss == customSetDriveCount {
if ss == customSetDriveCount {
@ -128,7 +173,16 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
if ! found {
if ! found {
return nil , config . ErrInvalidErasureSetSize ( nil ) . Msg ( msg )
return nil , config . ErrInvalidErasureSetSize ( nil ) . Msg ( msg )
}
}
// No automatic symmetry calculation expected, user is on their own
setSize = customSetDriveCount
setSize = customSetDriveCount
globalCustomErasureDriveCount = true
} else {
// Returns possible set counts with symmetry.
setCounts = possibleSetCountsWithSymmetry ( setCounts , argPatterns )
// Final set size with all the symmetry accounted for.
setSize = commonSetDriveCount ( commonSize , setCounts )
}
}
// Check whether setSize is with the supported range.
// Check whether setSize is with the supported range.
@ -202,7 +256,7 @@ func parseEndpointSet(customSetDriveCount uint64, args ...string) (ep endpointSe
argPatterns [ i ] = patterns
argPatterns [ i ] = patterns
}
}
ep . setIndexes , err = getSetIndexes ( args , getTotalSizes ( argPatterns ) , customSetDriveCount )
ep . setIndexes , err = getSetIndexes ( args , getTotalSizes ( argPatterns ) , customSetDriveCount , argPatterns )
if err != nil {
if err != nil {
return endpointSet { } , config . ErrInvalidErasureEndpoints ( nil ) . Msg ( err . Error ( ) )
return endpointSet { } , config . ErrInvalidErasureEndpoints ( nil ) . Msg ( err . Error ( ) )
}
}
@ -224,7 +278,7 @@ func GetAllSets(customSetDriveCount uint64, args ...string) ([][]string, error)
// Check if we have more one args.
// Check if we have more one args.
if len ( args ) > 1 {
if len ( args ) > 1 {
var err error
var err error
setIndexes , err = getSetIndexes ( args , [ ] uint64 { uint64 ( len ( args ) ) } , customSetDriveCount )
setIndexes , err = getSetIndexes ( args , [ ] uint64 { uint64 ( len ( args ) ) } , customSetDriveCount , nil )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
@ -258,6 +312,15 @@ func GetAllSets(customSetDriveCount uint64, args ...string) ([][]string, error)
return setArgs , nil
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
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
// both ellipses and without ellipses transparently.
// both ellipses and without ellipses transparently.
func createServerEndpoints ( serverAddr string , args ... string ) (
func createServerEndpoints ( serverAddr string , args ... string ) (
@ -268,7 +331,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
return nil , - 1 , - 1 , errInvalidArgument
return nil , - 1 , - 1 , errInvalidArgument
}
}
if v := env . Get ( "MINIO_ERASURE_SET_DRIVE_COUNT" , "" ) ; v != "" {
if v := env . Get ( EnvErasureSetDriveCount , "" ) ; v != "" {
setDriveCount , err = strconv . Atoi ( v )
setDriveCount , err = strconv . Atoi ( v )
if err != nil {
if err != nil {
return nil , - 1 , - 1 , config . ErrInvalidErasureSetSize ( err )
return nil , - 1 , - 1 , config . ErrInvalidErasureSetSize ( err )