fix: accept upload-id-marker together with key-marker in ListMultipartUploads

The posix and azure backends dropped every upload with a key equal to
the key-marker before handing the list to the lister, which then looked
for the upload-id-marker under the next key. An upload-id-marker that
belongs to the key-marker could never be found, so the request failed
with "Invalid uploadId marker", including when a client paged with the
NextKeyMarker and NextUploadIdMarker the gateway returned.

Keep the uploads of the key-marker itself when an upload-id-marker is
set, so the listing continues right after the given upload, as S3 does.
Enable the REST test that covers it.

Fixes #2149
This commit is contained in:
dualfroz committed 2026-10-03 16:41:32 +02:00
1 parent f81c6a5acb
commit cbe870a618
6 files changed
+55 -11

No files matched your search

+43
View File
@@ -346,6 +346,49 @@ func ListMultipartUploads_keyMarker_not_from_list(s *S3Conf) error {
})
}
func ListMultipartUploads_upload_id_marker_with_key_marker(s *S3Conf) error {
testName := "ListMultipartUploads_upload_id_marker_with_key_marker"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
for _, key := range []string{"bar", "baz", "baz", "foo"} {
_, err := createMp(s3client, bucket, key)
if err != nil {
return err
}
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
uploads := out.Uploads
// key-marker and upload-id-marker of any listed upload
// should continue the listing right after that upload
for i, up := range uploads {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: up.Key,
UploadIdMarker: up.UploadId,
})
cancel()
if err != nil {
return err
}
if !compareMultipartUploads(uploads[i+1:], out.Uploads) {
return fmt.Errorf("expected the mp list to be %v, instead got %v", uploads[i+1:], out.Uploads)
}
}
return nil
})
}
func ListMultipartUploads_delimiter_truncated(s *S3Conf) error {
testName := "ListMultipartUploads_delimiter_truncated"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+2
View File
@@ -546,6 +546,7 @@ func TestListMultipartUploads(ts *TestState) {
ts.Run(ListMultipartUploads_ignore_upload_id_marker)
ts.Run(ListMultipartUploads_invalid_uploadId_marker)
ts.Run(ListMultipartUploads_keyMarker_not_from_list)
ts.Run(ListMultipartUploads_upload_id_marker_with_key_marker)
ts.Run(ListMultipartUploads_delimiter_truncated)
ts.Run(ListMultipartUploads_prefix)
ts.Run(ListMultipartUploads_both_delimiter_and_prefix)
@@ -3254,6 +3255,7 @@ func GetIntTests() IntTests {
"ListMultipartUploads_ignore_upload_id_marker": ListMultipartUploads_ignore_upload_id_marker,
"ListMultipartUploads_invalid_uploadId_marker": ListMultipartUploads_invalid_uploadId_marker,
"ListMultipartUploads_keyMarker_not_from_list": ListMultipartUploads_keyMarker_not_from_list,
"ListMultipartUploads_upload_id_marker_with_key_marker": ListMultipartUploads_upload_id_marker_with_key_marker,
"ListMultipartUploads_delimiter_truncated": ListMultipartUploads_delimiter_truncated,
"ListMultipartUploads_prefix": ListMultipartUploads_prefix,
"ListMultipartUploads_both_delimiter_and_prefix": ListMultipartUploads_both_delimiter_and_prefix,