Files
seaweedfs/weed/server/filer_server_format_test.go
T
Chris Lu 10b64686ba filer: repack chunk TTLs round up and follow the S3 expiry anchor
SecondsToTTL truncates to the volume TTL grid, so 3599 remaining
seconds became 59m and anything under a minute became no TTL at all -
permanent chunks under an expiring entry. Round the remaining lifetime
up to the smallest representable value instead, and anchor it the way
FindEntry expires entries: S3-expiring entries age from Mtime, others
from Crtime, so a recently overwritten S3 object is no longer treated
as nearly expired.

Also bind each layout to a digest of the chunk list it described.
Offset writes and appends keep Extended while changing the chunks, so
a same-size partial write used to leave the old playlist and extents
being served over new bytes; the views now detect the mismatch and
answer 404 until the file is re-ingested or repacked.
2026-08-10 18:25:03 -07:00

71 lines
2.0 KiB
Go

package weed_server
import (
"bytes"
"math"
"testing"
"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), math.MaxInt32}, // beyond every unit's 255 cap
}
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 int64(got) < test.seconds && got != math.MaxInt32 {
t.Fatalf("roundUpToVolumeTTL(%d) = %d shortened the lifetime", test.seconds, got)
}
// the rounded value must survive the volume TTL string conversion intact
if got != math.MaxInt32 {
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 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")
}
}