fix(s3api): clear stale object lock years (#9890)

Problem: Re-storing object-lock default retention with Days left a previous Years extended attribute in place, so later loads could see both Days and stale Years.

Root cause: StoreObjectLockConfigurationInExtended only wrote period fields that were set on the new configuration and did not delete old Days or Years keys before writing the replacement rule.

Fix: Clear stored default-retention Days and Years keys before writing the current default retention period fields.

Reproduction: go test ./weed/s3api -run TestStoreObjectLockConfigurationClearsStaleYears -count=1 failed before the fix because the stale years key remained.

Validation: go test ./weed/s3api -run TestStoreObjectLockConfigurationClearsStaleYears -count=1; go test ./weed/s3api -count=1; git diff --check; git diff --cached --check

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
7y-9
2026-06-09 00:48:38 -07:00
committed by GitHub
co-authored by Codex
parent 7aba10fa1a
commit 1cf92f6c2e
2 changed files with 39 additions and 0 deletions
+3
View File
@@ -95,6 +95,9 @@ func StoreObjectLockConfigurationInExtended(entry *filer_pb.Entry, config *Objec
if config.Rule != nil && config.Rule.DefaultRetention != nil {
defaultRetention := config.Rule.DefaultRetention
delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey)
delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey)
// Store mode
if defaultRetention.Mode != "" {
entry.Extended[s3_constants.ExtObjectLockDefaultModeKey] = []byte(defaultRetention.Mode)
+36
View File
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
)
@@ -817,6 +818,41 @@ func TestValidateDefaultRetention(t *testing.T) {
}
}
func TestStoreObjectLockConfigurationClearsStaleYears(t *testing.T) {
entry := &filer_pb.Entry{
Extended: map[string][]byte{
s3_constants.ExtObjectLockEnabledKey: []byte(s3_constants.ObjectLockEnabled),
s3_constants.ExtObjectLockDefaultModeKey: []byte(s3_constants.RetentionModeCompliance),
s3_constants.ExtObjectLockDefaultYearsKey: []byte("1"),
},
}
config := CreateObjectLockConfiguration(true, s3_constants.RetentionModeGovernance, 30, 0)
if err := StoreObjectLockConfigurationInExtended(entry, config); err != nil {
t.Fatalf("StoreObjectLockConfigurationInExtended returned error: %v", err)
}
if _, exists := entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey]; exists {
t.Fatalf("stale default retention years key was not cleared")
}
loaded, found := LoadObjectLockConfigurationFromExtended(entry)
if !found {
t.Fatalf("expected stored object lock configuration to load")
}
if loaded.Rule == nil || loaded.Rule.DefaultRetention == nil {
t.Fatalf("expected loaded configuration to include default retention")
}
retention := loaded.Rule.DefaultRetention
if !retention.DaysSet || retention.Days != 30 {
t.Fatalf("expected loaded retention days to be 30, got DaysSet=%v Days=%d", retention.DaysSet, retention.Days)
}
if retention.YearsSet || retention.Years != 0 {
t.Fatalf("expected loaded retention years to be cleared, got YearsSet=%v Years=%d", retention.YearsSet, retention.Years)
}
}
// Helper function to create a time pointer
func timePtr(t time.Time) *time.Time {
return &t