mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
	
	
		
			319 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
		
		
			
		
	
	
			319 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
|  | /* | ||
|  |  * MinIO Cloud Storage, (C) 2020 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 cmd | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"bytes" | ||
|  | 	"encoding/hex" | ||
|  | 	"encoding/json" | ||
|  | 	"testing" | ||
|  | 
 | ||
|  | 	"github.com/dustin/go-humanize" | ||
|  | 	jsoniter "github.com/json-iterator/go" | ||
|  | ) | ||
|  | 
 | ||
|  | func TestIsXLMetaFormatValid(t *testing.T) { | ||
|  | 	tests := []struct { | ||
|  | 		name    int | ||
|  | 		version string | ||
|  | 		format  string | ||
|  | 		want    bool | ||
|  | 	}{ | ||
|  | 		{1, "123", "fs", false}, | ||
|  | 		{2, "123", xlMetaFormat, false}, | ||
|  | 		{3, xlMetaVersion100, "test", false}, | ||
|  | 		{4, xlMetaVersion101, "hello", false}, | ||
|  | 		{5, xlMetaVersion100, xlMetaFormat, true}, | ||
|  | 		{6, xlMetaVersion101, xlMetaFormat, true}, | ||
|  | 	} | ||
|  | 	for _, tt := range tests { | ||
|  | 		if got := isXLMetaFormatValid(tt.version, tt.format); got != tt.want { | ||
|  | 			t.Errorf("Test %d: Expected %v but received %v", tt.name, got, tt.want) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestIsXLMetaErasureInfoValid(t *testing.T) { | ||
|  | 	tests := []struct { | ||
|  | 		name   int | ||
|  | 		data   int | ||
|  | 		parity int | ||
|  | 		want   bool | ||
|  | 	}{ | ||
|  | 		{1, 5, 6, false}, | ||
|  | 		{2, 5, 5, true}, | ||
|  | 		{3, 0, 5, false}, | ||
|  | 		{4, 5, 0, false}, | ||
|  | 		{5, 5, 0, false}, | ||
|  | 		{6, 5, 4, true}, | ||
|  | 	} | ||
|  | 	for _, tt := range tests { | ||
|  | 		if got := isXLMetaErasureInfoValid(tt.data, tt.parity); got != tt.want { | ||
|  | 			t.Errorf("Test %d: Expected %v but received %v", tt.name, got, tt.want) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // newTestXLMetaV1 - initializes new xlMetaV1Object, adds version, allocates a fresh erasure info and metadata.
 | ||
|  | func newTestXLMetaV1() xlMetaV1Object { | ||
|  | 	xlMeta := xlMetaV1Object{} | ||
|  | 	xlMeta.Version = xlMetaVersion101 | ||
|  | 	xlMeta.Format = xlMetaFormat | ||
|  | 	xlMeta.Minio.Release = "test" | ||
|  | 	xlMeta.Erasure = ErasureInfo{ | ||
|  | 		Algorithm:    "klauspost/reedsolomon/vandermonde", | ||
|  | 		DataBlocks:   5, | ||
|  | 		ParityBlocks: 5, | ||
|  | 		BlockSize:    10485760, | ||
|  | 		Index:        10, | ||
|  | 		Distribution: []int{9, 10, 1, 2, 3, 4, 5, 6, 7, 8}, | ||
|  | 	} | ||
|  | 	xlMeta.Stat = StatInfo{ | ||
|  | 		Size:    int64(20), | ||
|  | 		ModTime: UTCNow(), | ||
|  | 	} | ||
|  | 	// Set meta data.
 | ||
|  | 	xlMeta.Meta = make(map[string]string) | ||
|  | 	xlMeta.Meta["testKey1"] = "val1" | ||
|  | 	xlMeta.Meta["testKey2"] = "val2" | ||
|  | 	return xlMeta | ||
|  | } | ||
|  | 
 | ||
|  | func (m *xlMetaV1Object) AddTestObjectCheckSum(partNumber int, algorithm BitrotAlgorithm, hash string) { | ||
|  | 	checksum, err := hex.DecodeString(hash) | ||
|  | 	if err != nil { | ||
|  | 		panic(err) | ||
|  | 	} | ||
|  | 	m.Erasure.Checksums[partNumber-1] = ChecksumInfo{partNumber, algorithm, checksum} | ||
|  | } | ||
|  | 
 | ||
|  | // AddTestObjectPart - add a new object part in order.
 | ||
|  | func (m *xlMetaV1Object) AddTestObjectPart(partNumber int, partSize int64) { | ||
|  | 	partInfo := ObjectPartInfo{ | ||
|  | 		Number: partNumber, | ||
|  | 		Size:   partSize, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Proceed to include new part info.
 | ||
|  | 	m.Parts[partNumber-1] = partInfo | ||
|  | } | ||
|  | 
 | ||
|  | // Constructs xlMetaV1Object{} for given number of parts and converts it into bytes.
 | ||
|  | func getXLMetaBytes(totalParts int) []byte { | ||
|  | 	xlSampleMeta := getSampleXLMeta(totalParts) | ||
|  | 	xlMetaBytes, err := json.Marshal(xlSampleMeta) | ||
|  | 	if err != nil { | ||
|  | 		panic(err) | ||
|  | 	} | ||
|  | 	return xlMetaBytes | ||
|  | } | ||
|  | 
 | ||
|  | // Returns sample xlMetaV1Object{} for number of parts.
 | ||
|  | func getSampleXLMeta(totalParts int) xlMetaV1Object { | ||
|  | 	xlMeta := newTestXLMetaV1() | ||
|  | 	// Number of checksum info == total parts.
 | ||
|  | 	xlMeta.Erasure.Checksums = make([]ChecksumInfo, totalParts) | ||
|  | 	// total number of parts.
 | ||
|  | 	xlMeta.Parts = make([]ObjectPartInfo, totalParts) | ||
|  | 	for i := 0; i < totalParts; i++ { | ||
|  | 		// hard coding hash and algo value for the checksum, Since we are benchmarking the parsing of xl.meta the magnitude doesn't affect the test,
 | ||
|  | 		// The magnitude doesn't make a difference, only the size does.
 | ||
|  | 		xlMeta.AddTestObjectCheckSum(i+1, BLAKE2b512, "a23f5eff248c4372badd9f3b2455a285cd4ca86c3d9a570b091d3fc5cd7ca6d9484bbea3f8c5d8d4f84daae96874419eda578fd736455334afbac2c924b3915a") | ||
|  | 		xlMeta.AddTestObjectPart(i+1, 67108864) | ||
|  | 	} | ||
|  | 	return xlMeta | ||
|  | } | ||
|  | 
 | ||
|  | // Compare the unmarshaled XLMetaV1 with the one obtained from jsoniter parsing.
 | ||
|  | func compareXLMetaV1(t *testing.T, unMarshalXLMeta, jsoniterXLMeta xlMetaV1Object) { | ||
|  | 	// Start comparing the fields of xlMetaV1Object obtained from jsoniter parsing with one parsed using json unmarshaling.
 | ||
|  | 	if unMarshalXLMeta.Version != jsoniterXLMeta.Version { | ||
|  | 		t.Errorf("Expected the Version to be \"%s\", but got \"%s\".", unMarshalXLMeta.Version, jsoniterXLMeta.Version) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Format != jsoniterXLMeta.Format { | ||
|  | 		t.Errorf("Expected the format to be \"%s\", but got \"%s\".", unMarshalXLMeta.Format, jsoniterXLMeta.Format) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Stat.Size != jsoniterXLMeta.Stat.Size { | ||
|  | 		t.Errorf("Expected the stat size to be %v, but got %v.", unMarshalXLMeta.Stat.Size, jsoniterXLMeta.Stat.Size) | ||
|  | 	} | ||
|  | 	if !unMarshalXLMeta.Stat.ModTime.Equal(jsoniterXLMeta.Stat.ModTime) { | ||
|  | 		t.Errorf("Expected the modTime to be \"%v\", but got \"%v\".", unMarshalXLMeta.Stat.ModTime, jsoniterXLMeta.Stat.ModTime) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Erasure.Algorithm != jsoniterXLMeta.Erasure.Algorithm { | ||
|  | 		t.Errorf("Expected the erasure algorithm to be \"%v\", but got \"%v\".", unMarshalXLMeta.Erasure.Algorithm, jsoniterXLMeta.Erasure.Algorithm) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Erasure.DataBlocks != jsoniterXLMeta.Erasure.DataBlocks { | ||
|  | 		t.Errorf("Expected the erasure data blocks to be %v, but got %v.", unMarshalXLMeta.Erasure.DataBlocks, jsoniterXLMeta.Erasure.DataBlocks) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Erasure.ParityBlocks != jsoniterXLMeta.Erasure.ParityBlocks { | ||
|  | 		t.Errorf("Expected the erasure parity blocks to be %v, but got %v.", unMarshalXLMeta.Erasure.ParityBlocks, jsoniterXLMeta.Erasure.ParityBlocks) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Erasure.BlockSize != jsoniterXLMeta.Erasure.BlockSize { | ||
|  | 		t.Errorf("Expected the erasure block size to be %v, but got %v.", unMarshalXLMeta.Erasure.BlockSize, jsoniterXLMeta.Erasure.BlockSize) | ||
|  | 	} | ||
|  | 	if unMarshalXLMeta.Erasure.Index != jsoniterXLMeta.Erasure.Index { | ||
|  | 		t.Errorf("Expected the erasure index to be %v, but got %v.", unMarshalXLMeta.Erasure.Index, jsoniterXLMeta.Erasure.Index) | ||
|  | 	} | ||
|  | 	if len(unMarshalXLMeta.Erasure.Distribution) != len(jsoniterXLMeta.Erasure.Distribution) { | ||
|  | 		t.Errorf("Expected the size of Erasure Distribution to be %d, but got %d.", len(unMarshalXLMeta.Erasure.Distribution), len(jsoniterXLMeta.Erasure.Distribution)) | ||
|  | 	} else { | ||
|  | 		for i := 0; i < len(unMarshalXLMeta.Erasure.Distribution); i++ { | ||
|  | 			if unMarshalXLMeta.Erasure.Distribution[i] != jsoniterXLMeta.Erasure.Distribution[i] { | ||
|  | 				t.Errorf("Expected the Erasure Distribution to be %d, got %d.", unMarshalXLMeta.Erasure.Distribution[i], jsoniterXLMeta.Erasure.Distribution[i]) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if len(unMarshalXLMeta.Erasure.Checksums) != len(jsoniterXLMeta.Erasure.Checksums) { | ||
|  | 		t.Errorf("Expected the size of Erasure Checksums to be %d, but got %d.", len(unMarshalXLMeta.Erasure.Checksums), len(jsoniterXLMeta.Erasure.Checksums)) | ||
|  | 	} else { | ||
|  | 		for i := 0; i < len(unMarshalXLMeta.Erasure.Checksums); i++ { | ||
|  | 			if unMarshalXLMeta.Erasure.Checksums[i].PartNumber != jsoniterXLMeta.Erasure.Checksums[i].PartNumber { | ||
|  | 				t.Errorf("Expected the Erasure Checksum PartNumber to be \"%d\", got \"%d\".", unMarshalXLMeta.Erasure.Checksums[i].PartNumber, jsoniterXLMeta.Erasure.Checksums[i].PartNumber) | ||
|  | 			} | ||
|  | 			if unMarshalXLMeta.Erasure.Checksums[i].Algorithm != jsoniterXLMeta.Erasure.Checksums[i].Algorithm { | ||
|  | 				t.Errorf("Expected the Erasure Checksum Algorithm to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Algorithm, jsoniterXLMeta.Erasure.Checksums[i].Algorithm) | ||
|  | 			} | ||
|  | 			if !bytes.Equal(unMarshalXLMeta.Erasure.Checksums[i].Hash, jsoniterXLMeta.Erasure.Checksums[i].Hash) { | ||
|  | 				t.Errorf("Expected the Erasure Checksum Hash to be \"%s\", got \"%s\".", unMarshalXLMeta.Erasure.Checksums[i].Hash, jsoniterXLMeta.Erasure.Checksums[i].Hash) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if unMarshalXLMeta.Minio.Release != jsoniterXLMeta.Minio.Release { | ||
|  | 		t.Errorf("Expected the Release string to be \"%s\", but got \"%s\".", unMarshalXLMeta.Minio.Release, jsoniterXLMeta.Minio.Release) | ||
|  | 	} | ||
|  | 	if len(unMarshalXLMeta.Parts) != len(jsoniterXLMeta.Parts) { | ||
|  | 		t.Errorf("Expected info of  %d parts to be present, but got %d instead.", len(unMarshalXLMeta.Parts), len(jsoniterXLMeta.Parts)) | ||
|  | 	} else { | ||
|  | 		for i := 0; i < len(unMarshalXLMeta.Parts); i++ { | ||
|  | 			if unMarshalXLMeta.Parts[i].Number != jsoniterXLMeta.Parts[i].Number { | ||
|  | 				t.Errorf("Expected the number of part %d to be \"%d\", got \"%d\".", i+1, unMarshalXLMeta.Parts[i].Number, jsoniterXLMeta.Parts[i].Number) | ||
|  | 			} | ||
|  | 			if unMarshalXLMeta.Parts[i].Size != jsoniterXLMeta.Parts[i].Size { | ||
|  | 				t.Errorf("Expected the size of part %d to be %v, got %v.", i+1, unMarshalXLMeta.Parts[i].Size, jsoniterXLMeta.Parts[i].Size) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for key, val := range unMarshalXLMeta.Meta { | ||
|  | 		jsoniterVal, exists := jsoniterXLMeta.Meta[key] | ||
|  | 		if !exists { | ||
|  | 			t.Errorf("No meta data entry for Key \"%s\" exists.", key) | ||
|  | 		} | ||
|  | 		if val != jsoniterVal { | ||
|  | 			t.Errorf("Expected the value for Meta data key \"%s\" to be \"%s\", but got \"%s\".", key, val, jsoniterVal) | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Tests the correctness of constructing XLMetaV1 using jsoniter lib.
 | ||
|  | // The result will be compared with the result obtained from json.unMarshal of the byte data.
 | ||
|  | func TestGetXLMetaV1Jsoniter1(t *testing.T) { | ||
|  | 	xlMetaJSON := getXLMetaBytes(1) | ||
|  | 
 | ||
|  | 	var unMarshalXLMeta xlMetaV1Object | ||
|  | 	if err := json.Unmarshal(xlMetaJSON, &unMarshalXLMeta); err != nil { | ||
|  | 		t.Errorf("Unmarshalling failed: %v", err) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var jsoniterXLMeta xlMetaV1Object | ||
|  | 	var json = jsoniter.ConfigCompatibleWithStandardLibrary | ||
|  | 	if err := json.Unmarshal(xlMetaJSON, &jsoniterXLMeta); err != nil { | ||
|  | 		t.Errorf("jsoniter parsing of XLMeta failed: %v", err) | ||
|  | 	} | ||
|  | 	compareXLMetaV1(t, unMarshalXLMeta, jsoniterXLMeta) | ||
|  | } | ||
|  | 
 | ||
|  | // Tests the correctness of constructing XLMetaV1 using jsoniter lib for XLMetaV1 of size 10 parts.
 | ||
|  | // The result will be compared with the result obtained from json.unMarshal of the byte data.
 | ||
|  | func TestGetXLMetaV1Jsoniter10(t *testing.T) { | ||
|  | 
 | ||
|  | 	xlMetaJSON := getXLMetaBytes(10) | ||
|  | 
 | ||
|  | 	var unMarshalXLMeta xlMetaV1Object | ||
|  | 	if err := json.Unmarshal(xlMetaJSON, &unMarshalXLMeta); err != nil { | ||
|  | 		t.Errorf("Unmarshalling failed: %v", err) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var jsoniterXLMeta xlMetaV1Object | ||
|  | 	var json = jsoniter.ConfigCompatibleWithStandardLibrary | ||
|  | 	if err := json.Unmarshal(xlMetaJSON, &jsoniterXLMeta); err != nil { | ||
|  | 		t.Errorf("jsoniter parsing of XLMeta failed: %v", err) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	compareXLMetaV1(t, unMarshalXLMeta, jsoniterXLMeta) | ||
|  | } | ||
|  | 
 | ||
|  | // Test the predicted part size from the part index
 | ||
|  | func TestGetPartSizeFromIdx(t *testing.T) { | ||
|  | 	// Create test cases
 | ||
|  | 	testCases := []struct { | ||
|  | 		totalSize    int64 | ||
|  | 		partSize     int64 | ||
|  | 		partIndex    int | ||
|  | 		expectedSize int64 | ||
|  | 	}{ | ||
|  | 		// Total size is zero
 | ||
|  | 		{0, 10, 1, 0}, | ||
|  | 		// part size 2MiB, total size 4MiB
 | ||
|  | 		{4 * humanize.MiByte, 2 * humanize.MiByte, 1, 2 * humanize.MiByte}, | ||
|  | 		{4 * humanize.MiByte, 2 * humanize.MiByte, 2, 2 * humanize.MiByte}, | ||
|  | 		{4 * humanize.MiByte, 2 * humanize.MiByte, 3, 0}, | ||
|  | 		// part size 2MiB, total size 5MiB
 | ||
|  | 		{5 * humanize.MiByte, 2 * humanize.MiByte, 1, 2 * humanize.MiByte}, | ||
|  | 		{5 * humanize.MiByte, 2 * humanize.MiByte, 2, 2 * humanize.MiByte}, | ||
|  | 		{5 * humanize.MiByte, 2 * humanize.MiByte, 3, 1 * humanize.MiByte}, | ||
|  | 		{5 * humanize.MiByte, 2 * humanize.MiByte, 4, 0}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		s, err := calculatePartSizeFromIdx(GlobalContext, testCase.totalSize, testCase.partSize, testCase.partIndex) | ||
|  | 		if err != nil { | ||
|  | 			t.Errorf("Test %d: Expected to pass but failed. %s", i+1, err) | ||
|  | 		} | ||
|  | 		if err == nil && s != testCase.expectedSize { | ||
|  | 			t.Errorf("Test %d: The calculated part size is incorrect: expected = %d, found = %d\n", i+1, testCase.expectedSize, s) | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	testCasesFailure := []struct { | ||
|  | 		totalSize int64 | ||
|  | 		partSize  int64 | ||
|  | 		partIndex int | ||
|  | 		err       error | ||
|  | 	}{ | ||
|  | 		// partSize is 0, returns error.
 | ||
|  | 		{10, 0, 1, errPartSizeZero}, | ||
|  | 		// partIndex is 0, returns error.
 | ||
|  | 		{10, 1, 0, errPartSizeIndex}, | ||
|  | 		// Total size is -1, returns error.
 | ||
|  | 		{-2, 10, 1, errInvalidArgument}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCaseFailure := range testCasesFailure { | ||
|  | 		_, err := calculatePartSizeFromIdx(GlobalContext, testCaseFailure.totalSize, testCaseFailure.partSize, testCaseFailure.partIndex) | ||
|  | 		if err == nil { | ||
|  | 			t.Errorf("Test %d: Expected to failed but passed. %s", i+1, err) | ||
|  | 		} | ||
|  | 		if err != nil && err != testCaseFailure.err { | ||
|  | 			t.Errorf("Test %d: Expected err %s, but got %s", i+1, testCaseFailure.err, err) | ||
|  | 		} | ||
|  | 	} | ||
|  | } |