github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/erasure-encode_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 "testing" 26 27 "github.com/dustin/go-humanize" 28 ) 29 30 type badDisk struct{ StorageAPI } 31 32 func (a badDisk) String() string { 33 return "bad-disk" 34 } 35 36 func (a badDisk) AppendFile(ctx context.Context, volume string, path string, buf []byte) error { 37 return errFaultyDisk 38 } 39 40 func (a badDisk) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) { 41 return nil, errFaultyDisk 42 } 43 44 func (a badDisk) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) error { 45 return errFaultyDisk 46 } 47 48 func (badDisk) Hostname() string { 49 return "" 50 } 51 52 const oneMiByte = 1 * humanize.MiByte 53 54 var erasureEncodeTests = []struct { 55 dataBlocks int 56 onDisks, offDisks int 57 blocksize, data int64 58 offset int 59 algorithm BitrotAlgorithm 60 shouldFail, shouldFailQuorum bool 61 }{ 62 {dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 0 63 {dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 1, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false}, // 1 64 {dataBlocks: 4, onDisks: 8, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 2 65 {dataBlocks: 5, onDisks: 10, offDisks: 3, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 3 66 {dataBlocks: 6, onDisks: 12, offDisks: 4, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 4 67 {dataBlocks: 7, onDisks: 14, offDisks: 5, blocksize: int64(blockSizeV2), data: 0, offset: 0, shouldFail: false, algorithm: SHA256, shouldFailQuorum: false}, // 5 68 {dataBlocks: 8, onDisks: 16, offDisks: 7, blocksize: int64(blockSizeV2), data: 0, offset: 0, shouldFail: false, algorithm: DefaultBitrotAlgorithm, shouldFailQuorum: false}, // 6 69 {dataBlocks: 2, onDisks: 4, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: true}, // 7 70 {dataBlocks: 4, onDisks: 8, offDisks: 4, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: SHA256, shouldFail: false, shouldFailQuorum: true}, // 8 71 {dataBlocks: 7, onDisks: 14, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 9 72 {dataBlocks: 8, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 10 73 {dataBlocks: 5, onDisks: 10, offDisks: 3, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 11 74 {dataBlocks: 3, onDisks: 6, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte / 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 12 75 {dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(oneMiByte / 2), data: oneMiByte, offset: oneMiByte/2 + 1, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 13 76 {dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(oneMiByte - 1), data: oneMiByte, offset: oneMiByte - 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false}, // 14 77 {dataBlocks: 8, onDisks: 12, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 15 78 {dataBlocks: 8, onDisks: 10, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 16 79 {dataBlocks: 10, onDisks: 14, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 17, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 17 80 {dataBlocks: 2, onDisks: 6, offDisks: 2, blocksize: int64(oneMiByte), data: oneMiByte, offset: oneMiByte / 2, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 18 81 {dataBlocks: 10, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true}, // 19 82 } 83 84 func TestErasureEncode(t *testing.T) { 85 for i, test := range erasureEncodeTests { 86 setup, err := newErasureTestSetup(t, test.dataBlocks, test.onDisks-test.dataBlocks, test.blocksize) 87 if err != nil { 88 t.Fatalf("Test %d: failed to create test setup: %v", i, err) 89 } 90 disks := setup.disks 91 erasure, err := NewErasure(context.Background(), test.dataBlocks, test.onDisks-test.dataBlocks, test.blocksize) 92 if err != nil { 93 t.Fatalf("Test %d: failed to create ErasureStorage: %v", i, err) 94 } 95 buffer := make([]byte, test.blocksize, 2*test.blocksize) 96 97 data := make([]byte, test.data) 98 if _, err = io.ReadFull(rand.Reader, data); err != nil { 99 t.Fatalf("Test %d: failed to generate random test data: %v", i, err) 100 } 101 writers := make([]io.Writer, len(disks)) 102 for i, disk := range disks { 103 if disk == OfflineDisk { 104 continue 105 } 106 writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize()) 107 } 108 n, err := erasure.Encode(context.Background(), bytes.NewReader(data[test.offset:]), writers, buffer, erasure.dataBlocks+1) 109 closeBitrotWriters(writers) 110 if err != nil && !test.shouldFail { 111 t.Errorf("Test %d: should pass but failed with: %v", i, err) 112 } 113 if err == nil && test.shouldFail { 114 t.Errorf("Test %d: should fail but it passed", i) 115 } 116 for i, w := range writers { 117 if w == nil { 118 disks[i] = OfflineDisk 119 } 120 } 121 if err == nil { 122 if length := int64(len(data[test.offset:])); n != length { 123 t.Errorf("Test %d: invalid number of bytes written: got: #%d want #%d", i, n, length) 124 } 125 writers := make([]io.Writer, len(disks)) 126 for i, disk := range disks { 127 if disk == nil { 128 continue 129 } 130 writers[i] = newBitrotWriter(disk, "", "testbucket", "object2", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize()) 131 } 132 for j := range disks[:test.offDisks] { 133 switch w := writers[j].(type) { 134 case *wholeBitrotWriter: 135 w.disk = badDisk{nil} 136 case *streamingBitrotWriter: 137 w.closeWithErr(errFaultyDisk) 138 } 139 } 140 if test.offDisks > 0 { 141 writers[0] = nil 142 } 143 n, err = erasure.Encode(context.Background(), bytes.NewReader(data[test.offset:]), writers, buffer, erasure.dataBlocks+1) 144 closeBitrotWriters(writers) 145 if err != nil && !test.shouldFailQuorum { 146 t.Errorf("Test %d: should pass but failed with: %v", i, err) 147 } 148 if err == nil && test.shouldFailQuorum { 149 t.Errorf("Test %d: should fail but it passed", i) 150 } 151 if err == nil { 152 if length := int64(len(data[test.offset:])); n != length { 153 t.Errorf("Test %d: invalid number of bytes written: got: #%d want #%d", i, n, length) 154 } 155 } 156 } 157 } 158 } 159 160 // Benchmarks 161 162 func benchmarkErasureEncode(data, parity, dataDown, parityDown int, size int64, b *testing.B) { 163 setup, err := newErasureTestSetup(b, data, parity, blockSizeV2) 164 if err != nil { 165 b.Fatalf("failed to create test setup: %v", err) 166 } 167 erasure, err := NewErasure(context.Background(), data, parity, blockSizeV2) 168 if err != nil { 169 b.Fatalf("failed to create ErasureStorage: %v", err) 170 } 171 disks := setup.disks 172 buffer := make([]byte, blockSizeV2, 2*blockSizeV2) 173 content := make([]byte, size) 174 175 for i := 0; i < dataDown; i++ { 176 disks[i] = OfflineDisk 177 } 178 for i := data; i < data+parityDown; i++ { 179 disks[i] = OfflineDisk 180 } 181 182 b.ResetTimer() 183 b.SetBytes(size) 184 b.ReportAllocs() 185 for i := 0; i < b.N; i++ { 186 writers := make([]io.Writer, len(disks)) 187 for i, disk := range disks { 188 if disk == OfflineDisk { 189 continue 190 } 191 disk.Delete(context.Background(), "testbucket", "object", DeleteOptions{ 192 Recursive: false, 193 Immediate: false, 194 }) 195 writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize()) 196 } 197 _, err := erasure.Encode(context.Background(), bytes.NewReader(content), writers, buffer, erasure.dataBlocks+1) 198 closeBitrotWriters(writers) 199 if err != nil { 200 panic(err) 201 } 202 } 203 } 204 205 func BenchmarkErasureEncodeQuick(b *testing.B) { 206 const size = 12 * 1024 * 1024 207 b.Run(" 00|00 ", func(b *testing.B) { benchmarkErasureEncode(2, 2, 0, 0, size, b) }) 208 b.Run(" 00|X0 ", func(b *testing.B) { benchmarkErasureEncode(2, 2, 0, 1, size, b) }) 209 b.Run(" X0|00 ", func(b *testing.B) { benchmarkErasureEncode(2, 2, 1, 0, size, b) }) 210 } 211 212 func BenchmarkErasureEncode_4_64KB(b *testing.B) { 213 const size = 64 * 1024 214 b.Run(" 00|00 ", func(b *testing.B) { benchmarkErasureEncode(2, 2, 0, 0, size, b) }) 215 b.Run(" 00|X0 ", func(b *testing.B) { benchmarkErasureEncode(2, 2, 0, 1, size, b) }) 216 b.Run(" X0|00 ", func(b *testing.B) { benchmarkErasureEncode(2, 2, 1, 0, size, b) }) 217 } 218 219 func BenchmarkErasureEncode_8_20MB(b *testing.B) { 220 const size = 20 * 1024 * 1024 221 b.Run(" 0000|0000 ", func(b *testing.B) { benchmarkErasureEncode(4, 4, 0, 0, size, b) }) 222 b.Run(" 0000|X000 ", func(b *testing.B) { benchmarkErasureEncode(4, 4, 0, 1, size, b) }) 223 b.Run(" X000|0000 ", func(b *testing.B) { benchmarkErasureEncode(4, 4, 1, 0, size, b) }) 224 b.Run(" 0000|XXX0 ", func(b *testing.B) { benchmarkErasureEncode(4, 4, 0, 3, size, b) }) 225 b.Run(" XXX0|0000 ", func(b *testing.B) { benchmarkErasureEncode(4, 4, 3, 0, size, b) }) 226 } 227 228 func BenchmarkErasureEncode_12_30MB(b *testing.B) { 229 const size = 30 * 1024 * 1024 230 b.Run(" 000000|000000 ", func(b *testing.B) { benchmarkErasureEncode(6, 6, 0, 0, size, b) }) 231 b.Run(" 000000|X00000 ", func(b *testing.B) { benchmarkErasureEncode(6, 6, 0, 1, size, b) }) 232 b.Run(" X00000|000000 ", func(b *testing.B) { benchmarkErasureEncode(6, 6, 1, 0, size, b) }) 233 b.Run(" 000000|XXXXX0 ", func(b *testing.B) { benchmarkErasureEncode(6, 6, 0, 5, size, b) }) 234 b.Run(" XXXXX0|000000 ", func(b *testing.B) { benchmarkErasureEncode(6, 6, 5, 0, size, b) }) 235 } 236 237 func BenchmarkErasureEncode_16_40MB(b *testing.B) { 238 const size = 40 * 1024 * 1024 239 b.Run(" 00000000|00000000 ", func(b *testing.B) { benchmarkErasureEncode(8, 8, 0, 0, size, b) }) 240 b.Run(" 00000000|X0000000 ", func(b *testing.B) { benchmarkErasureEncode(8, 8, 0, 1, size, b) }) 241 b.Run(" X0000000|00000000 ", func(b *testing.B) { benchmarkErasureEncode(8, 8, 1, 0, size, b) }) 242 b.Run(" 00000000|XXXXXXX0 ", func(b *testing.B) { benchmarkErasureEncode(8, 8, 0, 7, size, b) }) 243 b.Run(" XXXXXXX0|00000000 ", func(b *testing.B) { benchmarkErasureEncode(8, 8, 7, 0, size, b) }) 244 }