Files
seaweedfs/weed/server/filer_server_format_test.go
Chris Lu 618febdba5 filer: inline content disqualifies format views and repack
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.
2026-08-10 19:23:09 -07:00

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")
}
}