mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-20 22:27:04 +00:00
fix(s3/lifecycle): restore walker tag and null-version state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -104,6 +104,7 @@ func walkBucketTree(ctx context.Context, client filer_pb.SeaweedFilerClient, dir
|
||||
ModTime: time.Unix(e.Attributes.Mtime, int64(e.Attributes.MtimeNs)),
|
||||
Size: int64(e.Attributes.FileSize),
|
||||
IsLatest: true, // Non-versioned default.
|
||||
Tags: extractTags(e.Extended),
|
||||
}
|
||||
return cb(entry)
|
||||
})
|
||||
@@ -183,7 +184,7 @@ func expandVersionsDir(ctx context.Context, client filer_pb.SeaweedFilerClient,
|
||||
// Resolve latest:
|
||||
// 1. Pointer names a real id -> that wins.
|
||||
// 2. Pointer absent (or stale: set but no sibling carries it)
|
||||
// + items[0] is an EXPLICIT null -> null is latest.
|
||||
// + any EXPLICIT null bare exists -> null is latest.
|
||||
// 3. Otherwise -> newest sibling (latestPos = 0 by default).
|
||||
//
|
||||
// A stale pointer falls through to the no-pointer fallback rather
|
||||
@@ -203,8 +204,13 @@ func expandVersionsDir(ctx context.Context, client filer_pb.SeaweedFilerClient,
|
||||
}
|
||||
}
|
||||
}
|
||||
if !pointerResolved && len(items) > 0 && items[0].versionID == "null" && items[0].isExplicitNull {
|
||||
latestPos = 0
|
||||
if !pointerResolved {
|
||||
for i, it := range items {
|
||||
if it.versionID == "null" && it.isExplicitNull {
|
||||
latestPos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if start != "" && logical <= start {
|
||||
@@ -228,6 +234,7 @@ func expandVersionsDir(ctx context.Context, client filer_pb.SeaweedFilerClient,
|
||||
IsDeleteMarker: string(it.entry.Extended[s3_constants.ExtDeleteMarkerKey]) == "true",
|
||||
NumVersions: len(items),
|
||||
SuccessorModTime: successor,
|
||||
Tags: extractTags(it.entry.Extended),
|
||||
}
|
||||
if !isLatest {
|
||||
rank := i
|
||||
@@ -318,3 +325,21 @@ func isMPUInitDir(key string, entry *filer_pb.Entry) bool {
|
||||
v, ok := entry.Extended[s3_constants.ExtMultipartObjectKey]
|
||||
return ok && len(v) > 0
|
||||
}
|
||||
|
||||
func extractTags(ext map[string][]byte) map[string]string {
|
||||
if len(ext) == 0 {
|
||||
return nil
|
||||
}
|
||||
prefix := s3_constants.AmzObjectTagging + "-"
|
||||
var tags map[string]string
|
||||
for k, v := range ext {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
if tags == nil {
|
||||
tags = make(map[string]string)
|
||||
}
|
||||
tags[k[len(prefix):]] = string(v)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
@@ -126,6 +126,25 @@ func TestFilerListFunc_EmitsFlatFiles(t *testing.T) {
|
||||
assert.Equal(t, []string{"a.txt", "b.txt"}, got)
|
||||
}
|
||||
|
||||
func TestFilerListFunc_PropagatesTagsOnFlatFiles(t *testing.T) {
|
||||
mtime := time.Now().Add(-7 * 24 * time.Hour)
|
||||
tagged := fileWithExt("tagged.txt", mtime, 10, map[string][]byte{
|
||||
s3_constants.AmzObjectTagging + "-env": []byte("temp"),
|
||||
s3_constants.AmzObjectTagging + "-tier": []byte("cold"),
|
||||
})
|
||||
client := &fakeFiler{tree: map[string][]*filer_pb.Entry{
|
||||
"/buckets/bkt": {tagged},
|
||||
}}
|
||||
listFn := FilerListFunc(client, "/buckets")
|
||||
var got *bootstrap.Entry
|
||||
require.NoError(t, listFn(context.Background(), "bkt", "", func(e *bootstrap.Entry) error {
|
||||
got = e
|
||||
return nil
|
||||
}))
|
||||
require.NotNil(t, got)
|
||||
assert.Equal(t, map[string]string{"env": "temp", "tier": "cold"}, got.Tags)
|
||||
}
|
||||
|
||||
func TestFilerListFunc_RecursesIntoSubdirs(t *testing.T) {
|
||||
mtime := time.Now()
|
||||
client := &fakeFiler{tree: map[string][]*filer_pb.Entry{
|
||||
@@ -360,6 +379,40 @@ func TestFilerListFunc_VersionedExpansionExplicitNullIsLatestWhenPointerMissing(
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestFilerListFunc_VersionedExpansionExplicitNullBeatsNewerSiblingWhenPointerMissing(t *testing.T) {
|
||||
// Suspended-versioning current-ness is carried by the explicit bare
|
||||
// null, not by mtime ordering. Backdating the null to make it due
|
||||
// must not demote it behind a newer noncurrent sibling when the
|
||||
// .versions pointer is absent.
|
||||
tNull := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) // older
|
||||
tV1 := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC) // newer
|
||||
bareNull := fileWithExt("foo", tNull, 1, map[string][]byte{s3_constants.ExtVersionIdKey: []byte("null")})
|
||||
client := &fakeFiler{tree: map[string][]*filer_pb.Entry{
|
||||
"/buckets/bkt": {
|
||||
bareNull,
|
||||
versionsDir("foo"+s3_constants.VersionsFolder, ""),
|
||||
},
|
||||
"/buckets/bkt/foo" + s3_constants.VersionsFolder: {
|
||||
fileWithExt("v1", tV1, 1, map[string][]byte{s3_constants.ExtVersionIdKey: []byte("v1")}),
|
||||
},
|
||||
}}
|
||||
listFn := FilerListFunc(client, "/buckets")
|
||||
var got []*bootstrap.Entry
|
||||
require.NoError(t, listFn(context.Background(), "bkt", "", func(e *bootstrap.Entry) error {
|
||||
got = append(got, e)
|
||||
return nil
|
||||
}))
|
||||
require.Len(t, got, 2)
|
||||
byID := map[string]*bootstrap.Entry{}
|
||||
for _, e := range got {
|
||||
byID[e.VersionID] = e
|
||||
}
|
||||
require.NotNil(t, byID["null"])
|
||||
require.NotNil(t, byID["v1"])
|
||||
assert.True(t, byID["null"].IsLatest, "explicit null must stay latest even when older than noncurrent siblings")
|
||||
assert.False(t, byID["v1"].IsLatest)
|
||||
}
|
||||
|
||||
func TestFilerListFunc_VersionsDirWithoutMarkersRecursesAsRegular(t *testing.T) {
|
||||
// A `.versions`-named folder whose children have no
|
||||
// ExtVersionIdKey is a coincidence (user folder). Recurse into
|
||||
|
||||
Reference in New Issue
Block a user