fix: NotImplemented for GetObject/HeadObject PartNumber

Fixes #1520

Removes the incorrect logic for HeadObject returning successful response, when querying an incomplete multipart upload.

Implements the logic to return `NotImplemented` error if `GetObject`/`HeadObject` is attempted with `partNumber` in azure and posix backends. The front-end part is preserved to be used in s3 proxy backend.
This commit is contained in:
niksis02
2025-09-09 22:40:36 +04:00
parent 04fbe405ca
commit 2bb8a1eeb7
6 changed files with 89 additions and 192 deletions
+6 -4
View File
@@ -171,8 +171,7 @@ func TestPutObject(s *S3Conf) {
func TestHeadObject(s *S3Conf) {
HeadObject_non_existing_object(s)
HeadObject_invalid_part_number(s)
HeadObject_non_existing_mp(s)
HeadObject_mp_success(s)
HeadObject_part_number_not_supported(s)
HeadObject_directory_object_noslash(s)
HeadObject_non_existing_dir_object(s)
HeadObject_invalid_parent_dir(s)
@@ -222,6 +221,8 @@ func TestGetObject(s *S3Conf) {
GetObject_overrides_success(s)
GetObject_overrides_presign_success(s)
GetObject_overrides_fail_public(s)
GetObject_invalid_part_number(s)
GetObject_part_number_not_supported(s)
}
func TestListObjects(s *S3Conf) {
@@ -1117,8 +1118,7 @@ func GetIntTests() IntTests {
"PutObject_racey_success": PutObject_racey_success,
"HeadObject_non_existing_object": HeadObject_non_existing_object,
"HeadObject_invalid_part_number": HeadObject_invalid_part_number,
"HeadObject_non_existing_mp": HeadObject_non_existing_mp,
"HeadObject_mp_success": HeadObject_mp_success,
"HeadObject_part_number_not_supported": HeadObject_part_number_not_supported,
"HeadObject_directory_object_noslash": HeadObject_directory_object_noslash,
"HeadObject_non_existing_dir_object": HeadObject_non_existing_dir_object,
"HeadObject_name_too_long": HeadObject_name_too_long,
@@ -1154,6 +1154,8 @@ func GetIntTests() IntTests {
"GetObject_overrides_success": GetObject_overrides_success,
"GetObject_overrides_presign_success": GetObject_overrides_presign_success,
"GetObject_overrides_fail_public": GetObject_overrides_fail_public,
"GetObject_invalid_part_number": GetObject_invalid_part_number,
"GetObject_part_number_not_supported": GetObject_part_number_not_supported,
"ListObjects_non_existing_bucket": ListObjects_non_existing_bucket,
"ListObjects_with_prefix": ListObjects_with_prefix,
"ListObjects_truncated": ListObjects_truncated,
+33 -61
View File
@@ -3569,8 +3569,8 @@ func HeadObject_invalid_part_number(s *S3Conf) error {
})
}
func HeadObject_non_existing_mp(s *S3Conf) error {
testName := "HeadObject_non_existing_mp"
func HeadObject_part_number_not_supported(s *S3Conf) error {
testName := "HeadObject_part_number_not_supported"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
partNumber := int32(4)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
@@ -3580,65 +3580,7 @@ func HeadObject_non_existing_mp(s *S3Conf) error {
PartNumber: &partNumber,
})
cancel()
if err := checkSdkApiErr(err, "NotFound"); err != nil {
return err
}
return nil
})
}
func HeadObject_mp_success(s *S3Conf) error {
testName := "HeadObject_mp_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
partCount, partSize := int64(5), int64(1024)
partNumber := int32(3)
mp, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
parts, _, err := uploadParts(s3client, partCount*partSize, partCount, bucket, obj, *mp.UploadId)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err != nil {
return err
}
if out.ContentLength == nil {
return fmt.Errorf("expected non nil content length")
}
if *out.ContentLength != int64(partSize) {
return fmt.Errorf("expected content length to be %v, instead got %v",
partSize, *out.ContentLength)
}
if getString(out.ETag) != getString(parts[partNumber-1].ETag) {
return fmt.Errorf("expected ETag to be %v, instead got %v",
getString(parts[partNumber-1].ETag), getString(out.ETag))
}
if out.PartsCount == nil {
return fmt.Errorf("expected non nil parts count")
}
if *out.PartsCount != int32(partCount) {
return fmt.Errorf("expected part count to be %v, instead got %v",
partCount, *out.PartsCount)
}
if out.StorageClass != types.StorageClassStandard {
return fmt.Errorf("expected the storage class to be %v, instead got %v",
types.StorageClassStandard, out.StorageClass)
}
return nil
return checkSdkApiErr(err, "NotImplemented")
})
}
@@ -5560,6 +5502,36 @@ func GetObject_overrides_fail_public(s *S3Conf) error {
}, withAnonymousClient())
}
func GetObject_invalid_part_number(s *S3Conf) error {
testName := "GetObject_invalid_part_number"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
defer cancel()
_, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: getPtr("obj"),
PartNumber: getPtr(int32(-3)),
})
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPartNumber))
})
}
func GetObject_part_number_not_supported(s *S3Conf) error {
testName := "GetObject_part_number_not_supported"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
defer cancel()
_, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: getPtr("obj"),
PartNumber: getPtr(int32(3)),
})
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented))
})
}
func ListObjects_non_existing_bucket(s *S3Conf) error {
testName := "ListObjects_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {