github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/erasure-heal_test.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bytes" 22 "context" 23 "crypto/rand" 24 "io" 25 "os" 26 "testing" 27 ) 28 29 var erasureHealTests = []struct { 30 dataBlocks, disks int 31 32 // number of offline disks is also number of staleDisks for 33 // erasure reconstruction in this test 34 offDisks int 35 36 // bad disks are online disks which return errors 37 badDisks, badStaleDisks int 38 39 blocksize, size int64 40 algorithm BitrotAlgorithm 41 shouldFail bool 42 }{ 43 {dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: SHA256, shouldFail: false}, // 0 44 {dataBlocks: 3, disks: 6, offDisks: 2, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 1 45 {dataBlocks: 4, disks: 8, offDisks: 2, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 2 46 {dataBlocks: 5, disks: 10, offDisks: 3, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 3 47 {dataBlocks: 6, disks: 12, offDisks: 2, badDisks: 3, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: SHA256, shouldFail: false}, // 4 48 {dataBlocks: 7, disks: 14, offDisks: 4, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 5 49 {dataBlocks: 8, disks: 16, offDisks: 6, badDisks: 1, badStaleDisks: 1, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 6 50 {dataBlocks: 7, disks: 14, offDisks: 2, badDisks: 3, badStaleDisks: 0, blocksize: int64(oneMiByte / 2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 7 51 {dataBlocks: 6, disks: 12, offDisks: 1, badDisks: 0, badStaleDisks: 1, blocksize: int64(oneMiByte - 1), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 8 52 {dataBlocks: 5, disks: 10, offDisks: 3, badDisks: 0, badStaleDisks: 3, blocksize: int64(oneMiByte / 2), size: oneMiByte, algorithm: SHA256, shouldFail: true}, // 9 53 {dataBlocks: 4, disks: 8, offDisks: 1, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 10 54 {dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 1, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 11 55 {dataBlocks: 6, disks: 12, offDisks: 8, badDisks: 3, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 12 56 {dataBlocks: 7, disks: 14, offDisks: 3, badDisks: 4, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 13 57 {dataBlocks: 7, disks: 14, offDisks: 6, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 14 58 {dataBlocks: 8, disks: 16, offDisks: 4, badDisks: 5, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: true}, // 15 59 {dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 16 60 {dataBlocks: 12, disks: 16, offDisks: 2, badDisks: 1, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false}, // 17 61 {dataBlocks: 6, disks: 8, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte, algorithm: BLAKE2b512, shouldFail: false}, // 18 62 {dataBlocks: 2, disks: 4, offDisks: 1, badDisks: 0, badStaleDisks: 0, blocksize: int64(blockSizeV2), size: oneMiByte * 64, algorithm: SHA256, shouldFail: false}, // 19 63 } 64 65 func TestErasureHeal(t *testing.T) { 66 for i, test := range erasureHealTests { 67 if test.offDisks < test.badStaleDisks { 68 // test case sanity check 69 t.Fatalf("Test %d: Bad test case - number of stale drives cannot be less than number of badstale drives", i) 70 } 71 72 // create some test data 73 setup, err := newErasureTestSetup(t, test.dataBlocks, test.disks-test.dataBlocks, test.blocksize) 74 if err != nil { 75 t.Fatalf("Test %d: failed to setup Erasure environment: %v", i, err) 76 } 77 disks := setup.disks 78 erasure, err := NewErasure(context.Background(), test.dataBlocks, test.disks-test.dataBlocks, test.blocksize) 79 if err != nil { 80 t.Fatalf("Test %d: failed to create ErasureStorage: %v", i, err) 81 } 82 data := make([]byte, test.size) 83 if _, err = io.ReadFull(rand.Reader, data); err != nil { 84 t.Fatalf("Test %d: failed to create random test data: %v", i, err) 85 } 86 buffer := make([]byte, test.blocksize, 2*test.blocksize) 87 writers := make([]io.Writer, len(disks)) 88 for i, disk := range disks { 89 writers[i] = newBitrotWriter(disk, "", "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize()) 90 } 91 _, err = erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1) 92 closeBitrotWriters(writers) 93 if err != nil { 94 t.Fatalf("Test %d: failed to create random test data: %v", i, err) 95 } 96 97 readers := make([]io.ReaderAt, len(disks)) 98 for i, disk := range disks { 99 shardFilesize := erasure.ShardFileSize(test.size) 100 readers[i] = newBitrotReader(disk, nil, "testbucket", "testobject", shardFilesize, test.algorithm, bitrotWriterSum(writers[i]), erasure.ShardSize()) 101 } 102 103 // setup stale disks for the test case 104 staleDisks := make([]StorageAPI, len(disks)) 105 copy(staleDisks, disks) 106 for j := 0; j < len(staleDisks); j++ { 107 if j < test.offDisks { 108 readers[j] = nil 109 } else { 110 staleDisks[j] = nil 111 } 112 } 113 for j := 0; j < test.badDisks; j++ { 114 switch r := readers[test.offDisks+j].(type) { 115 case *streamingBitrotReader: 116 r.disk = badDisk{nil} 117 case *wholeBitrotReader: 118 r.disk = badDisk{nil} 119 } 120 } 121 for j := 0; j < test.badStaleDisks; j++ { 122 staleDisks[j] = badDisk{nil} 123 } 124 125 staleWriters := make([]io.Writer, len(staleDisks)) 126 for i, disk := range staleDisks { 127 if disk == nil { 128 continue 129 } 130 os.Remove(pathJoin(disk.String(), "testbucket", "testobject")) 131 staleWriters[i] = newBitrotWriter(disk, "", "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize()) 132 } 133 134 // test case setup is complete - now call Heal() 135 err = erasure.Heal(context.Background(), staleWriters, readers, test.size, nil) 136 closeBitrotReaders(readers) 137 closeBitrotWriters(staleWriters) 138 if err != nil && !test.shouldFail { 139 t.Errorf("Test %d: should pass but it failed with: %v", i, err) 140 } 141 if err == nil && test.shouldFail { 142 t.Errorf("Test %d: should fail but it passed", i) 143 } 144 if err == nil { 145 // Verify that checksums of staleDisks 146 // match expected values 147 for i := range staleWriters { 148 if staleWriters[i] == nil { 149 continue 150 } 151 if !bytes.Equal(bitrotWriterSum(staleWriters[i]), bitrotWriterSum(writers[i])) { 152 t.Errorf("Test %d: heal returned different bitrot checksums", i) 153 } 154 } 155 } 156 } 157 }