From 9add048f3c011e54f50bb1501479697394cdae0b Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sun, 26 Jun 2016 03:32:49 -0700 Subject: [PATCH] erasure: Add erasure encode/decode unit tests. (#1911) Fixes #1819 --- erasure-readfile.go | 19 +---- erasure.go | 17 ---- erasure_test.go | 184 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 32 deletions(-) delete mode 100644 erasure.go create mode 100644 erasure_test.go diff --git a/erasure-readfile.go b/erasure-readfile.go index e3ec5cee1..c3578b068 100644 --- a/erasure-readfile.go +++ b/erasure-readfile.go @@ -339,21 +339,10 @@ func metaPartBlockChecksums(disks []StorageAPI, eInfos []erasureInfo, partName s return blockCheckSums } -// Takes block index and block distribution to get the disk index. -func toDiskIndex(blockIdx int, distribution []int) int { - // Find out the right disk index for the input block index. - for index, blockIndex := range distribution { - if blockIndex-1 == blockIdx { - return index - } - } - return -1 -} - // isValidBlock - calculates the checksum hash for the block and // validates if its correct returns true for valid cases, false otherwise. func isValidBlock(disk StorageAPI, volume, path string, blockCheckSum checkSumInfo) (ok bool) { - ok = false + // Disk is not available, not a valid block. if disk == nil { return false } @@ -361,10 +350,10 @@ func isValidBlock(disk StorageAPI, volume, path string, blockCheckSum checkSumIn hashWriter := newHash(blockCheckSum.Algorithm) hashBytes, err := hashSum(disk, volume, path, hashWriter) if err != nil { - return ok + errorIf(err, "Unable to calculate checksum %s/%s", volume, path) + return false } - ok = hex.EncodeToString(hashBytes) == blockCheckSum.Hash - return ok + return hex.EncodeToString(hashBytes) == blockCheckSum.Hash } // decodeData - decode encoded blocks. diff --git a/erasure.go b/erasure.go deleted file mode 100644 index 4e7aad742..000000000 --- a/erasure.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2016 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 main diff --git a/erasure_test.go b/erasure_test.go new file mode 100644 index 000000000..7ea7bae8a --- /dev/null +++ b/erasure_test.go @@ -0,0 +1,184 @@ +/* + * Minio Cloud Storage, (C) 2016 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 main + +import ( + "bytes" + "testing" +) + +// mustEncodeData - encodes data slice and provides encoded 2 dimensional slice. +func mustEncodeData(data []byte, dataBlocks, parityBlocks int) [][]byte { + encodedData, err := encodeData(data, dataBlocks, parityBlocks) + if err != nil { + // Upon failure panic this function. + panic(err) + } + return encodedData +} + +// Generates good encoded data with one parity block and data block missing. +func getGoodEncodedData(data []byte, dataBlocks, parityBlocks int) [][]byte { + encodedData := mustEncodeData(data, dataBlocks, parityBlocks) + encodedData[7] = nil + encodedData[1] = nil + return encodedData +} + +// Generates bad encoded data with one parity block and data block with garbage data. +func getBadEncodedData(data []byte, dataBlocks, parityBlocks int) [][]byte { + encodedData := mustEncodeData(data, dataBlocks, parityBlocks) + encodedData[7] = []byte("garbage") + encodedData[1] = []byte("garbage") + return encodedData +} + +// Generates encoded data with all data blocks missing. +func getMissingData(data []byte, dataBlocks, parityBlocks int) [][]byte { + encodedData := mustEncodeData(data, dataBlocks, parityBlocks) + for i := 0; i < dataBlocks+1; i++ { + encodedData[i] = nil + } + return encodedData +} + +// Generates encoded data with less number of blocks than expected data blocks. +func getInsufficientData(data []byte, dataBlocks, parityBlocks int) [][]byte { + encodedData := mustEncodeData(data, dataBlocks, parityBlocks) + // Return half of the data. + return encodedData[:dataBlocks/2] +} + +// Represents erasure encoding matrix dataBlocks and paritBlocks. +type encodingMatrix struct { + dataBlocks int + parityBlocks int +} + +// List of encoding matrices the tests will run on. +var encodingMatrices = []encodingMatrix{ + {4, 4}, // 4 data, 4 parity blocks. + {5, 5}, // 5 data, 5 parity blocks. + {6, 6}, // 6 data, 6 parity blocks. + {7, 7}, // 7 data, 7 parity blocks. + {8, 8}, // 8 data, 8 parity blocks. +} + +// TestErasureEncode checks for encoding for different data sets. +func TestErasureEncode(t *testing.T) { + // Collection of cases for encode cases. + testEncodeCases := []struct { + inputData []byte + shouldPass bool + }{ + // Regular data encoded. + { + inputData: []byte("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."), + shouldPass: true, + }, + // Empty data errors out. + { + inputData: []byte(""), + shouldPass: false, + }, + // Single byte encoded. + { + inputData: []byte("1"), + shouldPass: true, + }, + } + + // Test encode cases. + for i, testCase := range testEncodeCases { + for _, encodingMatrix := range encodingMatrices { + _, err := encodeData(testCase.inputData, encodingMatrix.dataBlocks, encodingMatrix.parityBlocks) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass by failed instead with %s, for matrix %v", i+1, err, encodingMatrix) + } + } + } +} + +// Tests erasure decoding functionality for various types of inputs. +func TestErasureDecode(t *testing.T) { + data := []byte("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.") + + // List of decoding cases + // - validates bad encoded data. + // - validates good encoded data. + // - validates insufficient data. + testDecodeCases := []struct { + enFn func([]byte, int, int) [][]byte + shouldPass bool + }{ + // Generates bad encoded data. + { + enFn: getBadEncodedData, + shouldPass: false, + }, + // Generates good encoded data. + { + enFn: getGoodEncodedData, + shouldPass: true, + }, + // Generates missing data. + { + enFn: getMissingData, + shouldPass: false, + }, + // Generates short data. + { + enFn: getInsufficientData, + shouldPass: false, + }, + } + + // Validates all decode tests. + for i, testCase := range testDecodeCases { + for _, encodingMatrix := range encodingMatrices { + + // Encoding matrix. + dataBlocks := encodingMatrix.dataBlocks + parityBlocks := encodingMatrix.parityBlocks + + // Data block size. + blockSize := len(data) + + // Generates encoded data based on type of testCase function. + encodedData := testCase.enFn(data, dataBlocks, parityBlocks) + + // Decodes the data. + err := decodeData(encodedData, dataBlocks, parityBlocks) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass by failed instead with %s", i+1, err) + } + + // Proceed to extract the data blocks. + decodedDataWriter := new(bytes.Buffer) + _, err = writeDataBlocks(decodedDataWriter, encodedData, dataBlocks, 0, int64(blockSize)) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass by failed instead with %s", i+1, err) + } + + // Validate if decoded data is what we expected. + if bytes.Equal(decodedDataWriter.Bytes(), data) != testCase.shouldPass { + err := errUnexpected + t.Errorf("Test %d: Expected to pass by failed instead %s", i+1, err) + } + } + } +}