mirror of
https://github.com/minio/minio.git
synced 2025-11-09 05:34:56 -05:00
Add large bucket support for erasure coded backend (#5160)
This PR implements an object layer which combines input erasure sets of XL layers into a unified namespace. This object layer extends the existing erasure coded implementation, it is assumed in this design that providing > 16 disks is a static configuration as well i.e if you started the setup with 32 disks with 4 sets 8 disks per pack then you would need to provide 4 sets always. Some design details and restrictions: - Objects are distributed using consistent ordering to a unique erasure coded layer. - Each pack has its own dsync so locks are synchronized properly at pack (erasure layer). - Each pack still has a maximum of 16 disks requirement, you can start with multiple such sets statically. - Static sets set of disks and cannot be changed, there is no elastic expansion allowed. - Static sets set of disks and cannot be changed, there is no elastic removal allowed. - ListObjects() across sets can be noticeably slower since List happens on all servers, and is merged at this sets layer. Fixes #5465 Fixes #5464 Fixes #5461 Fixes #5460 Fixes #5459 Fixes #5458 Fixes #5460 Fixes #5488 Fixes #5489 Fixes #5497 Fixes #5496
This commit is contained in:
committed by
kannappanr
parent
dd80256151
commit
fb96779a8a
77
pkg/bpool/bpool.go
Normal file
77
pkg/bpool/bpool.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Original work https://github.com/oxtoacart/bpool borrowed
|
||||
// only bpool.go licensed under Apache 2.0.
|
||||
|
||||
// This file modifies original bpool.go to add one more option
|
||||
// to provide []byte capacity for better GC management.
|
||||
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 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 bpool
|
||||
|
||||
// BytePoolCap implements a leaky pool of []byte in the form of a bounded channel.
|
||||
type BytePoolCap struct {
|
||||
c chan []byte
|
||||
w int
|
||||
wcap int
|
||||
}
|
||||
|
||||
// NewBytePoolCap creates a new BytePool bounded to the given maxSize, with new
|
||||
// byte arrays sized based on width.
|
||||
func NewBytePoolCap(maxSize int, width int, capwidth int) (bp *BytePoolCap) {
|
||||
return &BytePoolCap{
|
||||
c: make(chan []byte, maxSize),
|
||||
w: width,
|
||||
wcap: capwidth,
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets a []byte from the BytePool, or creates a new one if none are
|
||||
// available in the pool.
|
||||
func (bp *BytePoolCap) Get() (b []byte) {
|
||||
select {
|
||||
case b = <-bp.c:
|
||||
// reuse existing buffer
|
||||
default:
|
||||
// create new buffer
|
||||
if bp.wcap > 0 {
|
||||
b = make([]byte, bp.w, bp.wcap)
|
||||
} else {
|
||||
b = make([]byte, bp.w)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Put returns the given Buffer to the BytePool.
|
||||
func (bp *BytePoolCap) Put(b []byte) {
|
||||
select {
|
||||
case bp.c <- b:
|
||||
// buffer went back into pool
|
||||
default:
|
||||
// buffer didn't go back into pool, just discard
|
||||
}
|
||||
}
|
||||
|
||||
// Width returns the width of the byte arrays in this pool.
|
||||
func (bp *BytePoolCap) Width() (n int) {
|
||||
return bp.w
|
||||
}
|
||||
|
||||
// WidthCap returns the cap width of the byte arrays in this pool.
|
||||
func (bp *BytePoolCap) WidthCap() (n int) {
|
||||
return bp.wcap
|
||||
}
|
||||
96
pkg/bpool/bpool_test.go
Normal file
96
pkg/bpool/bpool_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Original work https://github.com/oxtoacart/bpool borrowed
|
||||
// only bpool.go licensed under Apache 2.0.
|
||||
|
||||
// This file modifies original bpool.go to add one more option
|
||||
// to provide []byte capacity for better GC management.
|
||||
|
||||
/*
|
||||
* Minio Cloud Storage (C) 2018 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 bpool
|
||||
|
||||
import "testing"
|
||||
|
||||
// Tests - bytePool functionality.
|
||||
func TestBytePool(t *testing.T) {
|
||||
var size = 4
|
||||
var width = 10
|
||||
var capWidth = 16
|
||||
|
||||
bufPool := NewBytePoolCap(size, width, capWidth)
|
||||
|
||||
// Check the width
|
||||
if bufPool.Width() != width {
|
||||
t.Fatalf("bytepool width invalid: got %v want %v", bufPool.Width(), width)
|
||||
}
|
||||
|
||||
// Check with width cap
|
||||
if bufPool.WidthCap() != capWidth {
|
||||
t.Fatalf("bytepool capWidth invalid: got %v want %v", bufPool.WidthCap(), capWidth)
|
||||
}
|
||||
|
||||
// Check that retrieved buffer are of the expected width
|
||||
b := bufPool.Get()
|
||||
if len(b) != width {
|
||||
t.Fatalf("bytepool length invalid: got %v want %v", len(b), width)
|
||||
}
|
||||
if cap(b) != capWidth {
|
||||
t.Fatalf("bytepool length invalid: got %v want %v", cap(b), capWidth)
|
||||
}
|
||||
|
||||
bufPool.Put(b)
|
||||
|
||||
// Fill the pool beyond the capped pool size.
|
||||
for i := 0; i < size*2; i++ {
|
||||
bufPool.Put(make([]byte, bufPool.w))
|
||||
}
|
||||
|
||||
b = bufPool.Get()
|
||||
if len(b) != width {
|
||||
t.Fatalf("bytepool length invalid: got %v want %v", len(b), width)
|
||||
}
|
||||
if cap(b) != capWidth {
|
||||
t.Fatalf("bytepool length invalid: got %v want %v", cap(b), capWidth)
|
||||
}
|
||||
|
||||
bufPool.Put(b)
|
||||
|
||||
// Close the channel so we can iterate over it.
|
||||
close(bufPool.c)
|
||||
|
||||
// Check the size of the pool.
|
||||
if len(bufPool.c) != size {
|
||||
t.Fatalf("bytepool size invalid: got %v want %v", len(bufPool.c), size)
|
||||
}
|
||||
|
||||
bufPoolNoCap := NewBytePoolCap(size, width, 0)
|
||||
// Check the width
|
||||
if bufPoolNoCap.Width() != width {
|
||||
t.Fatalf("bytepool width invalid: got %v want %v", bufPool.Width(), width)
|
||||
}
|
||||
|
||||
// Check with width cap
|
||||
if bufPoolNoCap.WidthCap() != 0 {
|
||||
t.Fatalf("bytepool capWidth invalid: got %v want %v", bufPool.WidthCap(), 0)
|
||||
}
|
||||
b = bufPoolNoCap.Get()
|
||||
if len(b) != width {
|
||||
t.Fatalf("bytepool length invalid: got %v want %v", len(b), width)
|
||||
}
|
||||
if cap(b) != width {
|
||||
t.Fatalf("bytepool length invalid: got %v want %v", cap(b), width)
|
||||
}
|
||||
}
|
||||
207
pkg/ellipses/ellipses.go
Normal file
207
pkg/ellipses/ellipses.go
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2018 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 ellipses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// Regex to extract ellipses syntax inputs.
|
||||
regexpEllipses = regexp.MustCompile(`(.*)({[0-9]*\.\.\.[0-9]*})(.*)`)
|
||||
|
||||
// Ellipses constants
|
||||
openBraces = "{"
|
||||
closeBraces = "}"
|
||||
ellipses = "..."
|
||||
)
|
||||
|
||||
// Parses an ellipses range pattern of following style
|
||||
// `{1...64}`
|
||||
// `{33...64}`
|
||||
func parseEllipsesRange(pattern string) (seq []string, err error) {
|
||||
if strings.Index(pattern, openBraces) == -1 {
|
||||
return nil, errors.New("Invalid argument")
|
||||
}
|
||||
if strings.Index(pattern, closeBraces) == -1 {
|
||||
return nil, errors.New("Invalid argument")
|
||||
}
|
||||
|
||||
pattern = strings.TrimPrefix(pattern, openBraces)
|
||||
pattern = strings.TrimSuffix(pattern, closeBraces)
|
||||
|
||||
ellipsesRange := strings.Split(pattern, ellipses)
|
||||
if len(ellipsesRange) != 2 {
|
||||
return nil, errors.New("Invalid argument")
|
||||
}
|
||||
|
||||
var start, end uint64
|
||||
if start, err = strconv.ParseUint(ellipsesRange[0], 10, 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if end, err = strconv.ParseUint(ellipsesRange[1], 10, 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if start > end {
|
||||
return nil, fmt.Errorf("Incorrect range start %d cannot be bigger than end %d", start, end)
|
||||
}
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
if strings.HasPrefix(ellipsesRange[0], "0") && len(ellipsesRange[0]) > 1 || strings.HasPrefix(ellipsesRange[1], "0") {
|
||||
seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dd", len(ellipsesRange[1])), i))
|
||||
} else {
|
||||
seq = append(seq, fmt.Sprintf("%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
// Pattern - ellipses pattern, describes the range and also the
|
||||
// associated prefix and suffixes.
|
||||
type Pattern struct {
|
||||
Prefix string
|
||||
Suffix string
|
||||
Seq []string
|
||||
}
|
||||
|
||||
// argExpander - recursively expands labels into its respective forms.
|
||||
func argExpander(labels [][]string) (out [][]string) {
|
||||
if len(labels) == 1 {
|
||||
for _, v := range labels[0] {
|
||||
out = append(out, []string{v})
|
||||
}
|
||||
return out
|
||||
}
|
||||
for _, lbl := range labels[0] {
|
||||
rs := argExpander(labels[1:])
|
||||
for _, rlbls := range rs {
|
||||
r := append(rlbls, []string{lbl}...)
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ArgPattern contains a list of patterns provided in the input.
|
||||
type ArgPattern []Pattern
|
||||
|
||||
// Expand - expands all the ellipses patterns in
|
||||
// the given argument.
|
||||
func (a ArgPattern) Expand() [][]string {
|
||||
labels := make([][]string, len(a))
|
||||
for i := range labels {
|
||||
labels[i] = a[i].Expand()
|
||||
}
|
||||
return argExpander(labels)
|
||||
}
|
||||
|
||||
// Expand - expands a ellipses pattern.
|
||||
func (p Pattern) Expand() []string {
|
||||
var labels []string
|
||||
for i := range p.Seq {
|
||||
switch {
|
||||
case p.Prefix != "" && p.Suffix == "":
|
||||
labels = append(labels, fmt.Sprintf("%s%s", p.Prefix, p.Seq[i]))
|
||||
case p.Suffix != "" && p.Prefix == "":
|
||||
labels = append(labels, fmt.Sprintf("%s%s", p.Seq[i], p.Suffix))
|
||||
case p.Suffix == "" && p.Prefix == "":
|
||||
labels = append(labels, fmt.Sprintf("%s", p.Seq[i]))
|
||||
default:
|
||||
labels = append(labels, fmt.Sprintf("%s%s%s", p.Prefix, p.Seq[i], p.Suffix))
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
// HasEllipses - returns true if input arg has ellipses type pattern.
|
||||
func HasEllipses(args ...string) bool {
|
||||
var ok = true
|
||||
for _, arg := range args {
|
||||
ok = ok && (strings.Count(arg, ellipses) > 0 || (strings.Count(arg, openBraces) > 0 && strings.Count(arg, closeBraces) > 0))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrInvalidEllipsesFormatFn error returned when invalid ellipses format is detected.
|
||||
var ErrInvalidEllipsesFormatFn = func(arg string) error {
|
||||
return fmt.Errorf("Invalid ellipsis format in (%s), Ellipsis range must be provided in format {N...M} where N and M are positive integers, M must be greater than N, with an allowed minimum range of 4", arg)
|
||||
}
|
||||
|
||||
// FindEllipsesPatterns - finds all ellipses patterns, recursively and parses the ranges numerically.
|
||||
func FindEllipsesPatterns(arg string) (ArgPattern, error) {
|
||||
var patterns []Pattern
|
||||
parts := regexpEllipses.FindStringSubmatch(arg)
|
||||
if len(parts) == 0 {
|
||||
// We throw an error if arg doesn't have any recognizable ellipses pattern.
|
||||
return nil, ErrInvalidEllipsesFormatFn(arg)
|
||||
}
|
||||
|
||||
parts = parts[1:]
|
||||
patternFound := regexpEllipses.MatchString(parts[0])
|
||||
for patternFound {
|
||||
seq, err := parseEllipsesRange(parts[1])
|
||||
if err != nil {
|
||||
return patterns, err
|
||||
}
|
||||
patterns = append(patterns, Pattern{
|
||||
Prefix: "",
|
||||
Suffix: parts[2],
|
||||
Seq: seq,
|
||||
})
|
||||
parts = regexpEllipses.FindStringSubmatch(parts[0])
|
||||
if len(parts) > 0 {
|
||||
parts = parts[1:]
|
||||
patternFound = HasEllipses(parts[0])
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
seq, err := parseEllipsesRange(parts[1])
|
||||
if err != nil {
|
||||
return patterns, err
|
||||
}
|
||||
|
||||
patterns = append(patterns, Pattern{
|
||||
Prefix: parts[0],
|
||||
Suffix: parts[2],
|
||||
Seq: seq,
|
||||
})
|
||||
}
|
||||
|
||||
// Check if any of the prefix or suffixes now have flower braces
|
||||
// left over, in such a case we generally think that there is
|
||||
// perhaps a typo in users input and error out accordingly.
|
||||
for _, pattern := range patterns {
|
||||
if strings.Count(pattern.Prefix, openBraces) > 0 || strings.Count(pattern.Prefix, closeBraces) > 0 {
|
||||
return nil, ErrInvalidEllipsesFormatFn(arg)
|
||||
}
|
||||
if strings.Count(pattern.Suffix, openBraces) > 0 || strings.Count(pattern.Suffix, closeBraces) > 0 {
|
||||
return nil, ErrInvalidEllipsesFormatFn(arg)
|
||||
}
|
||||
}
|
||||
|
||||
return patterns, nil
|
||||
}
|
||||
244
pkg/ellipses/ellipses_test.go
Normal file
244
pkg/ellipses/ellipses_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2018 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 ellipses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test tests args with ellipses.
|
||||
func TestHasEllipses(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
expectedOk bool
|
||||
}{
|
||||
// Tests for all args without ellipses.
|
||||
{
|
||||
[]string{"64"},
|
||||
false,
|
||||
},
|
||||
// Found flower braces, still attempt to parse and throw an error.
|
||||
{
|
||||
[]string{"{1..64}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{1..2..}"},
|
||||
true,
|
||||
},
|
||||
// Test for valid input.
|
||||
{
|
||||
[]string{"1...64"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{1...2O}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"..."},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{-1...1}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{0...-1}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{1....4}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{1...64}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{...}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"{1...64}", "{65...128}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"http://minio{2...3}/export/set{1...64}"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"http://minio{2...3}/export/set{1...64}",
|
||||
"http://minio{2...3}/export/set{65...128}",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"mydisk-{a...z}{1...20}",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"mydisk-{1...4}{1..2.}",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
|
||||
gotOk := HasEllipses(testCase.args...)
|
||||
if gotOk != testCase.expectedOk {
|
||||
t.Errorf("Expected %t, got %t", testCase.expectedOk, gotOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test tests find ellipses patterns.
|
||||
func TestFindEllipsesPatterns(t *testing.T) {
|
||||
testCases := []struct {
|
||||
pattern string
|
||||
success bool
|
||||
expectedCount int
|
||||
}{
|
||||
// Tests for all invalid inputs
|
||||
{
|
||||
"{1..64}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"1...64",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"...",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{1...",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"...64}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{...}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{-1...1}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{0...-1}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{1...2O}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{64...1}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{1....4}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"mydisk-{a...z}{1...20}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"mydisk-{1...4}{1..2.}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{1..2.}-mydisk-{1...4}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{{1...4}}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"{4...02}",
|
||||
false,
|
||||
0,
|
||||
},
|
||||
// Test for valid input.
|
||||
{
|
||||
"{1...64}",
|
||||
true,
|
||||
64,
|
||||
},
|
||||
{
|
||||
"{1...64} {65...128}",
|
||||
true,
|
||||
4096,
|
||||
},
|
||||
{
|
||||
"{01...036}",
|
||||
true,
|
||||
36,
|
||||
},
|
||||
{
|
||||
"{001...036}",
|
||||
true,
|
||||
36,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
|
||||
argP, err := FindEllipsesPatterns(testCase.pattern)
|
||||
if err != nil && testCase.success {
|
||||
t.Errorf("Expected success but failed instead %s", err)
|
||||
}
|
||||
if err == nil && !testCase.success {
|
||||
t.Errorf("Expected failure but passed instead")
|
||||
}
|
||||
if err == nil {
|
||||
gotCount := len(argP.Expand())
|
||||
if gotCount != testCase.expectedCount {
|
||||
t.Errorf("Expected %d, got %d", testCase.expectedCount, gotCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
* Minio Cloud Storage, (C) 2017, 2018 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
|
||||
@@ -75,6 +73,13 @@ const (
|
||||
DriveStateMissing = "missing"
|
||||
)
|
||||
|
||||
// HealDriveInfo - struct for an individual drive info item.
|
||||
type HealDriveInfo struct {
|
||||
UUID string `json:"uuid"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
// HealResultItem - struct for an individual heal result item
|
||||
type HealResultItem struct {
|
||||
ResultIndex int64 `json:"resultId"`
|
||||
@@ -85,33 +90,87 @@ type HealResultItem struct {
|
||||
ParityBlocks int `json:"parityBlocks,omitempty"`
|
||||
DataBlocks int `json:"dataBlocks,omitempty"`
|
||||
DiskCount int `json:"diskCount"`
|
||||
DriveInfo struct {
|
||||
// below maps are from drive endpoint to drive state
|
||||
Before map[string]string `json:"before"`
|
||||
After map[string]string `json:"after"`
|
||||
} `json:"drives"`
|
||||
SetCount int `json:"setCount"`
|
||||
// below slices are from drive info.
|
||||
Before struct {
|
||||
Drives []HealDriveInfo `json:"drives"`
|
||||
} `json:"before"`
|
||||
After struct {
|
||||
Drives []HealDriveInfo `json:"drives"`
|
||||
} `json:"after"`
|
||||
ObjectSize int64 `json:"objectSize"`
|
||||
}
|
||||
|
||||
// InitDrives - initialize maps used to represent drive info
|
||||
func (hri *HealResultItem) InitDrives() {
|
||||
hri.DriveInfo.Before = make(map[string]string)
|
||||
hri.DriveInfo.After = make(map[string]string)
|
||||
// GetMissingCounts - returns the number of missing disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetMissingCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateMissing {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateMissing {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOnlineCounts - returns the number of online disks before and
|
||||
// after heal
|
||||
// GetOfflineCounts - returns the number of offline disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetOfflineCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateOffline {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateOffline {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetCorruptedCounts - returns the number of corrupted disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetCorruptedCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateCorrupt {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateCorrupt {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOnlineCounts - returns the number of online disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetOnlineCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.DriveInfo.Before {
|
||||
if v == DriveStateOk {
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateOk {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.DriveInfo.After {
|
||||
if v == DriveStateOk {
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateOk {
|
||||
a++
|
||||
}
|
||||
}
|
||||
|
||||
73
pkg/madmin/heal-commands_test.go
Normal file
73
pkg/madmin/heal-commands_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2018 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 madmin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests heal drives missing and offline counts.
|
||||
func TestHealDriveCounts(t *testing.T) {
|
||||
rs := HealResultItem{}
|
||||
rs.Before.Drives = make([]HealDriveInfo, 20)
|
||||
rs.After.Drives = make([]HealDriveInfo, 20)
|
||||
for i := range rs.Before.Drives {
|
||||
if i < 4 {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateMissing}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateMissing}
|
||||
} else if i > 4 && i < 15 {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateOffline}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateOffline}
|
||||
} else if i > 15 {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateCorrupt}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateCorrupt}
|
||||
} else {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateOk}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateOk}
|
||||
}
|
||||
}
|
||||
|
||||
i, j := rs.GetOnlineCounts()
|
||||
if i > 2 {
|
||||
t.Errorf("Expected '2', got %d before online disks", i)
|
||||
}
|
||||
if j > 2 {
|
||||
t.Errorf("Expected '2', got %d after online disks", j)
|
||||
}
|
||||
i, j = rs.GetOfflineCounts()
|
||||
if i > 10 {
|
||||
t.Errorf("Expected '10', got %d before offline disks", i)
|
||||
}
|
||||
if j > 10 {
|
||||
t.Errorf("Expected '10', got %d after offline disks", j)
|
||||
}
|
||||
i, j = rs.GetCorruptedCounts()
|
||||
if i > 4 {
|
||||
t.Errorf("Expected '4', got %d before corrupted disks", i)
|
||||
}
|
||||
if j > 4 {
|
||||
t.Errorf("Expected '4', got %d after corrupted disks", j)
|
||||
}
|
||||
i, j = rs.GetMissingCounts()
|
||||
if i > 4 {
|
||||
t.Errorf("Expected '4', got %d before missing disks", i)
|
||||
}
|
||||
if j > 4 {
|
||||
t.Errorf("Expected '4', got %d after missing disks", i)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ const (
|
||||
// Add your own backend.
|
||||
)
|
||||
|
||||
// DriveInfo - represents each drive info, describing
|
||||
// status, uuid and endpoint.
|
||||
type DriveInfo HealDriveInfo
|
||||
|
||||
// StorageInfo - represents total capacity of underlying storage.
|
||||
type StorageInfo struct {
|
||||
// Total disk space.
|
||||
@@ -52,8 +56,13 @@ type StorageInfo struct {
|
||||
// Following fields are only meaningful if BackendType is Erasure.
|
||||
OnlineDisks int // Online disks during server startup.
|
||||
OfflineDisks int // Offline disks during server startup.
|
||||
StandardSCData int // Data disks for currently configured Standard storage class.
|
||||
StandardSCParity int // Parity disks for currently configured Standard storage class.
|
||||
RRSCData int // Data disks for currently configured Reduced Redundancy storage class.
|
||||
RRSCParity int // Parity disks for currently configured Reduced Redundancy storage class.
|
||||
|
||||
// List of all disk status, this is only meaningful if BackendType is Erasure.
|
||||
Sets [][]DriveInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
59
pkg/sync/errgroup/errgroup.go
Normal file
59
pkg/sync/errgroup/errgroup.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 errgroup
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Group is a collection of goroutines working on subtasks that are part of
|
||||
// the same overall task.
|
||||
//
|
||||
// A zero Group is valid and does not cancel on error.
|
||||
type Group struct {
|
||||
wg sync.WaitGroup
|
||||
errs []error
|
||||
}
|
||||
|
||||
// WithNErrs returns a new Group with length of errs slice upto nerrs,
|
||||
// upon Wait() errors are returned collected from all tasks.
|
||||
func WithNErrs(nerrs int) *Group {
|
||||
return &Group{errs: make([]error, nerrs)}
|
||||
}
|
||||
|
||||
// Wait blocks until all function calls from the Go method have returned, then
|
||||
// returns the slice of errors from all function calls.
|
||||
func (g *Group) Wait() []error {
|
||||
g.wg.Wait()
|
||||
return g.errs
|
||||
}
|
||||
|
||||
// Go calls the given function in a new goroutine.
|
||||
//
|
||||
// The first call to return a non-nil error will be
|
||||
// collected in errs slice and returned by Wait().
|
||||
func (g *Group) Go(f func() error, index int) {
|
||||
g.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
|
||||
if err := f(); err != nil {
|
||||
g.errs[index] = err
|
||||
}
|
||||
}()
|
||||
}
|
||||
52
pkg/sync/errgroup/errgroup_test.go
Normal file
52
pkg/sync/errgroup/errgroup_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 errgroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGroupWithNErrs(t *testing.T) {
|
||||
err1 := fmt.Errorf("errgroup_test: 1")
|
||||
err2 := fmt.Errorf("errgroup_test: 2")
|
||||
|
||||
cases := []struct {
|
||||
errs []error
|
||||
}{
|
||||
{errs: []error{nil}},
|
||||
{errs: []error{err1}},
|
||||
{errs: []error{err1, nil}},
|
||||
{errs: []error{err1, nil, err2}},
|
||||
}
|
||||
|
||||
for j, tc := range cases {
|
||||
t.Run(fmt.Sprintf("Test%d", j+1), func(t *testing.T) {
|
||||
g := WithNErrs(len(tc.errs))
|
||||
for i, err := range tc.errs {
|
||||
err := err
|
||||
g.Go(func() error { return err }, i)
|
||||
}
|
||||
|
||||
gotErrs := g.Wait()
|
||||
if !reflect.DeepEqual(gotErrs, tc.errs) {
|
||||
t.Errorf("Expected %#v, got %#v", tc.errs, gotErrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user