mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-09 16:10:44 +02:00
The identity digests covered only chunks, while the view's extent path would serve from inline Content when present - a gRPC update could set Content with the chunks and size unchanged and segments were served from bytes the layout never described. Format entries are never written with inline content, so treat it as disqualifying: views answer stale, repack rejects it up front, the extent path no longer reads it, and the source identity digests it so it cannot appear mid-repack unnoticed.
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"bytes"
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
)
|
|
|
|
func TestRoundUpToVolumeTTL(t *testing.T) {
|
|
tests := []struct {
|
|
seconds int64
|
|
want int32
|
|
}{
|
|
{1, 60},
|
|
{59, 60},
|
|
{60, 60},
|
|
{61, 120},
|
|
{3599, 3600},
|
|
{3600, 3600},
|
|
{3601, 3660},
|
|
{255 * 60, 255 * 60},
|
|
{255*60 + 1, 5 * 3600}, // minutes overflow 255, ceil to hours
|
|
{20_000_000, 232 * 24 * 3600}, // ~231.5 days, ceil to days
|
|
{int64(math.MaxInt32), 0}, // beyond every unit's 255 cap: no TTL, never a shortened one
|
|
}
|
|
for _, test := range tests {
|
|
got := roundUpToVolumeTTL(test.seconds)
|
|
if got != test.want {
|
|
t.Fatalf("roundUpToVolumeTTL(%d) = %d, want %d", test.seconds, got, test.want)
|
|
}
|
|
if got == 0 {
|
|
continue // no volume TTL: chunks outlive the entry
|
|
}
|
|
if int64(got) < test.seconds {
|
|
t.Fatalf("roundUpToVolumeTTL(%d) = %d shortened the lifetime", test.seconds, got)
|
|
}
|
|
// the rounded value must survive the volume TTL string conversion intact
|
|
ttl, err := needle.ReadTTL(needle.SecondsToTTL(got))
|
|
if err != nil || int64(ttl.Minutes())*60 != int64(got) {
|
|
t.Fatalf("SecondsToTTL(%d) = %q does not round-trip (err %v)", got, needle.SecondsToTTL(got), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRepackSourceIdentity(t *testing.T) {
|
|
base := func() *filer.Entry {
|
|
return &filer.Entry{
|
|
FullPath: "/videos/movie.ts",
|
|
Attr: filer.Attr{
|
|
FileSize: 30, TtlSec: 600,
|
|
Crtime: time.Unix(1000, 0), Mtime: time.Unix(2000, 0),
|
|
},
|
|
Chunks: []*filer_pb.FileChunk{{FileId: "1,ab", Offset: 0, Size: 30}},
|
|
}
|
|
}
|
|
identity := repackSourceIdentity(base())
|
|
if !bytes.Equal(identity, repackSourceIdentity(base())) {
|
|
t.Fatalf("identity is not deterministic")
|
|
}
|
|
mutations := map[string]func(*filer.Entry){
|
|
"ttl cleared": func(e *filer.Entry) { e.TtlSec = 0 },
|
|
"size changed": func(e *filer.Entry) { e.FileSize = 31 },
|
|
"mtime moved": func(e *filer.Entry) { e.Mtime = time.Unix(3000, 0) },
|
|
"crtime moved": func(e *filer.Entry) { e.Crtime = time.Unix(1001, 0) },
|
|
"hard linked": func(e *filer.Entry) { e.HardLinkId = []byte{1} },
|
|
"went remote": func(e *filer.Entry) { e.Remote = &filer_pb.RemoteEntry{} },
|
|
"chunk moved": func(e *filer.Entry) { e.Chunks[0].Offset = 1 },
|
|
"content set": func(e *filer.Entry) { e.Content = []byte{0x47} },
|
|
}
|
|
for name, mutate := range mutations {
|
|
changed := base()
|
|
mutate(changed)
|
|
if bytes.Equal(identity, repackSourceIdentity(changed)) {
|
|
t.Fatalf("identity ignored: %s", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatChunkIdentity(t *testing.T) {
|
|
chunks := []*filer_pb.FileChunk{
|
|
{FileId: "1,ab", Offset: 0, Size: 10},
|
|
{FileId: "2,cd", Offset: 10, Size: 20},
|
|
}
|
|
identity := formatChunkIdentity(chunks)
|
|
if !bytes.Equal(identity, formatChunkIdentity(chunks)) {
|
|
t.Fatalf("identity is not deterministic")
|
|
}
|
|
changedFid := []*filer_pb.FileChunk{
|
|
{FileId: "1,ab", Offset: 0, Size: 10},
|
|
{FileId: "3,ef", Offset: 10, Size: 20},
|
|
}
|
|
if bytes.Equal(identity, formatChunkIdentity(changedFid)) {
|
|
t.Fatalf("identity ignored a chunk replacement")
|
|
}
|
|
changedOffset := []*filer_pb.FileChunk{
|
|
{FileId: "1,ab", Offset: 0, Size: 10},
|
|
{FileId: "2,cd", Offset: 12, Size: 20},
|
|
}
|
|
if bytes.Equal(identity, formatChunkIdentity(changedOffset)) {
|
|
t.Fatalf("identity ignored an offset change")
|
|
}
|
|
// a truncate mutates Size while keeping the chunk id
|
|
changedSize := []*filer_pb.FileChunk{
|
|
{FileId: "1,ab", Offset: 0, Size: 10},
|
|
{FileId: "2,cd", Offset: 10, Size: 15},
|
|
}
|
|
if bytes.Equal(identity, formatChunkIdentity(changedSize)) {
|
|
t.Fatalf("identity ignored a size change")
|
|
}
|
|
changedMtime := []*filer_pb.FileChunk{
|
|
{FileId: "1,ab", Offset: 0, Size: 10},
|
|
{FileId: "2,cd", Offset: 10, Size: 20, ModifiedTsNs: 7},
|
|
}
|
|
if bytes.Equal(identity, formatChunkIdentity(changedMtime)) {
|
|
t.Fatalf("identity ignored a modification timestamp change")
|
|
}
|
|
changedManifest := []*filer_pb.FileChunk{
|
|
{FileId: "1,ab", Offset: 0, Size: 10},
|
|
{FileId: "2,cd", Offset: 10, Size: 20, IsChunkManifest: true},
|
|
}
|
|
if bytes.Equal(identity, formatChunkIdentity(changedManifest)) {
|
|
t.Fatalf("identity ignored a manifest flag change")
|
|
}
|
|
}
|