diff --git a/hadoop-ozone/integration-test-s3/dev-support/findbugsExcludeFile.xml b/hadoop-ozone/integration-test-s3/dev-support/findbugsExcludeFile.xml index ee5ed59808bf..36821a531828 100644 --- a/hadoop-ozone/integration-test-s3/dev-support/findbugsExcludeFile.xml +++ b/hadoop-ozone/integration-test-s3/dev-support/findbugsExcludeFile.xml @@ -13,4 +13,8 @@ limitations under the License. See accompanying LICENSE file. --> + + + + diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java index a668adbeaed2..6417ed3a4ed7 100644 --- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java @@ -21,9 +21,12 @@ import static org.apache.hadoop.ozone.s3.awssdk.S3SDKTestUtils.calculateDigest; import static org.apache.hadoop.ozone.s3.awssdk.S3SDKTestUtils.createFile; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static software.amazon.awssdk.core.sync.RequestBody.fromString; import java.io.ByteArrayOutputStream; import java.io.File; @@ -51,7 +54,10 @@ import org.apache.hadoop.hdds.client.ReplicationFactor; import org.apache.hadoop.hdds.client.ReplicationType; import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.StorageType; import org.apache.hadoop.ozone.MiniOzoneCluster; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.client.BucketArgs; import org.apache.hadoop.ozone.client.ObjectStore; import org.apache.hadoop.ozone.client.OzoneBucket; import org.apache.hadoop.ozone.client.OzoneClient; @@ -62,9 +68,13 @@ import org.apache.hadoop.ozone.s3.endpoint.S3Owner; import org.apache.hadoop.security.UserGroupInformation; import org.apache.ozone.test.OzoneTestBase; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.io.TempDir; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.sync.RequestBody; @@ -77,24 +87,43 @@ import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest; +import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse; import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload; import software.amazon.awssdk.services.s3.model.CompletedPart; import software.amazon.awssdk.services.s3.model.CopyObjectRequest; import software.amazon.awssdk.services.s3.model.CopyObjectResponse; +import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse; +import software.amazon.awssdk.services.s3.model.Delete; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectTaggingRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest; +import software.amazon.awssdk.services.s3.model.GetBucketAclRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import software.amazon.awssdk.services.s3.model.ListBucketsResponse; +import software.amazon.awssdk.services.s3.model.ListMultipartUploadsRequest; import software.amazon.awssdk.services.s3.model.ListObjectsRequest; import software.amazon.awssdk.services.s3.model.ListObjectsResponse; import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.ListPartsRequest; +import software.amazon.awssdk.services.s3.model.ObjectIdentifier; +import software.amazon.awssdk.services.s3.model.PutBucketAclRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectTaggingRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.awssdk.services.s3.model.S3Object; import software.amazon.awssdk.services.s3.model.Tag; import software.amazon.awssdk.services.s3.model.Tagging; +import software.amazon.awssdk.services.s3.model.UploadPartCopyRequest; import software.amazon.awssdk.services.s3.model.UploadPartRequest; import software.amazon.awssdk.services.s3.model.UploadPartResponse; import software.amazon.awssdk.services.s3.presigner.S3Presigner; @@ -621,4 +650,591 @@ private void completeMultipartUpload(String bucketName, String key, String uploa assertEquals(bucketName, compResponse.bucket()); assertEquals(key, compResponse.key()); } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class S3BucketOwnershipVerificationConditionsTests { + + private static final String DEFAULT_BUCKET_NAME = "default-bucket"; + private String correctOwner; + private static final String WRONG_OWNER = "wrong-owner"; + private static final String BUCKET_VERIFICATION_TEST_KEY = "test-key"; + + @BeforeAll + public void setup() { + // create bucket to verify bucket ownership + s3Client.createBucket(b -> b.bucket(DEFAULT_BUCKET_NAME)); + GetBucketAclRequest normalRequest = GetBucketAclRequest.builder().bucket(DEFAULT_BUCKET_NAME).build(); + correctOwner = s3Client.getBucketAcl(normalRequest).owner().displayName(); + + // create objects to verify bucket ownership + s3Client.putObject(b -> b.bucket(DEFAULT_BUCKET_NAME).key(BUCKET_VERIFICATION_TEST_KEY), RequestBody.empty()); + } + + @Nested + class BucketEndpointTests { + + @Test + public void testGetBucketAcl() { + GetBucketAclRequest correctRequest = GetBucketAclRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.getBucketAcl(correctRequest)); + + GetBucketAclRequest wrongRequest = GetBucketAclRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.getBucketAcl(wrongRequest)); + } + + @Test + public void testListObjects() { + ListObjectsRequest correctRequest = ListObjectsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.listObjects(correctRequest)); + + ListObjectsRequest wrongRequest = ListObjectsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.listObjects(wrongRequest)); + } + + @Test + public void testListMultipartUploads() { + ListMultipartUploadsRequest correctRequest = ListMultipartUploadsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.listMultipartUploads(correctRequest)); + + ListMultipartUploadsRequest wrongRequest = ListMultipartUploadsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied( + () -> s3Client.listMultipartUploads(wrongRequest)); + } + + @Test + public void testPutBucketAcl() { + PutBucketAclRequest correctRequest = PutBucketAclRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .grantRead("") + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.putBucketAcl(correctRequest)); + + PutBucketAclRequest wrongRequest = PutBucketAclRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .grantRead("") + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.putBucketAcl(wrongRequest)); + } + + @Test + public void testHeadBucket() { + HeadBucketRequest correctRequest = HeadBucketRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.headBucket(correctRequest)); + + HeadBucketRequest wrongRequest = HeadBucketRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(WRONG_OWNER) + .build(); + S3Exception exception = assertThrows(S3Exception.class, () -> s3Client.headBucket(wrongRequest)); + assertEquals(403, exception.statusCode()); + } + + @Test + public void testDeleteBucket() { + s3Client.createBucket(builder -> builder.bucket("for-delete")); + String newCorrectOwner = s3Client.getBucketAcl(builder -> builder.bucket("for-delete")).owner().displayName(); + + DeleteBucketRequest wrongRequest = DeleteBucketRequest.builder() + .bucket("for-delete") + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.deleteBucket(wrongRequest)); + + DeleteBucketRequest correctRequest = DeleteBucketRequest.builder() + .bucket("for-delete") + .expectedBucketOwner(newCorrectOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.deleteBucket(correctRequest)); + } + + @Test + public void testMultiDelete() { + DeleteObjectsRequest correctRequest = DeleteObjectsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(correctOwner) + .delete(Delete.builder().objects(ObjectIdentifier.builder().key("test").build()).build()) + .build(); + + verifyPassBucketOwnershipVerification(() -> s3Client.deleteObjects(correctRequest)); + + DeleteObjectsRequest wrongRequest = DeleteObjectsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(WRONG_OWNER) + .delete(Delete.builder().objects(ObjectIdentifier.builder().key("test").build()).build()) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.deleteObjects(wrongRequest)); + } + } + + @Nested + class ObjectEndpointTests { + + private static final String TEST_CONTENT = "test-content"; + + @Test + public void testCreateKey() { + String newKey = "create-key"; + + PutObjectRequest wrongRequest = PutObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.putObject(wrongRequest, fromString(TEST_CONTENT))); + + PutObjectRequest correctRequest = PutObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.putObject(correctRequest, fromString(TEST_CONTENT))); + } + + @Test + public void testPutObjectTagging() { + List tags = new ArrayList<>(); + tags.add(Tag.builder().key("env").value("test").build()); + tags.add(Tag.builder().key("project").value("example").build()); + PutObjectTaggingRequest wrongRequest = PutObjectTaggingRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .tagging(Tagging.builder().tagSet(tags).build()) + .expectedBucketOwner(WRONG_OWNER) + .build(); + + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.putObjectTagging(wrongRequest)); + + PutObjectTaggingRequest correctRequest = PutObjectTaggingRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .tagging(Tagging.builder().tagSet(tags).build()) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.putObjectTagging(correctRequest)); + } + + @Test + public void testCreateMultipartKey() { + CreateMultipartUploadRequest wrongRequest = CreateMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.createMultipartUpload(wrongRequest)); + + CreateMultipartUploadRequest correctRequest = CreateMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.createMultipartUpload(correctRequest)); + } + + @Test + public void testCreateMultipartByCopy() { + String sourceKey = "test-multipart-by-copy-source-key"; + String destKey = "test-multipart-by-copy-dest-key"; + + s3Client.putObject(b -> b.bucket(DEFAULT_BUCKET_NAME).key(sourceKey), fromString(TEST_CONTENT)); + + CreateMultipartUploadResponse initResponse = + s3Client.createMultipartUpload(b -> b.bucket(DEFAULT_BUCKET_NAME).key(destKey)); + + String uploadId = initResponse.uploadId(); + + UploadPartCopyRequest wrongRequest = UploadPartCopyRequest.builder() + .sourceBucket(DEFAULT_BUCKET_NAME) + .sourceKey(sourceKey) + .expectedSourceBucketOwner(WRONG_OWNER) + .destinationBucket(DEFAULT_BUCKET_NAME) + .destinationKey(destKey) + .uploadId(uploadId) + .partNumber(1) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.uploadPartCopy(wrongRequest)); + + UploadPartCopyRequest correctRequest = UploadPartCopyRequest.builder() + .sourceBucket(DEFAULT_BUCKET_NAME) + .sourceKey(sourceKey) + .expectedSourceBucketOwner(correctOwner) + .destinationBucket(DEFAULT_BUCKET_NAME) + .destinationKey(destKey) + .uploadId(uploadId) + .expectedBucketOwner(correctOwner) + .partNumber(1) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.uploadPartCopy(correctRequest)); + } + + @Test + public void testCopyObject() { + String sourceKey = "test-copy-object-source-key"; + String destKey = "test-copy-object-dest-key"; + s3Client.putObject(b -> b.bucket(DEFAULT_BUCKET_NAME).key(sourceKey), fromString("test")); + + CopyObjectRequest wrongRequest = CopyObjectRequest.builder() + .sourceBucket(DEFAULT_BUCKET_NAME) + .sourceKey(sourceKey) + .destinationBucket(DEFAULT_BUCKET_NAME) + .destinationKey(destKey) + .expectedSourceBucketOwner(WRONG_OWNER) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.copyObject(wrongRequest)); + + CopyObjectRequest correctRequest = CopyObjectRequest.builder() + .sourceBucket(DEFAULT_BUCKET_NAME) + .sourceKey(sourceKey) + .destinationBucket(DEFAULT_BUCKET_NAME) + .destinationKey(destKey) + .expectedSourceBucketOwner(correctOwner) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.copyObject(correctRequest)); + } + + @Test + public void testCreateDirectory() { + String newKey = "create-directory-key/"; + + PutObjectRequest wrongRequest = PutObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.putObject(wrongRequest, RequestBody.empty())); + + PutObjectRequest correctRequest = PutObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.putObject(correctRequest, RequestBody.empty())); + } + + @Test + public void testGetKey() { + GetObjectRequest correctRequest = GetObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.getObject(correctRequest)); + + GetObjectRequest wrongRequest = GetObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.getObject(wrongRequest)); + } + + @Test + public void testGetObjectTagging() { + GetObjectTaggingRequest correctRequest = GetObjectTaggingRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.getObjectTagging(correctRequest)); + + GetObjectTaggingRequest wrongRequest = GetObjectTaggingRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.getObjectTagging(wrongRequest)); + } + + @Test + public void testListParts() { + String newKey = "list-parts-key"; + CreateMultipartUploadResponse multipartUploadResponse = s3Client.createMultipartUpload(b -> { + b.bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .build(); + }); + + String uploadId = multipartUploadResponse.uploadId(); + + s3Client.uploadPart( + UploadPartRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .uploadId(uploadId) + .partNumber(1) + .contentLength((long) TEST_CONTENT.getBytes(StandardCharsets.UTF_8).length) + .build(), fromString(TEST_CONTENT)); + + ListPartsRequest correctRequest = ListPartsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .uploadId(uploadId) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.listParts(correctRequest)); + + ListPartsRequest wrongResponse = ListPartsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .uploadId(uploadId) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.listParts(wrongResponse)); + } + + @Test + public void testHeadKey() { + HeadObjectRequest correctRequest = HeadObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.headObject(correctRequest)); + + HeadObjectRequest wrongRequest = HeadObjectRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(WRONG_OWNER) + .build(); + S3Exception exception = assertThrows(S3Exception.class, () -> s3Client.headObject(wrongRequest)); + assertEquals(403, exception.statusCode()); + } + + @Test + public void testDeleteKey() { + String newKey = "delete-key"; + s3Client.putObject(b -> b.bucket(DEFAULT_BUCKET_NAME).key(newKey), fromString(TEST_CONTENT)); + + DeleteObjectsRequest wrongRequest = DeleteObjectsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(WRONG_OWNER) + .delete(Delete.builder().objects(ObjectIdentifier.builder().key(newKey).build()).build()) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.deleteObjects(wrongRequest)); + + DeleteObjectsRequest correctRequest = DeleteObjectsRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .expectedBucketOwner(correctOwner) + .delete(Delete.builder().objects(ObjectIdentifier.builder().key(newKey).build()).build()) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.deleteObjects(correctRequest)); + } + + @Test + public void testDeleteObjectTagging() { + DeleteObjectTaggingRequest correctRequest = DeleteObjectTaggingRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.deleteObjectTagging(correctRequest)); + + DeleteObjectTaggingRequest wrongRequest = DeleteObjectTaggingRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.deleteObjectTagging(wrongRequest)); + } + + @Test + public void testAbortMultipartUpload() { + CreateMultipartUploadResponse multipartUploadResponse = + s3Client.createMultipartUpload(b -> b.bucket(DEFAULT_BUCKET_NAME).key(BUCKET_VERIFICATION_TEST_KEY)); + + String uploadId = multipartUploadResponse.uploadId(); + + AbortMultipartUploadRequest wrongRequest = AbortMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .uploadId(uploadId) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.abortMultipartUpload(wrongRequest)); + + AbortMultipartUploadRequest correctRequest = AbortMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(BUCKET_VERIFICATION_TEST_KEY) + .uploadId(uploadId) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.abortMultipartUpload(correctRequest)); + } + + @Test + public void testInitMultipartUpload() { + String newKey = "init-multipart-upload-key"; + + CreateMultipartUploadRequest wrongRequest = CreateMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.createMultipartUpload(wrongRequest)); + + CreateMultipartUploadRequest correctRequest = CreateMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.createMultipartUpload(correctRequest)); + } + + @Test + public void testCompleteMultipartUpload() { + String newKey = "complete-multipart-upload-key"; + CreateMultipartUploadResponse multipartUploadResponse = + s3Client.createMultipartUpload(b -> b.bucket(DEFAULT_BUCKET_NAME).key(newKey)); + + String uploadId = multipartUploadResponse.uploadId(); + + UploadPartResponse uploadPartResponse = s3Client.uploadPart(b -> b.bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .uploadId(uploadId) + .partNumber(1) + .contentLength((long) TEST_CONTENT.getBytes(StandardCharsets.UTF_8).length) + .build(), fromString(TEST_CONTENT)); + + CompletedMultipartUpload completedUpload = CompletedMultipartUpload.builder() + .parts( + CompletedPart.builder().partNumber(1).eTag(uploadPartResponse.eTag()).build() + ).build(); + + + CompleteMultipartUploadRequest wrongRequest = CompleteMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .uploadId(uploadId) + .multipartUpload(completedUpload) + .expectedBucketOwner(WRONG_OWNER) + .build(); + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.completeMultipartUpload(wrongRequest)); + + CompleteMultipartUploadRequest correctRequest = CompleteMultipartUploadRequest.builder() + .bucket(DEFAULT_BUCKET_NAME) + .key(newKey) + .uploadId(uploadId) + .multipartUpload(completedUpload) + .expectedBucketOwner(correctOwner) + .build(); + verifyPassBucketOwnershipVerification(() -> s3Client.completeMultipartUpload(correctRequest)); + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class LinkBucketTests { + private static final String NON_S3_VOLUME_NAME = "link-bucket-volume"; + private OzoneVolume nonS3Volume; + private OzoneVolume s3Volume; + + @BeforeAll + public void setup() throws Exception { + try (OzoneClient ozoneClient = cluster.newClient()) { + ozoneClient.getObjectStore().createVolume(NON_S3_VOLUME_NAME); + nonS3Volume = ozoneClient.getObjectStore().getVolume(NON_S3_VOLUME_NAME); + s3Volume = ozoneClient.getObjectStore().getS3Volume(); + } + } + + @Test + public void setBucketVerificationOnLinkBucket() throws Exception { + // create link bucket + String linkBucketName = "link-bucket"; + nonS3Volume.createBucket(OzoneConsts.BUCKET); + BucketArgs.Builder bb = new BucketArgs.Builder() + .setStorageType(StorageType.DEFAULT) + .setVersioning(false) + .setSourceVolume(NON_S3_VOLUME_NAME) + .setSourceBucket(OzoneConsts.BUCKET); + s3Volume.createBucket(linkBucketName, bb.build()); + + GetBucketAclRequest wrongRequest = GetBucketAclRequest.builder() + .bucket(linkBucketName) + .expectedBucketOwner(WRONG_OWNER) + .build(); + + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.getBucketAcl(wrongRequest)); + + String owner = s3Client.getBucketAcl(GetBucketAclRequest.builder() + .bucket(linkBucketName) + .build()).owner().displayName(); + GetBucketAclRequest correctRequest = GetBucketAclRequest.builder() + .bucket(linkBucketName) + .expectedBucketOwner(owner) + .build(); + + verifyPassBucketOwnershipVerification(() -> s3Client.getBucketAcl(correctRequest)); + } + + @Test + public void testDanglingBucket() throws Exception { + String sourceBucket = "source-bucket"; + String linkBucket = "link-bucket-dangling"; + nonS3Volume.createBucket(sourceBucket); + BucketArgs.Builder bb = new BucketArgs.Builder() + .setStorageType(StorageType.DEFAULT) + .setVersioning(false) + .setSourceVolume(NON_S3_VOLUME_NAME) + .setSourceBucket(sourceBucket); + s3Volume.createBucket(linkBucket, bb.build()); + + // remove source bucket to make dangling bucket + nonS3Volume.deleteBucket(sourceBucket); + + GetBucketAclRequest wrongRequest = GetBucketAclRequest.builder() + .bucket(linkBucket) + .expectedBucketOwner(WRONG_OWNER) + .build(); + + verifyBucketOwnershipVerificationAccessDenied(() -> s3Client.getBucketAcl(wrongRequest)); + + String owner = s3Client.getBucketAcl(GetBucketAclRequest.builder() + .bucket(linkBucket) + .build()).owner().displayName(); + GetBucketAclRequest correctRequest = GetBucketAclRequest.builder() + .bucket(linkBucket) + .expectedBucketOwner(owner) + .build(); + + verifyPassBucketOwnershipVerification(() -> s3Client.getBucketAcl(correctRequest)); + } + } + + private void verifyPassBucketOwnershipVerification(Executable function) { + assertDoesNotThrow(function); + } + + private void verifyBucketOwnershipVerificationAccessDenied(Executable function) { + S3Exception exception = assertThrows(S3Exception.class, function); + assertEquals(403, exception.statusCode()); + assertEquals("Access Denied", exception.awsErrorDetails().errorCode()); + } + } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java index cb641d1ea016..cd3739c4023d 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java @@ -93,6 +93,9 @@ public class BucketEndpoint extends EndpointBase { private static final Logger LOG = LoggerFactory.getLogger(BucketEndpoint.class); + @Context + private HttpHeaders headers; + private boolean listKeysShallowEnabled; private int maxKeysLimit = 1000; @@ -120,8 +123,7 @@ public Response get( @QueryParam("acl") String aclMarker, @QueryParam("key-marker") String keyMarker, @QueryParam("upload-id-marker") String uploadIdMarker, - @DefaultValue("1000") @QueryParam("max-uploads") int maxUploads, - @Context HttpHeaders hh) throws OS3Exception, IOException { + @DefaultValue("1000") @QueryParam("max-uploads") int maxUploads) throws OS3Exception, IOException { long startNanos = Time.monotonicNowNanos(); S3GAction s3GAction = S3GAction.GET_BUCKET; PerformanceStringBuilder perf = new PerformanceStringBuilder(); @@ -132,6 +134,8 @@ public Response get( OzoneBucket bucket = null; try { + bucket = getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); if (aclMarker != null) { s3GAction = S3GAction.GET_ACL; S3BucketAcl result = getAcl(bucketName); @@ -167,7 +171,6 @@ public Response get( boolean shallow = listKeysShallowEnabled && OZONE_URI_DELIMITER.equals(delimiter); - bucket = getBucket(bucketName); ozoneKeyIterator = bucket.listKeys(prefix, prevKey, shallow); } catch (OMException ex) { @@ -309,7 +312,6 @@ private int validateMaxKeys(int maxKeys) throws OS3Exception { @PUT public Response put(@PathParam("bucket") String bucketName, @QueryParam("acl") String aclMarker, - @Context HttpHeaders httpHeaders, InputStream body) throws IOException, OS3Exception { long startNanos = Time.monotonicNowNanos(); S3GAction s3GAction = S3GAction.CREATE_BUCKET; @@ -317,7 +319,7 @@ public Response put(@PathParam("bucket") String bucketName, try { if (aclMarker != null) { s3GAction = S3GAction.PUT_ACL; - Response response = putAcl(bucketName, httpHeaders, body); + Response response = putAcl(bucketName, body); AUDIT.logWriteSuccess( buildAuditMessageForSuccess(s3GAction, getAuditParameters())); return response; @@ -413,7 +415,8 @@ public Response head(@PathParam("bucket") String bucketName) long startNanos = Time.monotonicNowNanos(); S3GAction s3GAction = S3GAction.HEAD_BUCKET; try { - getBucket(bucketName); + OzoneBucket bucket = getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); AUDIT.logReadSuccess( buildAuditMessageForSuccess(s3GAction, getAuditParameters())); getMetrics().updateHeadBucketSuccessStats(startNanos); @@ -438,6 +441,8 @@ public Response delete(@PathParam("bucket") String bucketName) S3GAction s3GAction = S3GAction.DELETE_BUCKET; try { + OzoneBucket bucket = getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); deleteS3Bucket(bucketName); } catch (OMException ex) { AUDIT.logWriteFailure( @@ -492,6 +497,7 @@ public MultiDeleteResponse multiDelete(@PathParam("bucket") String bucketName, } long startNanos = Time.monotonicNowNanos(); try { + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); undeletedKeyResultMap = bucket.deleteKeys(deleteKeys, true); for (DeleteObject d : request.getObjects()) { ErrorInfo error = undeletedKeyResultMap.get(d.getKey()); @@ -540,10 +546,7 @@ public S3BucketAcl getAcl(String bucketName) S3BucketAcl result = new S3BucketAcl(); try { OzoneBucket bucket = getBucket(bucketName); - OzoneVolume volume = getVolume(); - // TODO: use bucket owner instead of volume owner here once bucket owner - // TODO: is supported. - S3Owner owner = S3Owner.of(volume.getOwner()); + S3Owner owner = S3Owner.of(bucket.getOwner()); result.setOwner(owner); // TODO: remove this duplication avoid logic when ACCESS and DEFAULT scope @@ -581,17 +584,18 @@ public S3BucketAcl getAcl(String bucketName) *

* see: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html */ - public Response putAcl(String bucketName, HttpHeaders httpHeaders, + public Response putAcl(String bucketName, InputStream body) throws IOException, OS3Exception { long startNanos = Time.monotonicNowNanos(); - String grantReads = httpHeaders.getHeaderString(S3Acl.GRANT_READ); - String grantWrites = httpHeaders.getHeaderString(S3Acl.GRANT_WRITE); - String grantReadACP = httpHeaders.getHeaderString(S3Acl.GRANT_READ_CAP); - String grantWriteACP = httpHeaders.getHeaderString(S3Acl.GRANT_WRITE_CAP); - String grantFull = httpHeaders.getHeaderString(S3Acl.GRANT_FULL_CONTROL); + String grantReads = headers.getHeaderString(S3Acl.GRANT_READ); + String grantWrites = headers.getHeaderString(S3Acl.GRANT_WRITE); + String grantReadACP = headers.getHeaderString(S3Acl.GRANT_READ_CAP); + String grantWriteACP = headers.getHeaderString(S3Acl.GRANT_WRITE_CAP); + String grantFull = headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL); try { OzoneBucket bucket = getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); OzoneVolume volume = getVolume(); List ozoneAclListOnBucket = new ArrayList<>(); @@ -770,6 +774,11 @@ public OzoneConfiguration getOzoneConfiguration() { return this.ozoneConfiguration; } + @VisibleForTesting + public void setHeaders(HttpHeaders headers) { + this.headers = headers; + } + @Override @PostConstruct public void init() { diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java index 7ebed80c2878..02482023e5df 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java @@ -243,6 +243,9 @@ public Response put( throw newError(NOT_IMPLEMENTED, keyPath); } OzoneVolume volume = getVolume(); + OzoneBucket bucket = volume.getBucket(bucketName); + String bucketOwner = bucket.getOwner(); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucketOwner); if (taggingMarker != null) { s3GAction = S3GAction.PUT_OBJECT_TAGGING; return putObjectTagging(volume, bucketName, keyPath, body); @@ -265,7 +268,6 @@ public Response put( boolean storageTypeDefault = StringUtils.isEmpty(storageType); // Normal put object - OzoneBucket bucket = volume.getBucket(bucketName); ReplicationConfig replicationConfig = getReplicationConfig(bucket, storageType, storageConfig); @@ -428,6 +430,8 @@ public Response get( S3GAction s3GAction = S3GAction.GET_KEY; PerformanceStringBuilder perf = new PerformanceStringBuilder(); try { + OzoneBucket bucket = getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); if (taggingMarker != null) { s3GAction = S3GAction.GET_OBJECT_TAGGING; return getObjectTagging(bucketName, keyPath); @@ -622,6 +626,8 @@ public Response head( OzoneKey key; try { + OzoneBucket bucket = getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); key = getClientProtocol().headS3Object(bucketName, keyPath); isFile(keyPath, key); @@ -743,6 +749,8 @@ public Response delete( try { OzoneVolume volume = getVolume(); + OzoneBucket bucket = volume.getBucket(bucketName); + S3Owner.verifyBucketOwnerCondition(headers, bucketName, bucket.getOwner()); if (taggingMarker != null) { s3GAction = S3GAction.DELETE_OBJECT_TAGGING; return deleteObjectTagging(volume, bucketName, keyPath); @@ -818,6 +826,7 @@ public Response initializeMultipartUpload( try { OzoneBucket ozoneBucket = getBucket(bucket); + S3Owner.verifyBucketOwnerCondition(headers, bucket, ozoneBucket.getOwner()); String storageType = headers.getHeaderString(STORAGE_CLASS_HEADER); String storageConfig = headers.getHeaderString(CUSTOM_METADATA_HEADER_PREFIX + STORAGE_CONFIG_HEADER); @@ -889,6 +898,9 @@ public Response completeMultipartUpload(@PathParam("bucket") String bucket, OmMultipartUploadCompleteInfo omMultipartUploadCompleteInfo; try { + OzoneBucket ozoneBucket = volume.getBucket(bucket); + S3Owner.verifyBucketOwnerCondition(headers, bucket, ozoneBucket.getOwner()); + for (CompleteMultipartUploadRequest.Part part : partList) { partsMap.put(part.getPartNumber(), part.getETag()); } @@ -896,9 +908,7 @@ public Response completeMultipartUpload(@PathParam("bucket") String bucket, LOG.debug("Parts map {}", partsMap); } - omMultipartUploadCompleteInfo = getClientProtocol() - .completeMultipartUpload(volume.getName(), bucket, key, uploadID, - partsMap); + omMultipartUploadCompleteInfo = ozoneBucket.completeMultipartUpload(key, uploadID, partsMap); CompleteMultipartUploadResponse completeMultipartUploadResponse = new CompleteMultipartUploadResponse(); completeMultipartUploadResponse.setBucket(bucket); @@ -990,6 +1000,9 @@ private Response createMultipartKey(OzoneVolume volume, String bucket, Pair result = parseSourceHeader(copyHeader); String sourceBucket = result.getLeft(); String sourceKey = result.getRight(); + String sourceBucketOwner = volume.getBucket(sourceBucket).getOwner(); + S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, sourceBucket, sourceBucketOwner, bucket, + ozoneBucket.getOwner()); OzoneKeyDetails sourceKeyDetails = getClientProtocol().getKeyDetails( volume.getName(), sourceBucket, sourceKey); @@ -1236,6 +1249,10 @@ private CopyObjectResponse copyObject(OzoneVolume volume, String sourceBucket = result.getLeft(); String sourceKey = result.getRight(); DigestInputStream sourceDigestInputStream = null; + + String sourceBucketOwner = volume.getBucket(sourceBucket).getOwner(); + // The destBucket owner has already been checked in the caller method + S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, sourceBucket, sourceBucketOwner, null, null); try { OzoneKeyDetails sourceKeyDetails = getClientProtocol().getKeyDetails( volume.getName(), sourceBucket, sourceKey); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java index bfec9038bcb0..f9baf0d5b7ff 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java @@ -17,10 +17,15 @@ package org.apache.hadoop.ozone.s3.endpoint; +import javax.ws.rs.core.HttpHeaders; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.s3.util.S3Consts; /** * Represents an owner of S3 resources in the Ozone S3 compatibility layer. @@ -80,4 +85,59 @@ public String toString() { ", id='" + id + '\'' + '}'; } + + /** + * Verify the bucket owner condition. + * + * @param headers HTTP headers + * @param bucketName bucket name + * @param bucketOwner bucket owner + * @throws OS3Exception if the expected bucket owner does not match + */ + public static void verifyBucketOwnerCondition(HttpHeaders headers, String bucketName, String bucketOwner) + throws OS3Exception { + if (headers == null || bucketOwner == null) { + return; + } + + final String expectedBucketOwner = headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER); + + if (StringUtils.isEmpty(expectedBucketOwner)) { + return; + } + if (expectedBucketOwner.equals(bucketOwner)) { + return; + } + throw S3ErrorTable.newError(S3ErrorTable.BUCKET_OWNER_MISMATCH, bucketName); + } + + /** + * Verify the bucket owner condition on copy operation. + * + * @param headers HTTP headers + * @param sourceBucketName source bucket name + * @param sourceOwner source bucket owner + * @param destBucketName dest bucket name + * @param destOwner destination bucket owner + * @throws OS3Exception if the expected source or destination bucket owner does not match + */ + public static void verifyBucketOwnerConditionOnCopyOperation(HttpHeaders headers, String sourceBucketName, + String sourceOwner, String destBucketName, + String destOwner) + throws OS3Exception { + if (headers == null) { + return; + } + + final String expectedSourceOwner = headers.getHeaderString(S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER); + final String expectedDestOwner = headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER); + + if (expectedSourceOwner != null && sourceOwner != null && !sourceOwner.equals(expectedSourceOwner)) { + throw S3ErrorTable.newError(S3ErrorTable.BUCKET_OWNER_MISMATCH, sourceBucketName); + } + + if (expectedDestOwner != null && destOwner != null && !destOwner.equals(expectedDestOwner)) { + throw S3ErrorTable.newError(S3ErrorTable.BUCKET_OWNER_MISMATCH, destBucketName); + } + } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 200d9e8acb74..060ed83d1bcc 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -156,6 +156,10 @@ public final class S3ErrorTable { "a valid custom EC storage config for if using STANDARD_IA.", HTTP_BAD_REQUEST); + public static final OS3Exception BUCKET_OWNER_MISMATCH = new OS3Exception( + "Access Denied", "User doesn't have permission to access this resource due to a " + + "bucket ownership mismatch.", HTTP_FORBIDDEN); + private static Function generateInternalError = e -> new OS3Exception("InternalError", e.getMessage(), HTTP_INTERNAL_ERROR); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java index 0e8b58dc80f2..f41013755030 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java @@ -91,6 +91,11 @@ public final class S3Consts { public static final Pattern TAG_REGEX_PATTERN = Pattern.compile("^([\\p{L}\\p{Z}\\p{N}_.:/=+\\-]*)$"); public static final String MP_PARTS_COUNT = "x-amz-mp-parts-count"; + // Bucket owner condition headers + // See https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-owner-condition.html + public static final String EXPECTED_BUCKET_OWNER_HEADER = "x-amz-expected-bucket-owner"; + public static final String EXPECTED_SOURCE_BUCKET_OWNER_HEADER = "x-amz-source-expected-bucket-owner"; + //Never Constructed private S3Consts() { diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpointBuilder.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpointBuilder.java index 1a3c4c492aa4..d192e17625a5 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpointBuilder.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpointBuilder.java @@ -31,6 +31,7 @@ public BucketEndpointBuilder() { public BucketEndpoint build() { BucketEndpoint endpoint = super.build(); endpoint.setOzoneConfiguration(getConfig()); + endpoint.setHeaders(getHeaders()); return endpoint; } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java index 1099fbea98cd..84bc414e37ee 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java @@ -67,6 +67,7 @@ public void setup() throws IOException { bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() .setClient(client) + .setHeaders(headers) .build(); } @@ -82,7 +83,7 @@ public void testGetAcl() throws Exception { when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); Response response = bucketEndpoint.get(BUCKET_NAME, null, null, null, 0, null, - null, null, null, ACL_MARKER, null, null, 0, headers); + null, null, null, ACL_MARKER, null, null, 0); assertEquals(HTTP_OK, response.getStatus()); System.out.println(response.getEntity()); } @@ -93,7 +94,7 @@ public void testSetAclWithNotSupportedGranteeType() throws Exception { .thenReturn(S3Acl.ACLIdentityType.GROUP.getHeaderType() + "=root"); when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); OS3Exception e = assertThrows(OS3Exception.class, () -> - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null)); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null)); assertEquals(e.getHttpCode(), HTTP_NOT_IMPLEMENTED); } @@ -103,7 +104,7 @@ public void testRead() throws Exception { when(headers.getHeaderString(S3Acl.GRANT_READ)) .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(1, getResponse.getAclList().getGrantList().size()); @@ -117,7 +118,7 @@ public void testWrite() throws Exception { when(headers.getHeaderString(S3Acl.GRANT_WRITE)) .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(1, getResponse.getAclList().getGrantList().size()); @@ -131,7 +132,7 @@ public void testReadACP() throws Exception { when(headers.getHeaderString(S3Acl.GRANT_READ_CAP)) .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); @@ -146,7 +147,7 @@ public void testWriteACP() throws Exception { when(headers.getHeaderString(S3Acl.GRANT_WRITE_CAP)) .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(1, getResponse.getAclList().getGrantList().size()); @@ -160,7 +161,7 @@ public void testFullControl() throws Exception { when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL)) .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(1, getResponse.getAclList().getGrantList().size()); @@ -182,7 +183,7 @@ public void testCombination() throws Exception { when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL)) .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(5, getResponse.getAclList().getGrantList().size()); @@ -195,7 +196,7 @@ public void testPutClearOldAcls() throws Exception { .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); // Put READ Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(1, getResponse.getAclList().getGrantList().size()); @@ -212,7 +213,7 @@ public void testPutClearOldAcls() throws Exception { .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); //Put WRITE response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, null); assertEquals(HTTP_OK, response.getStatus()); getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(1, getResponse.getAclList().getGrantList().size()); @@ -230,7 +231,7 @@ public void testAclInBodyWithGroupUser() { .getResourceAsStream("groupAccessControlList.xml"); when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); assertThrows(OS3Exception.class, () -> bucketEndpoint.put( - BUCKET_NAME, ACL_MARKER, headers, inputBody)); + BUCKET_NAME, ACL_MARKER, inputBody)); } @Test @@ -239,7 +240,7 @@ public void testAclInBody() throws Exception { .getResourceAsStream("userAccessControlList.xml"); when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); Response response = - bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, inputBody); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, inputBody); assertEquals(HTTP_OK, response.getStatus()); S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); assertEquals(2, getResponse.getAclList().getGrantList().size()); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketDelete.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketDelete.java index c32b0e0ffd5c..54841563aefa 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketDelete.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketDelete.java @@ -55,8 +55,6 @@ public void setup() throws Exception { bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() .setClient(clientStub) .build(); - - } @Test diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java index 3f0810dedee2..d55846c10af6 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java @@ -57,7 +57,7 @@ public void listRoot() throws OS3Exception, IOException { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, "", - null, null, null, null, null, null, 0, null) + null, null, null, null, null, null, 0) .getEntity(); assertEquals(1, getBucketResponse.getCommonPrefixes().size()); @@ -82,7 +82,7 @@ public void listDir() throws OS3Exception, IOException { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, - "dir1", null, null, null, null, null, null, 0, null).getEntity(); + "dir1", null, null, null, null, null, null, 0).getEntity(); assertEquals(1, getBucketResponse.getCommonPrefixes().size()); assertEquals("dir1/", @@ -106,7 +106,7 @@ public void listSubDir() throws OS3Exception, IOException { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket .get("b1", "/", null, null, 100, "dir1/", null, - null, null, null, null, null, 0, null) + null, null, null, null, null, 0) .getEntity(); assertEquals(1, getBucketResponse.getCommonPrefixes().size()); @@ -141,7 +141,7 @@ public void listObjectOwner() throws OS3Exception, IOException { getBucket.setRequestIdentifier(new RequestIdentifier()); ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, - "key", null, null, null, null, null, null, 0, null).getEntity(); + "key", null, null, null, null, null, null, 0).getEntity(); assertEquals(2, getBucketResponse.getContents().size()); assertEquals(user1.getShortUserName(), @@ -165,7 +165,7 @@ public void listWithPrefixAndDelimiter() throws OS3Exception, IOException { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, - "dir1", null, null, null, null, null, null, 0, null).getEntity(); + "dir1", null, null, null, null, null, null, 0).getEntity(); assertEquals(3, getBucketResponse.getCommonPrefixes().size()); @@ -185,7 +185,7 @@ public void listWithPrefixAndDelimiter1() throws OS3Exception, IOException { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, - "", null, null, null, null, null, null, 0, null).getEntity(); + "", null, null, null, null, null, null, 0).getEntity(); assertEquals(3, getBucketResponse.getCommonPrefixes().size()); assertEquals("file2", getBucketResponse.getContents().get(0) @@ -206,7 +206,7 @@ public void listWithPrefixAndDelimiter2() throws OS3Exception, IOException { getBucket.setRequestIdentifier(new RequestIdentifier()); ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, "dir1bh", - null, "dir1/dir2/file2", null, null, null, null, 0, null).getEntity(); + null, "dir1/dir2/file2", null, null, null, null, 0).getEntity(); assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -226,7 +226,7 @@ public void listWithPrefixAndEmptyStrDelimiter() // Should behave the same if delimiter is null ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "", null, null, 100, "dir1/", - null, null, null, null, null, null, 0, null).getEntity(); + null, null, null, null, null, null, 0).getEntity(); assertEquals(0, getBucketResponse.getCommonPrefixes().size()); assertEquals(4, getBucketResponse.getContents().size()); @@ -258,7 +258,7 @@ public void listWithContinuationToken() throws OS3Exception, IOException { // First time ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", null, null, null, maxKeys, - "", null, null, null, null, null, null, 0, null).getEntity(); + "", null, null, null, null, null, null, 0).getEntity(); assertTrue(getBucketResponse.isTruncated()); assertEquals(2, getBucketResponse.getContents().size()); @@ -267,7 +267,7 @@ public void listWithContinuationToken() throws OS3Exception, IOException { String continueToken = getBucketResponse.getNextToken(); getBucketResponse = (ListObjectResponse) getBucket.get("b1", null, null, null, maxKeys, - "", continueToken, null, null, null, null, null, 0, null).getEntity(); + "", continueToken, null, null, null, null, null, 0).getEntity(); assertTrue(getBucketResponse.isTruncated()); assertEquals(2, getBucketResponse.getContents().size()); @@ -277,7 +277,7 @@ public void listWithContinuationToken() throws OS3Exception, IOException { //3rd time getBucketResponse = (ListObjectResponse) getBucket.get("b1", null, null, null, maxKeys, - "", continueToken, null, null, null, null, null, 0, null).getEntity(); + "", continueToken, null, null, null, null, null, 0).getEntity(); assertFalse(getBucketResponse.isTruncated()); assertEquals(1, getBucketResponse.getContents().size()); @@ -310,7 +310,7 @@ public void listWithContinuationTokenDirBreak() getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, - "test/", null, null, null, null, null, null, 0, null).getEntity(); + "test/", null, null, null, null, null, null, 0).getEntity(); assertEquals(0, getBucketResponse.getContents().size()); assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -322,7 +322,7 @@ public void listWithContinuationTokenDirBreak() getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, "test/", getBucketResponse.getNextToken(), null, null, null, - null, null, 0, null).getEntity(); + null, null, 0).getEntity(); assertEquals(1, getBucketResponse.getContents().size()); assertEquals(1, getBucketResponse.getCommonPrefixes().size()); assertEquals("test/dir3/", @@ -354,7 +354,7 @@ public void listWithContinuationToken1() throws OS3Exception, IOException { // First time ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, - "dir", null, null, null, null, null, null, 0, null).getEntity(); + "dir", null, null, null, null, null, null, 0).getEntity(); assertTrue(getBucketResponse.isTruncated()); assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -363,7 +363,7 @@ public void listWithContinuationToken1() throws OS3Exception, IOException { String continueToken = getBucketResponse.getNextToken(); getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, - "dir", continueToken, null, null, null, null, null, 0, null).getEntity(); + "dir", continueToken, null, null, null, null, null, 0).getEntity(); assertTrue(getBucketResponse.isTruncated()); assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -371,7 +371,7 @@ public void listWithContinuationToken1() throws OS3Exception, IOException { continueToken = getBucketResponse.getNextToken(); getBucketResponse = (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, - "dir", continueToken, null, null, null, null, null, 0, null).getEntity(); + "dir", continueToken, null, null, null, null, null, 0).getEntity(); assertFalse(getBucketResponse.isTruncated()); assertEquals(1, getBucketResponse.getCommonPrefixes().size()); @@ -391,7 +391,7 @@ public void listWithContinuationTokenFail() throws IOException { getBucket.setRequestIdentifier(new RequestIdentifier()); OS3Exception e = assertThrows(OS3Exception.class, () -> getBucket.get("b1", - "/", null, null, 2, "dir", "random", null, null, null, null, null, 1000, null) + "/", null, null, 2, "dir", "random", null, null, null, null, null, 1000) .getEntity(), "listWithContinuationTokenFail"); assertEquals("random", e.getResource()); assertEquals("Invalid Argument", e.getErrorMessage()); @@ -410,7 +410,7 @@ public void testStartAfter() throws IOException, OS3Exception { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket.get("b1", null, null, null, 1000, - null, null, null, null, null, null, null, 0, null).getEntity(); + null, null, null, null, null, null, null, 0).getEntity(); assertFalse(getBucketResponse.isTruncated()); assertEquals(5, getBucketResponse.getContents().size()); @@ -421,14 +421,14 @@ public void testStartAfter() throws IOException, OS3Exception { getBucketResponse = (ListObjectResponse) getBucket.get("b1", null, null, null, - 1000, null, null, startAfter, null, null, null, null, 0, null).getEntity(); + 1000, null, null, startAfter, null, null, null, null, 0).getEntity(); assertFalse(getBucketResponse.isTruncated()); assertEquals(4, getBucketResponse.getContents().size()); getBucketResponse = (ListObjectResponse) getBucket.get("b1", null, null, null, - 1000, null, null, "random", null, null, null, null, 0, null).getEntity(); + 1000, null, null, "random", null, null, null, null, 0).getEntity(); assertFalse(getBucketResponse.isTruncated()); assertEquals(0, getBucketResponse.getContents().size()); @@ -475,7 +475,7 @@ public void testEncodingType() throws IOException, OS3Exception { ListObjectResponse response = (ListObjectResponse) getBucket.get( "b1", delimiter, encodingType, null, 1000, prefix, - null, startAfter, null, null, null, null, 0, null).getEntity(); + null, startAfter, null, null, null, null, 0).getEntity(); // Assert encodingType == url. // The Object name will be encoded by ObjectKeyNameAdapter @@ -493,7 +493,7 @@ public void testEncodingType() throws IOException, OS3Exception { response = (ListObjectResponse) getBucket.get( "b1", delimiter, null, null, 1000, prefix, - null, startAfter, null, null, null, null, 0, null).getEntity(); + null, startAfter, null, null, null, null, 0).getEntity(); // Assert encodingType == null. // The Object name will not be encoded by ObjectKeyNameAdapter @@ -518,13 +518,14 @@ public void testEncodingTypeException() throws IOException { getBucket.setRequestIdentifier(new RequestIdentifier()); OS3Exception e = assertThrows(OS3Exception.class, () -> getBucket.get( "b1", null, "unSupportType", null, 1000, null, - null, null, null, null, null, null, 0, null).getEntity()); + null, null, null, null, null, null, 0).getEntity()); assertEquals(S3ErrorTable.INVALID_ARGUMENT.getCode(), e.getCode()); } @Test public void testListObjectsWithInvalidMaxKeys() throws Exception { OzoneClient client = createClientWithKeys("file1"); + client.getObjectStore().createS3Bucket("bucket"); BucketEndpoint bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() .setClient(client) .build(); @@ -532,14 +533,14 @@ public void testListObjectsWithInvalidMaxKeys() throws Exception { // maxKeys < 0 OS3Exception e1 = assertThrows(OS3Exception.class, () -> bucketEndpoint.get("bucket", null, null, null, -1, null, - null, null, null, null, null, null, 1000, null) + null, null, null, null, null, null, 1000) ); assertEquals(S3ErrorTable.INVALID_ARGUMENT.getCode(), e1.getCode()); // maxKeys == 0 OS3Exception e2 = assertThrows(OS3Exception.class, () -> bucketEndpoint.get("bucket", null, null, null, 0, null, - null, null, null, null, null, null, 1000, null) + null, null, null, null, null, null, 1000) ); assertEquals(S3ErrorTable.INVALID_ARGUMENT.getCode(), e2.getCode()); } @@ -571,7 +572,7 @@ public void testListObjectsRespectsConfiguredMaxKeysLimit() throws Exception { ListObjectResponse response = (ListObjectResponse) bucketEndpoint.get("b1", null, null, null, requestedMaxKeys, null, null, null, null, null, null, null, - 1000, null).getEntity(); + 1000).getEntity(); // Assert: The number of returned keys should be capped at the configured limit assertEquals(Integer.parseInt(configuredMaxKeysLimit), response.getContents().size()); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java index 6f5d4d5d13bd..00543678234a 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java @@ -57,7 +57,7 @@ public void setup() throws Exception { @Test public void testBucketFailWithAuthHeaderMissing() throws Exception { try { - bucketEndpoint.put(bucketName, null, null, null); + bucketEndpoint.put(bucketName, null, null); } catch (OS3Exception ex) { assertEquals(HTTP_NOT_FOUND, ex.getHttpCode()); assertEquals(MALFORMED_HEADER.getCode(), ex.getCode()); @@ -66,13 +66,13 @@ public void testBucketFailWithAuthHeaderMissing() throws Exception { @Test public void testBucketPut() throws Exception { - Response response = bucketEndpoint.put(bucketName, null, null, null); + Response response = bucketEndpoint.put(bucketName, null, null); assertEquals(200, response.getStatus()); assertNotNull(response.getLocation()); // Create-bucket on an existing bucket fails OS3Exception e = assertThrows(OS3Exception.class, () -> bucketEndpoint.put( - bucketName, null, null, null)); + bucketName, null, null)); assertEquals(HTTP_CONFLICT, e.getHttpCode()); assertEquals(BUCKET_ALREADY_EXISTS.getCode(), e.getCode()); } @@ -80,7 +80,7 @@ public void testBucketPut() throws Exception { @Test public void testBucketFailWithInvalidHeader() throws Exception { try { - bucketEndpoint.put(bucketName, null, null, null); + bucketEndpoint.put(bucketName, null, null); } catch (OS3Exception ex) { assertEquals(HTTP_NOT_FOUND, ex.getHttpCode()); assertEquals(MALFORMED_HEADER.getCode(), ex.getCode()); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java index 9b34b6ec86ac..81f6853bf73f 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java @@ -130,13 +130,14 @@ public void testCreateBucket() throws IOException { .setClient(client) .build(); OS3Exception e = assertThrows(OS3Exception.class, () -> - bucketEndpoint.put("bucketName", null, null, null)); + bucketEndpoint.put("bucketName", null, null)); assertEquals(HTTP_FORBIDDEN, e.getHttpCode()); } @Test public void testDeleteBucket() throws IOException { doThrow(exception).when(objectStore).deleteS3Bucket(anyString()); + when(objectStore.getS3Bucket(anyString())).thenReturn(bucket); BucketEndpoint bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() .setClient(client) .build(); @@ -168,7 +169,7 @@ public void testListKey() throws IOException { .build(); OS3Exception e = assertThrows(OS3Exception.class, () -> bucketEndpoint.get( "bucketName", null, null, null, 1000, - null, null, null, null, null, null, null, 0, null)); + null, null, null, null, null, null, null, 0)); assertEquals(HTTP_FORBIDDEN, e.getHttpCode()); } @@ -210,10 +211,11 @@ public void testGetAcl() throws Exception { .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); BucketEndpoint bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() .setClient(client) + .setHeaders(headers) .build(); OS3Exception e = assertThrows(OS3Exception.class, () -> bucketEndpoint.get( "bucketName", null, null, null, 1000, null, null, null, null, "acl", - null, null, 0, null), "Expected OS3Exception with FORBIDDEN http code."); + null, null, 0), "Expected OS3Exception with FORBIDDEN http code."); assertEquals(HTTP_FORBIDDEN, e.getHttpCode()); } @@ -232,9 +234,10 @@ public void testSetAcl() throws Exception { .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); BucketEndpoint bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() .setClient(client) + .setHeaders(headers) .build(); try { - bucketEndpoint.put("bucketName", "acl", headers, null); + bucketEndpoint.put("bucketName", "acl", null); } catch (Exception e) { assertTrue(e instanceof OS3Exception && ((OS3Exception)e).getHttpCode() == HTTP_FORBIDDEN); @@ -247,6 +250,7 @@ public void testSetAcl() throws Exception { @Test public void testGetKey() throws IOException { when(client.getProxy()).thenReturn(clientProtocol); + when(objectStore.getS3Bucket(anyString())).thenReturn(bucket); doThrow(exception).when(clientProtocol) .getS3KeyDetails(anyString(), anyString()); ObjectEndpoint objectEndpoint = EndpointBuilder.newObjectEndpointBuilder() @@ -281,6 +285,7 @@ public void testPutKey() throws IOException { @Test public void testDeleteKey() throws IOException { when(objectStore.getS3Volume()).thenReturn(volume); + when(volume.getBucket(anyString())).thenReturn(bucket); doThrow(exception).when(clientProtocol).deleteKey(anyString(), anyString(), anyString(), anyBoolean()); ObjectEndpoint objectEndpoint = EndpointBuilder.newObjectEndpointBuilder() diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3Owner.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3Owner.java new file mode 100644 index 000000000000..c47759e6369f --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3Owner.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.ozone.s3.endpoint; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.ws.rs.core.HttpHeaders; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.s3.util.S3Consts; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +/** + * Unit test class for testing logic related to TestS3Owner. + */ +public class TestS3Owner { + + private static final String SOURCE_BUCKET_NAME = "source-bucket"; + private static final String DEST_BUCKET_NAME = "dest-bucket"; + private HttpHeaders headers; + + @BeforeEach + public void setup() { + headers = mock(HttpHeaders.class); + } + + @Test + public void testHeaderIsNull() { + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerCondition(null, SOURCE_BUCKET_NAME, "test")); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(null, SOURCE_BUCKET_NAME, "test", + SOURCE_BUCKET_NAME, "test")); + } + + @Test + public void testServerBucketOwnerIsNull() { + when(headers.getHeaderString(S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER)).thenReturn("test"); + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("test"); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerCondition(headers, SOURCE_BUCKET_NAME, null)); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, null, + SOURCE_BUCKET_NAME, "test")); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, "test", + SOURCE_BUCKET_NAME, null)); + } + + @ParameterizedTest + @NullAndEmptySource + public void testS3OwnerNotEnable(String bucketOwnerHeader) { + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(bucketOwnerHeader); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerCondition(headers, SOURCE_BUCKET_NAME, "test")); + + when(headers.getHeaderString(S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER)).thenReturn(bucketOwnerHeader); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(null, SOURCE_BUCKET_NAME, "test", + SOURCE_BUCKET_NAME, "test")); + } + + @Test + public void testClientBucketOwnerIsNull() { + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerCondition(headers, SOURCE_BUCKET_NAME, null)); + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("test"); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, null, + SOURCE_BUCKET_NAME, "test")); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, "test", + SOURCE_BUCKET_NAME, null)); + } + + @Test + public void testPassExpectedBucketOwner() { + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("test"); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerCondition(headers, SOURCE_BUCKET_NAME, "test")); + } + + @Test + public void testFailExpectedBucketOwner() { + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("wrong"); + OS3Exception exception = + assertThrows(OS3Exception.class, () -> S3Owner.verifyBucketOwnerCondition(headers, SOURCE_BUCKET_NAME, "test")); + assertThat(exception.getErrorMessage()).isEqualTo(S3ErrorTable.BUCKET_OWNER_MISMATCH.getErrorMessage()); + } + + @Test + public void testCopyOperationPass() { + when(headers.getHeaderString(S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER)).thenReturn("source"); + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("dest"); + assertDoesNotThrow(() -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, "source", + SOURCE_BUCKET_NAME, "dest")); + } + + @Test + public void testCopyOperationFailedOnSourceBucketOwner() { + when(headers.getHeaderString(S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER)).thenReturn("source"); + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("dest"); + OS3Exception exception = + assertThrows(OS3Exception.class, + () -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, "wrong", + DEST_BUCKET_NAME, "dest")); + assertThat(exception.getErrorMessage()).isEqualTo(S3ErrorTable.BUCKET_OWNER_MISMATCH.getErrorMessage()); + assertThat(exception.getResource()).isEqualTo(SOURCE_BUCKET_NAME); + } + + @Test + public void testCopyOperationFailedOnDestBucketOwner() { + when(headers.getHeaderString(S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER)).thenReturn("source"); + when(headers.getHeaderString(S3Consts.EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("dest"); + OS3Exception exception = + assertThrows(OS3Exception.class, + () -> S3Owner.verifyBucketOwnerConditionOnCopyOperation(headers, SOURCE_BUCKET_NAME, "source", + DEST_BUCKET_NAME, "wrong")); + assertThat(exception.getErrorMessage()).isEqualTo(S3ErrorTable.BUCKET_OWNER_MISMATCH.getErrorMessage()); + assertThat(exception.getResource()).isEqualTo(DEST_BUCKET_NAME); + } +} diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java index 63465ef7552e..8f570252d228 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java @@ -145,7 +145,7 @@ public void testGetBucketSuccess() throws Exception { bucketEndpoint.get(bucketName, null, null, null, 1000, null, null, "random", null, - null, null, null, 0, null).getEntity(); + null, null, null, 0).getEntity(); long curMetric = metrics.getGetBucketSuccess(); assertEquals(1L, curMetric - oriMetric); @@ -158,7 +158,7 @@ public void testGetBucketFailure() throws Exception { // Searching for a bucket that does not exist OS3Exception e = assertThrows(OS3Exception.class, () -> bucketEndpoint.get( "newBucket", null, null, null, 1000, null, null, "random", null, - null, null, null, 0, null)); + null, null, null, 0)); assertEquals(S3ErrorTable.NO_SUCH_BUCKET.getCode(), e.getCode()); assertEquals(S3ErrorTable.NO_SUCH_BUCKET.getErrorMessage(), e.getErrorMessage()); @@ -170,7 +170,7 @@ public void testGetBucketFailure() throws Exception { public void testCreateBucketSuccess() throws Exception { long oriMetric = metrics.getCreateBucketSuccess(); - assertDoesNotThrow(() -> bucketEndpoint.put("newBucket", null, null, null)); + assertDoesNotThrow(() -> bucketEndpoint.put("newBucket", null, null)); long curMetric = metrics.getCreateBucketSuccess(); assertEquals(1L, curMetric - oriMetric); } @@ -181,7 +181,7 @@ public void testCreateBucketFailure() throws Exception { // Creating an error by trying to create a bucket that already exists OS3Exception e = assertThrows(OS3Exception.class, () -> bucketEndpoint.put( - bucketName, null, null, null)); + bucketName, null, null)); assertEquals(HTTP_CONFLICT, e.getHttpCode()); assertEquals(BUCKET_ALREADY_EXISTS.getCode(), e.getCode()); @@ -222,7 +222,7 @@ public void testGetAclSuccess() throws Exception { Response response = bucketEndpoint.get(bucketName, null, null, null, 0, null, null, - null, null, "acl", null, null, 0, null); + null, null, "acl", null, null, 0); long curMetric = metrics.getGetAclSuccess(); assertEquals(HTTP_OK, response.getStatus()); assertEquals(1L, curMetric - oriMetric); @@ -235,7 +235,7 @@ public void testGetAclFailure() throws Exception { // Failing the getACL endpoint by applying ACL on a non-Existent Bucket OS3Exception e = assertThrows(OS3Exception.class, () -> bucketEndpoint.get( "random_bucket", null, null, null, 0, null, - null, null, null, "acl", null, null, 0, null)); + null, null, null, "acl", null, null, 0)); assertEquals(S3ErrorTable.NO_SUCH_BUCKET.getCode(), e.getCode()); assertEquals(S3ErrorTable.NO_SUCH_BUCKET.getErrorMessage(), e.getErrorMessage()); @@ -251,7 +251,7 @@ public void testPutAclSuccess() throws Exception { InputStream inputBody = TestBucketAcl.class.getClassLoader() .getResourceAsStream("userAccessControlList.xml"); - bucketEndpoint.put("b1", ACL_MARKER, headers, inputBody); + bucketEndpoint.put("b1", ACL_MARKER, inputBody); inputBody.close(); long curMetric = metrics.getPutAclSuccess(); assertEquals(1L, curMetric - oriMetric); @@ -265,7 +265,7 @@ public void testPutAclFailure() throws Exception { InputStream inputBody = TestBucketAcl.class.getClassLoader() .getResourceAsStream("userAccessControlList.xml"); try { - assertThrows(OS3Exception.class, () -> bucketEndpoint.put("unknown_bucket", ACL_MARKER, headers, + assertThrows(OS3Exception.class, () -> bucketEndpoint.put("unknown_bucket", ACL_MARKER, inputBody)); } finally { inputBody.close();