mirror of
https://github.com/pgsty/minio.git
synced 2026-07-20 04:30:26 +03:00
[feature] allow for an odd number of erasure packs (#9221)
Too many deployments come up with an odd number of hosts or drives, to facilitate even distribution among those setups allow for odd and prime numbers based packs.
This commit is contained in:
@@ -40,7 +40,7 @@ type endpointSet struct {
|
||||
|
||||
// Supported set sizes this is used to find the optimal
|
||||
// single set size.
|
||||
var setSizes = []uint64{4, 6, 8, 10, 12, 14, 16}
|
||||
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.
|
||||
@@ -60,7 +60,7 @@ func getDivisibleSize(totalSizes []uint64) (result uint64) {
|
||||
|
||||
// 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] && count%2 == 0)
|
||||
return (count >= setSizes[0] && count <= setSizes[len(setSizes)-1])
|
||||
}
|
||||
|
||||
// getSetIndexes returns list of indexes which provides the set size
|
||||
|
||||
@@ -130,9 +130,9 @@ func TestGetSetIndexesEnvOverride(t *testing.T) {
|
||||
{
|
||||
[]string{"http://host{1...11}/data{1...11}"},
|
||||
[]uint64{121},
|
||||
nil,
|
||||
[][]uint64{{11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}},
|
||||
11,
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"data{1...60}"},
|
||||
@@ -183,12 +183,6 @@ func TestGetSetIndexes(t *testing.T) {
|
||||
success bool
|
||||
}{
|
||||
// Invalid inputs.
|
||||
{
|
||||
[]string{"data{1...27}"},
|
||||
[]uint64{27},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{"data{1...3}"},
|
||||
[]uint64{3},
|
||||
@@ -202,6 +196,12 @@ func TestGetSetIndexes(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
// Valid inputs.
|
||||
{
|
||||
[]string{"data{1...27}"},
|
||||
[]uint64{27},
|
||||
[][]uint64{{9, 9, 9}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"data/controller1/export{1...4}, data/controller2/export{1...8}, data/controller3/export{1...12}"},
|
||||
[]uint64{4, 8, 12},
|
||||
@@ -223,7 +223,7 @@ func TestGetSetIndexes(t *testing.T) {
|
||||
{
|
||||
[]string{"data/controller{1...11}/export{1...8}"},
|
||||
[]uint64{88},
|
||||
[][]uint64{{8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}},
|
||||
[][]uint64{{11, 11, 11, 11, 11, 11, 11, 11}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
@@ -292,12 +292,6 @@ func TestParseEndpointSet(t *testing.T) {
|
||||
endpointSet{},
|
||||
false,
|
||||
},
|
||||
// Indivisible range.
|
||||
{
|
||||
"{1...27}",
|
||||
endpointSet{},
|
||||
false,
|
||||
},
|
||||
// No range specified.
|
||||
{
|
||||
"{...}",
|
||||
@@ -323,6 +317,23 @@ func TestParseEndpointSet(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
// Tests valid inputs.
|
||||
{
|
||||
"{1...27}",
|
||||
endpointSet{
|
||||
[]ellipses.ArgPattern{
|
||||
[]ellipses.Pattern{
|
||||
{
|
||||
Prefix: "",
|
||||
Suffix: "",
|
||||
Seq: getSequences(1, 27, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
[][]uint64{{9, 9, 9}},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"/export/set{1...64}",
|
||||
endpointSet{
|
||||
|
||||
+1
-1
@@ -705,7 +705,7 @@ func saveFormatXLAll(ctx context.Context, storageDisks []StorageAPI, formats []*
|
||||
}, index)
|
||||
}
|
||||
|
||||
writeQuorum := len(storageDisks)/2 + 1
|
||||
writeQuorum := getWriteQuorum(len(storageDisks))
|
||||
// Wait for the routines to finish.
|
||||
return reduceWriteQuorumErrs(ctx, g.Wait(), nil, writeQuorum)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,22 @@ func path2BucketObject(s string) (bucket, prefix string) {
|
||||
return path2BucketObjectWithBasePath("", s)
|
||||
}
|
||||
|
||||
func getDefaultParityBlocks(drive int) int {
|
||||
return drive / 2
|
||||
}
|
||||
|
||||
func getDefaultDataBlocks(drive int) int {
|
||||
return drive - getDefaultParityBlocks(drive)
|
||||
}
|
||||
|
||||
func getReadQuorum(drive int) int {
|
||||
return getDefaultDataBlocks(drive)
|
||||
}
|
||||
|
||||
func getWriteQuorum(drive int) int {
|
||||
return getDefaultDataBlocks(drive) + 1
|
||||
}
|
||||
|
||||
// URI scheme constants.
|
||||
const (
|
||||
httpScheme = "http"
|
||||
|
||||
+3
-3
@@ -65,7 +65,7 @@ func (xl xlObjects) MakeBucketWithLocation(ctx context.Context, bucket, location
|
||||
}, index)
|
||||
}
|
||||
|
||||
writeQuorum := len(storageDisks)/2 + 1
|
||||
writeQuorum := getWriteQuorum(len(storageDisks))
|
||||
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, writeQuorum)
|
||||
if err == errXLWriteQuorum {
|
||||
// Purge successfully created buckets if we don't have writeQuorum.
|
||||
@@ -136,7 +136,7 @@ func (xl xlObjects) getBucketInfo(ctx context.Context, bucketName string) (bucke
|
||||
// reduce to one error based on read quorum.
|
||||
// `nil` is deliberately passed for ignoredErrs
|
||||
// because these errors were already ignored.
|
||||
readQuorum := len(xl.getDisks()) / 2
|
||||
readQuorum := getReadQuorum(len(xl.getDisks()))
|
||||
return BucketInfo{}, reduceReadQuorumErrs(ctx, bucketErrs, nil, readQuorum)
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string, forceDelete
|
||||
return nil
|
||||
}
|
||||
|
||||
writeQuorum := len(storageDisks)/2 + 1
|
||||
writeQuorum := getWriteQuorum(len(storageDisks))
|
||||
err := reduceWriteQuorumErrs(ctx, dErrs, bucketOpIgnoredErrs, writeQuorum)
|
||||
if err == errXLWriteQuorum {
|
||||
undoDeleteBucket(storageDisks, bucket)
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ func (xl xlObjects) isObject(bucket, prefix string) (ok bool) {
|
||||
// quorum intentionally, but rely on the default case scenario. Actual quorum
|
||||
// verification will happen by top layer by using getObjectInfo() and will be
|
||||
// ignored if necessary.
|
||||
readQuorum := len(storageDisks) / 2
|
||||
readQuorum := getReadQuorum(len(storageDisks))
|
||||
|
||||
return reduceReadQuorumErrs(context.Background(), g.Wait(), objectOpIgnoredErrs, readQuorum) == nil
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (xl xlObjects) HealBucket(ctx context.Context, bucket string, dryRun, remov
|
||||
storageDisks := xl.getDisks()
|
||||
|
||||
// get write quorum for an object
|
||||
writeQuorum := len(storageDisks)/2 + 1
|
||||
writeQuorum := getWriteQuorum(len(storageDisks))
|
||||
|
||||
// Heal bucket.
|
||||
return healBucket(ctx, storageDisks, bucket, writeQuorum, dryRun)
|
||||
@@ -314,7 +314,7 @@ func (xl xlObjects) healObject(ctx context.Context, bucket string, object string
|
||||
if m, ok := isObjectDangling(partsMetadata, errs, dataErrs); ok {
|
||||
writeQuorum := m.Erasure.DataBlocks + 1
|
||||
if m.Erasure.DataBlocks == 0 {
|
||||
writeQuorum = len(storageDisks)/2 + 1
|
||||
writeQuorum = getWriteQuorum(len(storageDisks))
|
||||
}
|
||||
if !dryRun && remove {
|
||||
err = xl.deleteObject(ctx, bucket, object, writeQuorum, false)
|
||||
@@ -492,8 +492,8 @@ func (xl xlObjects) healObjectDir(ctx context.Context, bucket, object string, dr
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
DiskCount: len(storageDisks),
|
||||
ParityBlocks: len(storageDisks) / 2,
|
||||
DataBlocks: len(storageDisks) / 2,
|
||||
ParityBlocks: getDefaultParityBlocks(len(storageDisks)),
|
||||
DataBlocks: getDefaultDataBlocks(len(storageDisks)),
|
||||
ObjectSize: 0,
|
||||
}
|
||||
|
||||
@@ -601,8 +601,8 @@ func defaultHealResult(latestXLMeta xlMetaV1, storageDisks []StorageAPI, errs []
|
||||
|
||||
if !latestXLMeta.IsValid() {
|
||||
// Default to most common configuration for erasure blocks.
|
||||
result.ParityBlocks = len(storageDisks) / 2
|
||||
result.DataBlocks = len(storageDisks) / 2
|
||||
result.ParityBlocks = getDefaultParityBlocks(len(storageDisks))
|
||||
result.DataBlocks = getDefaultDataBlocks(len(storageDisks))
|
||||
} else {
|
||||
result.ParityBlocks = latestXLMeta.Erasure.ParityBlocks
|
||||
result.DataBlocks = latestXLMeta.Erasure.DataBlocks
|
||||
@@ -729,7 +729,7 @@ func (xl xlObjects) HealObject(ctx context.Context, bucket, object string, opts
|
||||
if m, ok := isObjectDangling(partsMetadata, errs, []error{}); ok {
|
||||
writeQuorum := m.Erasure.DataBlocks + 1
|
||||
if m.Erasure.DataBlocks == 0 {
|
||||
writeQuorum = len(xl.getDisks())/2 + 1
|
||||
writeQuorum = getWriteQuorum(len(storageDisks))
|
||||
}
|
||||
if !opts.DryRun && opts.Remove {
|
||||
xl.deleteObject(healCtx, bucket, object, writeQuorum, false)
|
||||
@@ -758,7 +758,7 @@ func (xl xlObjects) HealObject(ctx context.Context, bucket, object string, opts
|
||||
if m, ok := isObjectDangling(partsMetadata, errs, []error{}); ok {
|
||||
writeQuorum := m.Erasure.DataBlocks + 1
|
||||
if m.Erasure.DataBlocks == 0 {
|
||||
writeQuorum = len(storageDisks)/2 + 1
|
||||
writeQuorum = getWriteQuorum(len(storageDisks))
|
||||
}
|
||||
if !opts.DryRun && opts.Remove {
|
||||
xl.deleteObject(ctx, bucket, object, writeQuorum, false)
|
||||
|
||||
@@ -377,61 +377,6 @@ func pickValidXLMeta(ctx context.Context, metaArr []xlMetaV1, modTime time.Time,
|
||||
// list of all errors that can be ignored in a metadata operation.
|
||||
var objMetadataOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errVolumeNotFound, errFileNotFound, errFileAccessDenied, errCorruptedFormat)
|
||||
|
||||
// readXLMetaParts - returns the XL Metadata Parts from xl.json of one of the disks picked at random.
|
||||
func (xl xlObjects) readXLMetaParts(ctx context.Context, bucket, object string) (xlMetaParts []ObjectPartInfo, xlMeta map[string]string, err error) {
|
||||
var ignoredErrs []error
|
||||
for _, disk := range xl.getLoadBalancedDisks() {
|
||||
if disk == nil {
|
||||
ignoredErrs = append(ignoredErrs, errDiskNotFound)
|
||||
continue
|
||||
}
|
||||
xlMetaParts, xlMeta, err = readXLMetaParts(ctx, disk, bucket, object)
|
||||
if err == nil {
|
||||
return xlMetaParts, xlMeta, nil
|
||||
}
|
||||
// For any reason disk or bucket is not available continue
|
||||
// and read from other disks.
|
||||
if IsErrIgnored(err, objMetadataOpIgnoredErrs...) {
|
||||
ignoredErrs = append(ignoredErrs, err)
|
||||
continue
|
||||
}
|
||||
// Error is not ignored, return right here.
|
||||
return nil, nil, err
|
||||
}
|
||||
// If all errors were ignored, reduce to maximal occurrence
|
||||
// based on the read quorum.
|
||||
readQuorum := len(xl.getDisks()) / 2
|
||||
return nil, nil, reduceReadQuorumErrs(ctx, ignoredErrs, nil, readQuorum)
|
||||
}
|
||||
|
||||
// readXLMetaStat - return xlMetaV1.Stat and xlMetaV1.Meta from one of the disks picked at random.
|
||||
func (xl xlObjects) readXLMetaStat(ctx context.Context, bucket, object string) (xlStat statInfo, xlMeta map[string]string, err error) {
|
||||
var ignoredErrs []error
|
||||
for _, disk := range xl.getLoadBalancedDisks() {
|
||||
if disk == nil {
|
||||
ignoredErrs = append(ignoredErrs, errDiskNotFound)
|
||||
continue
|
||||
}
|
||||
// parses only xlMetaV1.Meta and xlMeta.Stat
|
||||
xlStat, xlMeta, err = readXLMetaStat(ctx, disk, bucket, object)
|
||||
if err == nil {
|
||||
return xlStat, xlMeta, nil
|
||||
}
|
||||
// For any reason disk or bucket is not available continue
|
||||
// and read from other disks.
|
||||
if IsErrIgnored(err, objMetadataOpIgnoredErrs...) {
|
||||
ignoredErrs = append(ignoredErrs, err)
|
||||
continue
|
||||
}
|
||||
// Error is not ignored, return right here.
|
||||
return statInfo{}, nil, err
|
||||
}
|
||||
// If all errors were ignored, reduce to maximal occurrence
|
||||
// based on the read quorum.
|
||||
readQuorum := len(xl.getDisks()) / 2
|
||||
return statInfo{}, nil, reduceReadQuorumErrs(ctx, ignoredErrs, nil, readQuorum)
|
||||
}
|
||||
|
||||
// writeXLMetadata - writes `xl.json` to a single disk.
|
||||
func writeXLMetadata(ctx context.Context, disk StorageAPI, bucket, prefix string, xlMeta xlMetaV1) error {
|
||||
jsonFile := path.Join(prefix, xlMetaJSONFile)
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -29,166 +26,6 @@ import (
|
||||
|
||||
const ActualSize = 1000
|
||||
|
||||
// Tests for reading XL object info.
|
||||
func TestXLReadStat(t *testing.T) {
|
||||
ExecObjectLayerDiskAlteredTest(t, testXLReadStat)
|
||||
}
|
||||
|
||||
func testXLReadStat(obj ObjectLayer, instanceType string, disks []string, t *testing.T) {
|
||||
// Setup for the tests.
|
||||
bucketName := getRandomBucketName()
|
||||
objectName := "test-object"
|
||||
// create bucket.
|
||||
err := obj.MakeBucketWithLocation(context.Background(), bucketName, "")
|
||||
// Stop the test if creation of the bucket fails.
|
||||
if err != nil {
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
// set of byte data for PutObject.
|
||||
// object has to be created before running tests for GetObject.
|
||||
// this is required even to assert the GetObject data,
|
||||
// since dataInserted === dataFetched back is a primary criteria for any object storage this assertion is critical.
|
||||
bytesData := []struct {
|
||||
byteData []byte
|
||||
}{
|
||||
{generateBytesData(6 * humanize.MiByte)},
|
||||
}
|
||||
// set of inputs for uploading the objects before tests for downloading is done.
|
||||
putObjectInputs := []struct {
|
||||
bucketName string
|
||||
objectName string
|
||||
contentLength int64
|
||||
textData []byte
|
||||
metaData map[string]string
|
||||
}{
|
||||
// case - 1.
|
||||
{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
|
||||
}
|
||||
// iterate through the above set of inputs and upkoad the object.
|
||||
for i, input := range putObjectInputs {
|
||||
// uploading the object.
|
||||
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), ObjectOptions{UserDefined: input.metaData})
|
||||
// if object upload fails stop the test.
|
||||
if err != nil {
|
||||
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
z := obj.(*xlZones)
|
||||
xl := z.zones[0].sets[0]
|
||||
_, _, err = xl.readXLMetaStat(context.Background(), bucketName, objectName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove one disk.
|
||||
removeDiskN(disks, 7)
|
||||
|
||||
// Removing disk shouldn't affect reading object info.
|
||||
_, _, err = xl.readXLMetaStat(context.Background(), bucketName, objectName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, disk := range disks {
|
||||
os.RemoveAll(path.Join(disk, bucketName))
|
||||
}
|
||||
|
||||
_, _, err = xl.readXLMetaStat(context.Background(), bucketName, objectName)
|
||||
if err != errVolumeNotFound {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for reading XL meta parts.
|
||||
func TestXLReadMetaParts(t *testing.T) {
|
||||
ExecObjectLayerDiskAlteredTest(t, testXLReadMetaParts)
|
||||
}
|
||||
|
||||
// testListObjectParts - Tests validate listing of object parts when disks go offline.
|
||||
func testXLReadMetaParts(obj ObjectLayer, instanceType string, disks []string, t *testing.T) {
|
||||
bucketNames := []string{"minio-bucket", "minio-2-bucket"}
|
||||
objectNames := []string{"minio-object-1.txt"}
|
||||
uploadIDs := []string{}
|
||||
var opts ObjectOptions
|
||||
|
||||
// bucketnames[0].
|
||||
// objectNames[0].
|
||||
// uploadIds [0].
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "")
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
// Initiate Multipart Upload on the above created bucket.
|
||||
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts)
|
||||
if err != nil {
|
||||
// Failed to create NewMultipartUpload, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
uploadIDs = append(uploadIDs, uploadID)
|
||||
|
||||
// Create multipart parts.
|
||||
// Need parts to be uploaded before MultipartLists can be called and tested.
|
||||
createPartCases := []struct {
|
||||
bucketName string
|
||||
objName string
|
||||
uploadID string
|
||||
PartID int
|
||||
inputReaderData string
|
||||
inputMd5 string
|
||||
intputDataSize int64
|
||||
expectedMd5 string
|
||||
}{
|
||||
// Case 1-4.
|
||||
// Creating sequence of parts for same uploadID.
|
||||
// Used to ensure that the ListMultipartResult produces one output for the four parts uploaded below for the given upload ID.
|
||||
{bucketNames[0], objectNames[0], uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"},
|
||||
{bucketNames[0], objectNames[0], uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh")), "1f7690ebdd9b4caf8fab49ca1757bf27"},
|
||||
{bucketNames[0], objectNames[0], uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd")), "09a0877d04abf8759f99adec02baf579"},
|
||||
{bucketNames[0], objectNames[0], uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd")), "e132e96a5ddad6da8b07bba6f6131fef"},
|
||||
}
|
||||
sha256sum := ""
|
||||
// Iterating over creatPartCases to generate multipart chunks.
|
||||
for _, testCase := range createPartCases {
|
||||
_, perr := obj.PutObjectPart(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, testCase.PartID, mustGetPutObjReader(t, bytes.NewBufferString(testCase.inputReaderData), testCase.intputDataSize, testCase.inputMd5, sha256sum), opts)
|
||||
if perr != nil {
|
||||
t.Fatalf("%s : %s", instanceType, perr)
|
||||
}
|
||||
}
|
||||
|
||||
z := obj.(*xlZones)
|
||||
xl := z.zones[0].sets[0]
|
||||
uploadIDPath := xl.getUploadIDDir(bucketNames[0], objectNames[0], uploadIDs[0])
|
||||
|
||||
_, _, err = xl.readXLMetaParts(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove one disk.
|
||||
removeDiskN(disks, 7)
|
||||
|
||||
// Removing disk shouldn't affect reading object parts info.
|
||||
_, _, err = xl.readXLMetaParts(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, disk := range disks {
|
||||
os.RemoveAll(path.Join(disk, bucketNames[0]))
|
||||
os.RemoveAll(path.Join(disk, minioMetaMultipartBucket, xl.getMultipartSHADir(bucketNames[0], objectNames[0])))
|
||||
}
|
||||
|
||||
_, _, err = xl.readXLMetaParts(context.Background(), minioMetaMultipartBucket, uploadIDPath)
|
||||
if err != errFileNotFound {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test xlMetaV1.AddObjectPart()
|
||||
func TestAddObjectPart(t *testing.T) {
|
||||
testCases := []struct {
|
||||
|
||||
@@ -808,7 +808,8 @@ func (xl xlObjects) cleanupStaleMultipartUploadsOnDisk(ctx context.Context, disk
|
||||
continue
|
||||
}
|
||||
if now.Sub(fi.ModTime) > expiry {
|
||||
xl.deleteObject(ctx, minioMetaMultipartBucket, uploadIDPath, len(xl.getDisks())/2+1, false)
|
||||
writeQuorum := getWriteQuorum(len(xl.getDisks()))
|
||||
xl.deleteObject(ctx, minioMetaMultipartBucket, uploadIDPath, writeQuorum, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -340,7 +340,7 @@ func (xl xlObjects) getObjectInfoDir(ctx context.Context, bucket, object string)
|
||||
}, index)
|
||||
}
|
||||
|
||||
readQuorum := len(storageDisks) / 2
|
||||
readQuorum := getReadQuorum(len(storageDisks))
|
||||
err := reduceReadQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, readQuorum)
|
||||
return dirObjectInfo(bucket, object, 0, map[string]string{}), err
|
||||
}
|
||||
@@ -488,7 +488,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string,
|
||||
// Get parity and data drive count based on storage class metadata
|
||||
parityDrives := globalStorageClass.GetParityForSC(opts.UserDefined[xhttp.AmzStorageClass])
|
||||
if parityDrives == 0 {
|
||||
parityDrives = len(storageDisks) / 2
|
||||
parityDrives = getDefaultParityBlocks(len(storageDisks))
|
||||
}
|
||||
dataDrives := len(storageDisks) - parityDrives
|
||||
|
||||
@@ -842,11 +842,13 @@ func (xl xlObjects) deleteObjects(ctx context.Context, bucket string, objects []
|
||||
isObjectDirs[i] = HasSuffix(object, SlashSeparator)
|
||||
}
|
||||
|
||||
storageDisks := xl.getDisks()
|
||||
|
||||
for i, object := range objects {
|
||||
if isObjectDirs[i] {
|
||||
_, err := xl.getObjectInfoDir(ctx, bucket, object)
|
||||
if err == errXLReadQuorum {
|
||||
if isObjectDirDangling(statAllDirs(ctx, xl.getDisks(), bucket, object)) {
|
||||
if isObjectDirDangling(statAllDirs(ctx, storageDisks, bucket, object)) {
|
||||
// If object is indeed dangling, purge it.
|
||||
errs[i] = nil
|
||||
}
|
||||
@@ -868,7 +870,7 @@ func (xl xlObjects) deleteObjects(ctx context.Context, bucket string, objects []
|
||||
// class for objects which have reduced quorum
|
||||
// storage class only needs to be honored for
|
||||
// Read() requests alone which we already do.
|
||||
writeQuorums[i] = len(xl.getDisks())/2 + 1
|
||||
writeQuorums[i] = getWriteQuorum(len(storageDisks))
|
||||
}
|
||||
|
||||
return xl.doDeleteObjects(ctx, bucket, objects, errs, writeQuorums, isObjectDirs)
|
||||
@@ -934,10 +936,12 @@ func (xl xlObjects) DeleteObject(ctx context.Context, bucket, object string) (er
|
||||
var writeQuorum int
|
||||
var isObjectDir = HasSuffix(object, SlashSeparator)
|
||||
|
||||
storageDisks := xl.getDisks()
|
||||
|
||||
if isObjectDir {
|
||||
_, err = xl.getObjectInfoDir(ctx, bucket, object)
|
||||
if err == errXLReadQuorum {
|
||||
if isObjectDirDangling(statAllDirs(ctx, xl.getDisks(), bucket, object)) {
|
||||
if isObjectDirDangling(statAllDirs(ctx, storageDisks, bucket, object)) {
|
||||
// If object is indeed dangling, purge it.
|
||||
err = nil
|
||||
}
|
||||
@@ -948,10 +952,10 @@ func (xl xlObjects) DeleteObject(ctx context.Context, bucket, object string) (er
|
||||
}
|
||||
|
||||
if isObjectDir {
|
||||
writeQuorum = len(xl.getDisks())/2 + 1
|
||||
writeQuorum = getWriteQuorum(len(storageDisks))
|
||||
} else {
|
||||
// Read metadata associated with the object from all disks.
|
||||
partsMetadata, errs := readAllXLMetadata(ctx, xl.getDisks(), bucket, object)
|
||||
partsMetadata, errs := readAllXLMetadata(ctx, storageDisks, bucket, object)
|
||||
// get Quorum for this object
|
||||
_, writeQuorum, err = objectQuorumFromMeta(ctx, xl, partsMetadata, errs)
|
||||
if err != nil {
|
||||
|
||||
@@ -122,44 +122,6 @@ func xlMetaV1UnmarshalJSON(ctx context.Context, xlMetaBuf []byte) (xlMeta xlMeta
|
||||
return xlMeta, err
|
||||
}
|
||||
|
||||
// read xl.json from the given disk, parse and return xlV1MetaV1.Parts.
|
||||
func readXLMetaParts(ctx context.Context, disk StorageAPI, bucket string, object string) ([]ObjectPartInfo, map[string]string, error) {
|
||||
// Reads entire `xl.json`.
|
||||
xlMetaBuf, err := disk.ReadAll(bucket, path.Join(object, xlMetaJSONFile))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var xlMeta xlMetaV1
|
||||
xlMeta, err = xlMetaV1UnmarshalJSON(ctx, xlMetaBuf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return xlMeta.Parts, xlMeta.Meta, nil
|
||||
}
|
||||
|
||||
// read xl.json from the given disk and parse xlV1Meta.Stat and xlV1Meta.Meta using jsoniter.
|
||||
func readXLMetaStat(ctx context.Context, disk StorageAPI, bucket string, object string) (si statInfo,
|
||||
mp map[string]string, e error) {
|
||||
// Reads entire `xl.json`.
|
||||
xlMetaBuf, err := disk.ReadAll(bucket, path.Join(object, xlMetaJSONFile))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return si, nil, err
|
||||
}
|
||||
|
||||
var xlMeta xlMetaV1
|
||||
xlMeta, err = xlMetaV1UnmarshalJSON(ctx, xlMetaBuf)
|
||||
if err != nil {
|
||||
return si, mp, err
|
||||
}
|
||||
|
||||
// Return structured `xl.json`.
|
||||
return xlMeta.Stat, xlMeta.Meta, nil
|
||||
}
|
||||
|
||||
// readXLMeta reads `xl.json` and returns back XL metadata structure.
|
||||
func readXLMeta(ctx context.Context, disk StorageAPI, bucket string, object string) (xlMeta xlMetaV1, err error) {
|
||||
// Reads entire `xl.json`.
|
||||
|
||||
Reference in New Issue
Block a user