mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-07 20:47:46 +01:00
Per codex review: TestLifecycleMultipleBucketsInOneSweep treated any HeadObject error as "deleted", which lets a transport failure or dead endpoint mask a real bug. Recognize NoSuchKey/NotFound/HTTP-404 specifically via a small isS3NotFound helper so the assertion actually proves deletion happened, not just that the call broke.
91 lines
2.7 KiB
Go
91 lines
2.7 KiB
Go
// Multiple-bucket integration scenario.
|
|
package lifecycle
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// isS3NotFound recognizes a NotFound (NoSuchKey/404) response from the
|
|
// AWS SDK. Treating any HeadObject error as "deleted" lets a transport
|
|
// failure or dead endpoint mask a real bug, so callers that need to
|
|
// prove deletion specifically should use this.
|
|
func isS3NotFound(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
var nsk *types.NoSuchKey
|
|
if errors.As(err, &nsk) {
|
|
return true
|
|
}
|
|
var notFound *types.NotFound
|
|
if errors.As(err, ¬Found) {
|
|
return true
|
|
}
|
|
var apiErr *smithyhttp.ResponseError
|
|
if errors.As(err, &apiErr) {
|
|
return apiErr.HTTPStatusCode() == 404
|
|
}
|
|
return false
|
|
}
|
|
|
|
// TestLifecycleMultipleBucketsInOneSweep: a single shell-driven shard
|
|
// sweep must process every bucket carrying lifecycle config, not just
|
|
// the first one alphabetically. Pinned because the scheduler iterates
|
|
// the buckets directory and a regression that returns early after the
|
|
// first match would silently disable lifecycle for every later bucket.
|
|
//
|
|
// Two buckets, each with its own 1-day prefix-expiration rule and one
|
|
// backdated object. After the worker runs, both objects must be gone.
|
|
func TestLifecycleMultipleBucketsInOneSweep(t *testing.T) {
|
|
c := s3Client(t)
|
|
fc, fcClose := filerClient(t)
|
|
defer fcClose()
|
|
|
|
bucketA := uniqueBucket("multi-a")
|
|
bucketB := uniqueBucket("multi-b")
|
|
mustCreateBucket(t, c, bucketA)
|
|
mustCreateBucket(t, c, bucketB)
|
|
|
|
putExpirationLifecycle(t, c, bucketA, "exp/", 1)
|
|
putExpirationLifecycle(t, c, bucketB, "exp/", 1)
|
|
|
|
const keyA = "exp/a.txt"
|
|
const keyB = "exp/b.txt"
|
|
putObject(t, c, bucketA, keyA, "a")
|
|
putObject(t, c, bucketB, keyB, "b")
|
|
backdateMtime(t, fc, bucketA, keyA, 30)
|
|
backdateMtime(t, fc, bucketB, keyB, 30)
|
|
|
|
out := runLifecycleShard(t)
|
|
t.Logf("shell output:\n%s", out)
|
|
|
|
// Both buckets must have their objects expired in this single sweep.
|
|
for _, c2 := range []struct {
|
|
bucket, key string
|
|
}{
|
|
{bucketA, keyA},
|
|
{bucketB, keyB},
|
|
} {
|
|
c2 := c2
|
|
require.Eventuallyf(t, func() bool {
|
|
_, err := c.HeadObject(context.Background(), &s3.HeadObjectInput{
|
|
Bucket: aws.String(c2.bucket), Key: aws.String(c2.key),
|
|
})
|
|
// Pin: only count 404 / NoSuchKey as deletion. Any other
|
|
// error (transport failure, dead endpoint, auth) would
|
|
// otherwise mask a real bug as a "passed" test.
|
|
return isS3NotFound(err)
|
|
}, 30*time.Second, 500*time.Millisecond,
|
|
"%s/%s must be expired by the multi-bucket sweep", c2.bucket, c2.key)
|
|
}
|
|
}
|