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 }