mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-23 11:45:35 +00:00
fbd85d31b0
A decode ends by deleting the shards it read, and the only thing standing between that and a bad reconstruction is verifyDecodedVolumeBeforeDelete, which asks whether .dat and .idx are non-empty. A .dat truncated to a single byte passes, and the shards -- the only other copy of everything past the cut -- are deleted on the strength of it. The server already knows the answer it never checks: FindDatFileSize returns the extent the EC index references, and WriteDatFile rebuilds to it. Compare the two once the file is written and fail the decode instead of reporting a short volume as a good one. Longer than the extent still verifies -- padding is not missing data -- so only a genuinely short rebuild is rejected. Needle counts cannot answer this: .idx is written from .ecx, so the count matches by construction and a truncated .dat still reports every needle.
91 lines
2.4 KiB
Go
91 lines
2.4 KiB
Go
package erasure_coding
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A decode hands the caller a volume and the caller then deletes the shards it
|
|
// came from. VerifyDecodedDatFile is what stands between a reconstruction that
|
|
// came up short -- a truncated shard, a partial write, a full disk -- and the
|
|
// deletion of the only other copy of the needles past the cut.
|
|
func TestVerifyDecodedDatFile(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
written int64
|
|
referenced int64
|
|
wantErr bool
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "exactly the referenced extent is complete",
|
|
written: 4096,
|
|
referenced: 4096,
|
|
},
|
|
{
|
|
name: "longer than referenced is fine: padding is not missing data",
|
|
written: 8192,
|
|
referenced: 4096,
|
|
},
|
|
{
|
|
name: "one byte short still loses the last needle",
|
|
written: 4095,
|
|
referenced: 4096,
|
|
wantErr: true,
|
|
errContains: "short of the 4096",
|
|
},
|
|
{
|
|
name: "an empty file is short of everything",
|
|
written: 0,
|
|
referenced: 4096,
|
|
wantErr: true,
|
|
errContains: "is 0 bytes",
|
|
},
|
|
{
|
|
name: "an index referencing nothing accepts an empty file",
|
|
written: 0,
|
|
referenced: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
base := filepath.Join(t.TempDir(), "5")
|
|
if err := os.WriteFile(base+".dat", make([]byte, tt.written), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err := VerifyDecodedDatFile(base, tt.referenced)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("wrote %d bytes against a %d-byte extent, want an error", tt.written, tt.referenced)
|
|
}
|
|
if !strings.Contains(err.Error(), tt.errContains) {
|
|
t.Errorf("error %q does not mention %q", err, tt.errContains)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("wrote %d bytes against a %d-byte extent: %v", tt.written, tt.referenced, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A missing .dat is not a short one, and the two want different answers: the
|
|
// caller cannot tell whether the decode wrote nothing or wrote elsewhere, so
|
|
// the error has to name the file rather than a byte count.
|
|
func TestVerifyDecodedDatFileMissing(t *testing.T) {
|
|
base := filepath.Join(t.TempDir(), "7")
|
|
|
|
err := VerifyDecodedDatFile(base, 4096)
|
|
if err == nil {
|
|
t.Fatal("a missing .dat must not verify")
|
|
}
|
|
if !strings.Contains(err.Error(), "stat decoded") {
|
|
t.Errorf("error %q does not say the file could not be read", err)
|
|
}
|
|
}
|