Fix S3 delete for non-empty directory markers (#8740)
* Fix S3 delete for non-empty directory markers * Address review feedback on directory marker deletes * Stabilize FUSE concurrent directory operations
This commit is contained in:
@@ -97,11 +97,26 @@ func NewFuseTestFramework(t *testing.T, config *TestConfig) *FuseTestFramework {
|
||||
// freePort asks the OS for a free TCP port.
|
||||
func freePort(t *testing.T) int {
|
||||
t.Helper()
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
port := l.Addr().(*net.TCPAddr).Port
|
||||
l.Close()
|
||||
return port
|
||||
const (
|
||||
minServicePort = 20000
|
||||
maxServicePort = 55535 // SeaweedFS gRPC service uses httpPort + 10000.
|
||||
)
|
||||
|
||||
portCount := maxServicePort - minServicePort + 1
|
||||
start := minServicePort + int(time.Now().UnixNano()%int64(portCount))
|
||||
|
||||
for attempt := 0; attempt < 512; attempt++ {
|
||||
port := minServicePort + (start-minServicePort+attempt)%portCount
|
||||
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
l.Close()
|
||||
return port
|
||||
}
|
||||
|
||||
t.Fatalf("failed to allocate port <= %d after repeated attempts", maxServicePort)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Setup starts SeaweedFS cluster and mounts FUSE filesystem
|
||||
|
||||
@@ -99,6 +99,10 @@ func TestS3Integration(t *testing.T) {
|
||||
testDeleteObject(t, cluster)
|
||||
})
|
||||
|
||||
t.Run("DeleteDirectoryMarkerWithChildren", func(t *testing.T) {
|
||||
testDeleteDirectoryMarkerWithChildren(t, cluster)
|
||||
})
|
||||
|
||||
t.Run("DeleteBucket", func(t *testing.T) {
|
||||
testDeleteBucket(t, cluster)
|
||||
})
|
||||
@@ -754,6 +758,47 @@ func testDeleteObject(t *testing.T, cluster *TestCluster) {
|
||||
t.Logf("✓ Deleted object: %s/%s", bucketName, objectKey)
|
||||
}
|
||||
|
||||
func testDeleteDirectoryMarkerWithChildren(t *testing.T, cluster *TestCluster) {
|
||||
bucketName := createTestBucket(t, cluster, "test-delete-dir-marker-")
|
||||
childKey := "test-content/file1.txt"
|
||||
directoryMarkerKey := "test-content/"
|
||||
|
||||
_, err := cluster.s3Client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(childKey),
|
||||
Body: bytes.NewReader([]byte("child")),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cluster.s3Client.PutObject(&s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(directoryMarkerKey),
|
||||
Body: bytes.NewReader(nil),
|
||||
ContentType: aws.String("application/octet-stream"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cluster.s3Client.DeleteObject(&s3.DeleteObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(directoryMarkerKey),
|
||||
})
|
||||
require.NoError(t, err, "Deleting a directory marker should succeed even when children exist")
|
||||
|
||||
listResp, err := cluster.s3Client.ListObjectsV2(&s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucketName),
|
||||
Prefix: aws.String("test-content/"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
foundKeys := make(map[string]bool)
|
||||
for _, obj := range listResp.Contents {
|
||||
foundKeys[aws.StringValue(obj.Key)] = true
|
||||
}
|
||||
|
||||
assert.True(t, foundKeys[childKey], "Child object should remain after deleting the directory marker")
|
||||
assert.False(t, foundKeys[directoryMarkerKey], "Directory marker should no longer be listed after deletion")
|
||||
}
|
||||
|
||||
func testDeleteBucket(t *testing.T, cluster *TestCluster) {
|
||||
bucketName := "test-delete-bucket-" + randomString(8)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user