| /* |
| * 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.beam.sdk.io.aws.s3; |
| |
| import static org.apache.beam.sdk.io.aws.s3.S3TestUtils.buildMockedS3FileSystem; |
| import static org.apache.beam.sdk.io.aws.s3.S3TestUtils.getSSECustomerKeyMd5; |
| import static org.apache.beam.sdk.io.aws.s3.S3TestUtils.s3Options; |
| import static org.apache.beam.sdk.io.aws.s3.S3TestUtils.s3OptionsWithCustomEndpointAndPathStyleAccessEnabled; |
| import static org.apache.beam.sdk.io.aws.s3.S3TestUtils.s3OptionsWithSSECustomerKey; |
| import static org.apache.beam.sdk.io.fs.CreateOptions.StandardCreateOptions.builder; |
| import static org.apache.beam.vendor.guava.v20_0.com.google.common.base.Preconditions.checkNotNull; |
| import static org.hamcrest.Matchers.contains; |
| import static org.hamcrest.Matchers.notNullValue; |
| import static org.hamcrest.Matchers.nullValue; |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertThat; |
| import static org.mockito.Matchers.anyObject; |
| import static org.mockito.Matchers.argThat; |
| import static org.mockito.Matchers.notNull; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import akka.http.scaladsl.Http; |
| import com.amazonaws.auth.AWSStaticCredentialsProvider; |
| import com.amazonaws.auth.AnonymousAWSCredentials; |
| import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; |
| import com.amazonaws.services.s3.AmazonS3; |
| import com.amazonaws.services.s3.AmazonS3ClientBuilder; |
| import com.amazonaws.services.s3.model.AmazonS3Exception; |
| import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest; |
| import com.amazonaws.services.s3.model.CopyObjectRequest; |
| import com.amazonaws.services.s3.model.CopyObjectResult; |
| import com.amazonaws.services.s3.model.CopyPartRequest; |
| import com.amazonaws.services.s3.model.CopyPartResult; |
| import com.amazonaws.services.s3.model.DeleteObjectsRequest; |
| import com.amazonaws.services.s3.model.GetObjectMetadataRequest; |
| import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; |
| import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; |
| import com.amazonaws.services.s3.model.ListObjectsV2Request; |
| import com.amazonaws.services.s3.model.ListObjectsV2Result; |
| import com.amazonaws.services.s3.model.ObjectMetadata; |
| import com.amazonaws.services.s3.model.S3ObjectSummary; |
| import io.findify.s3mock.S3Mock; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.ReadableByteChannel; |
| import java.nio.channels.WritableByteChannel; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.List; |
| import org.apache.beam.sdk.io.aws.options.S3Options; |
| import org.apache.beam.sdk.io.fs.MatchResult; |
| import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableList; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| import org.mockito.ArgumentMatcher; |
| |
| /** Test case for {@link S3FileSystem}. */ |
| @RunWith(JUnit4.class) |
| public class S3FileSystemTest { |
| private static S3Mock api; |
| private static AmazonS3 client; |
| |
| @BeforeClass |
| public static void beforeClass() { |
| api = new S3Mock.Builder().withInMemoryBackend().build(); |
| Http.ServerBinding binding = api.start(); |
| |
| EndpointConfiguration endpoint = |
| new EndpointConfiguration( |
| "http://localhost:" + binding.localAddress().getPort(), "us-west-2"); |
| client = |
| AmazonS3ClientBuilder.standard() |
| .withPathStyleAccessEnabled(true) |
| .withEndpointConfiguration(endpoint) |
| .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) |
| .build(); |
| } |
| |
| @AfterClass |
| public static void afterClass() { |
| api.stop(); |
| } |
| |
| @Test |
| public void testGlobTranslation() { |
| assertEquals("foo", S3FileSystem.wildcardToRegexp("foo")); |
| assertEquals("fo[^/]*o", S3FileSystem.wildcardToRegexp("fo*o")); |
| assertEquals("f[^/]*o\\.[^/]", S3FileSystem.wildcardToRegexp("f*o.?")); |
| assertEquals("foo-[0-9][^/]*", S3FileSystem.wildcardToRegexp("foo-[0-9]*")); |
| assertEquals("foo-[0-9].*", S3FileSystem.wildcardToRegexp("foo-[0-9]**")); |
| assertEquals(".*foo", S3FileSystem.wildcardToRegexp("**/*foo")); |
| assertEquals(".*foo", S3FileSystem.wildcardToRegexp("**foo")); |
| assertEquals("foo/[^/]*", S3FileSystem.wildcardToRegexp("foo/*")); |
| assertEquals("foo[^/]*", S3FileSystem.wildcardToRegexp("foo*")); |
| assertEquals("foo/[^/]*/[^/]*/[^/]*", S3FileSystem.wildcardToRegexp("foo/*/*/*")); |
| assertEquals("foo/[^/]*/.*", S3FileSystem.wildcardToRegexp("foo/*/**")); |
| assertEquals("foo.*baz", S3FileSystem.wildcardToRegexp("foo**baz")); |
| } |
| |
| @Test |
| public void testGetScheme() { |
| S3FileSystem s3FileSystem = new S3FileSystem(s3Options()); |
| assertEquals("s3", s3FileSystem.getScheme()); |
| } |
| |
| @Test |
| public void testGetPathStyleAccessEnabled() throws URISyntaxException { |
| S3FileSystem s3FileSystem = |
| new S3FileSystem(s3OptionsWithCustomEndpointAndPathStyleAccessEnabled()); |
| URL s3Url = s3FileSystem.getAmazonS3Client().getUrl("bucket", "file"); |
| assertEquals("https://s3.custom.dns/bucket/file", s3Url.toURI().toString()); |
| } |
| |
| @Test |
| public void testCopy() throws IOException { |
| testCopy(s3Options()); |
| testCopy(s3OptionsWithSSECustomerKey()); |
| } |
| |
| private GetObjectMetadataRequest createObjectMetadataRequest( |
| S3ResourceId path, S3Options options) { |
| GetObjectMetadataRequest getObjectMetadataRequest = |
| new GetObjectMetadataRequest(path.getBucket(), path.getKey()); |
| getObjectMetadataRequest.setSSECustomerKey(options.getSSECustomerKey()); |
| return getObjectMetadataRequest; |
| } |
| |
| private void assertGetObjectMetadata( |
| S3FileSystem s3FileSystem, |
| GetObjectMetadataRequest request, |
| S3Options options, |
| ObjectMetadata objectMetadata) { |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata(argThat(new GetObjectMetadataRequestMatcher(request)))) |
| .thenReturn(objectMetadata); |
| assertEquals( |
| getSSECustomerKeyMd5(options), |
| s3FileSystem.getAmazonS3Client().getObjectMetadata(request).getSSECustomerKeyMd5()); |
| } |
| |
| private void testCopy(S3Options options) throws IOException { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId sourcePath = S3ResourceId.fromUri("s3://bucket/from"); |
| S3ResourceId destinationPath = S3ResourceId.fromUri("s3://bucket/to"); |
| |
| ObjectMetadata objectMetadata = new ObjectMetadata(); |
| objectMetadata.setContentLength(0); |
| if (getSSECustomerKeyMd5(options) != null) { |
| objectMetadata.setSSECustomerKeyMd5(getSSECustomerKeyMd5(options)); |
| } |
| assertGetObjectMetadata( |
| s3FileSystem, createObjectMetadataRequest(sourcePath, options), options, objectMetadata); |
| |
| s3FileSystem.copy(sourcePath, destinationPath); |
| |
| verify(s3FileSystem.getAmazonS3Client(), times(1)) |
| .copyObject(argThat(notNullValue(CopyObjectRequest.class))); |
| |
| // we simulate a big object >= 5GB so it takes the multiPart path |
| objectMetadata.setContentLength(5_368_709_120L); |
| assertGetObjectMetadata( |
| s3FileSystem, createObjectMetadataRequest(sourcePath, options), options, objectMetadata); |
| |
| try { |
| s3FileSystem.copy(sourcePath, destinationPath); |
| } catch (NullPointerException e) { |
| // ignore failing unmocked path, this is covered by testMultipartCopy test |
| } |
| |
| verify(s3FileSystem.getAmazonS3Client(), never()) |
| .copyObject(argThat(nullValue(CopyObjectRequest.class))); |
| } |
| |
| @Test |
| public void testAtomicCopy() { |
| testAtomicCopy(s3Options()); |
| testAtomicCopy(s3OptionsWithSSECustomerKey()); |
| } |
| |
| private void testAtomicCopy(S3Options options) { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(options); |
| |
| S3ResourceId sourcePath = S3ResourceId.fromUri("s3://bucket/from"); |
| S3ResourceId destinationPath = S3ResourceId.fromUri("s3://bucket/to"); |
| |
| CopyObjectResult copyObjectResult = new CopyObjectResult(); |
| if (getSSECustomerKeyMd5(options) != null) { |
| copyObjectResult.setSSECustomerKeyMd5(getSSECustomerKeyMd5(options)); |
| } |
| CopyObjectRequest copyObjectRequest = |
| new CopyObjectRequest( |
| sourcePath.getBucket(), |
| sourcePath.getKey(), |
| destinationPath.getBucket(), |
| destinationPath.getKey()); |
| copyObjectRequest.setSourceSSECustomerKey(options.getSSECustomerKey()); |
| copyObjectRequest.setDestinationSSECustomerKey(options.getSSECustomerKey()); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .copyObject(argThat(notNullValue(CopyObjectRequest.class)))) |
| .thenReturn(copyObjectResult); |
| assertEquals( |
| getSSECustomerKeyMd5(options), |
| s3FileSystem.getAmazonS3Client().copyObject(copyObjectRequest).getSSECustomerKeyMd5()); |
| |
| ObjectMetadata sourceS3ObjectMetadata = new ObjectMetadata(); |
| s3FileSystem.atomicCopy(sourcePath, destinationPath, sourceS3ObjectMetadata); |
| |
| verify(s3FileSystem.getAmazonS3Client(), times(2)) |
| .copyObject(argThat(notNullValue(CopyObjectRequest.class))); |
| } |
| |
| @Test |
| public void testMultipartCopy() { |
| testMultipartCopy(s3Options()); |
| testMultipartCopy(s3OptionsWithSSECustomerKey()); |
| } |
| |
| private void testMultipartCopy(S3Options options) { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(options); |
| |
| S3ResourceId sourcePath = S3ResourceId.fromUri("s3://bucket/from"); |
| S3ResourceId destinationPath = S3ResourceId.fromUri("s3://bucket/to"); |
| |
| InitiateMultipartUploadResult initiateMultipartUploadResult = |
| new InitiateMultipartUploadResult(); |
| initiateMultipartUploadResult.setUploadId("upload-id"); |
| if (getSSECustomerKeyMd5(options) != null) { |
| initiateMultipartUploadResult.setSSECustomerKeyMd5(getSSECustomerKeyMd5(options)); |
| } |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .initiateMultipartUpload(argThat(notNullValue(InitiateMultipartUploadRequest.class)))) |
| .thenReturn(initiateMultipartUploadResult); |
| assertEquals( |
| getSSECustomerKeyMd5(options), |
| s3FileSystem |
| .getAmazonS3Client() |
| .initiateMultipartUpload( |
| new InitiateMultipartUploadRequest( |
| destinationPath.getBucket(), destinationPath.getKey())) |
| .getSSECustomerKeyMd5()); |
| |
| ObjectMetadata sourceObjectMetadata = new ObjectMetadata(); |
| sourceObjectMetadata.setContentLength((long) (options.getS3UploadBufferSizeBytes() * 1.5)); |
| sourceObjectMetadata.setContentEncoding("read-seek-efficient"); |
| if (getSSECustomerKeyMd5(options) != null) { |
| sourceObjectMetadata.setSSECustomerKeyMd5(getSSECustomerKeyMd5(options)); |
| } |
| assertGetObjectMetadata( |
| s3FileSystem, |
| createObjectMetadataRequest(sourcePath, options), |
| options, |
| sourceObjectMetadata); |
| |
| CopyPartResult copyPartResult1 = new CopyPartResult(); |
| copyPartResult1.setETag("etag-1"); |
| CopyPartResult copyPartResult2 = new CopyPartResult(); |
| copyPartResult1.setETag("etag-2"); |
| if (getSSECustomerKeyMd5(options) != null) { |
| copyPartResult1.setSSECustomerKeyMd5(getSSECustomerKeyMd5(options)); |
| copyPartResult2.setSSECustomerKeyMd5(getSSECustomerKeyMd5(options)); |
| } |
| CopyPartRequest copyPartRequest = new CopyPartRequest(); |
| copyPartRequest.setSourceSSECustomerKey(options.getSSECustomerKey()); |
| when(s3FileSystem.getAmazonS3Client().copyPart(argThat(notNullValue(CopyPartRequest.class)))) |
| .thenReturn(copyPartResult1) |
| .thenReturn(copyPartResult2); |
| assertEquals( |
| getSSECustomerKeyMd5(options), |
| s3FileSystem.getAmazonS3Client().copyPart(copyPartRequest).getSSECustomerKeyMd5()); |
| |
| s3FileSystem.multipartCopy(sourcePath, destinationPath, sourceObjectMetadata); |
| |
| verify(s3FileSystem.getAmazonS3Client(), times(1)) |
| .completeMultipartUpload(argThat(notNullValue(CompleteMultipartUploadRequest.class))); |
| } |
| |
| @Test |
| public void deleteThousandsOfObjectsInMultipleBuckets() throws IOException { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| List<String> buckets = ImmutableList.of("bucket1", "bucket2"); |
| List<String> keys = new ArrayList<>(); |
| for (int i = 0; i < 2500; i++) { |
| keys.add(String.format("key-%d", i)); |
| } |
| List<S3ResourceId> paths = new ArrayList<>(); |
| for (String bucket : buckets) { |
| for (String key : keys) { |
| paths.add(S3ResourceId.fromComponents(bucket, key)); |
| } |
| } |
| |
| s3FileSystem.delete(paths); |
| |
| // Should require 6 calls to delete 2500 objects in each of 2 buckets. |
| verify(s3FileSystem.getAmazonS3Client(), times(6)) |
| .deleteObjects(argThat(notNullValue(DeleteObjectsRequest.class))); |
| } |
| |
| @Test |
| public void matchNonGlob() { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/testdirectory/filethatexists"); |
| long lastModifiedMillis = 1540000000000L; |
| ObjectMetadata s3ObjectMetadata = new ObjectMetadata(); |
| s3ObjectMetadata.setContentLength(100); |
| s3ObjectMetadata.setContentEncoding("read-seek-efficient"); |
| s3ObjectMetadata.setLastModified(new Date(lastModifiedMillis)); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(path.getBucket(), path.getKey()))))) |
| .thenReturn(s3ObjectMetadata); |
| |
| MatchResult result = s3FileSystem.matchNonGlobPath(path); |
| assertThat( |
| result, |
| MatchResultMatcher.create( |
| ImmutableList.of( |
| MatchResult.Metadata.builder() |
| .setSizeBytes(100) |
| .setLastModifiedMillis(lastModifiedMillis) |
| .setResourceId(path) |
| .setIsReadSeekEfficient(true) |
| .build()))); |
| } |
| |
| @Test |
| public void matchNonGlobNotReadSeekEfficient() { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/testdirectory/filethatexists"); |
| long lastModifiedMillis = 1540000000000L; |
| ObjectMetadata s3ObjectMetadata = new ObjectMetadata(); |
| s3ObjectMetadata.setContentLength(100); |
| s3ObjectMetadata.setLastModified(new Date(lastModifiedMillis)); |
| s3ObjectMetadata.setContentEncoding("gzip"); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(path.getBucket(), path.getKey()))))) |
| .thenReturn(s3ObjectMetadata); |
| |
| MatchResult result = s3FileSystem.matchNonGlobPath(path); |
| assertThat( |
| result, |
| MatchResultMatcher.create( |
| ImmutableList.of( |
| MatchResult.Metadata.builder() |
| .setSizeBytes(100) |
| .setLastModifiedMillis(lastModifiedMillis) |
| .setResourceId(path) |
| .setIsReadSeekEfficient(false) |
| .build()))); |
| } |
| |
| @Test |
| public void matchNonGlobNullContentEncoding() { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/testdirectory/filethatexists"); |
| long lastModifiedMillis = 1540000000000L; |
| ObjectMetadata s3ObjectMetadata = new ObjectMetadata(); |
| s3ObjectMetadata.setContentLength(100); |
| s3ObjectMetadata.setLastModified(new Date(lastModifiedMillis)); |
| s3ObjectMetadata.setContentEncoding(null); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(path.getBucket(), path.getKey()))))) |
| .thenReturn(s3ObjectMetadata); |
| |
| MatchResult result = s3FileSystem.matchNonGlobPath(path); |
| assertThat( |
| result, |
| MatchResultMatcher.create( |
| ImmutableList.of( |
| MatchResult.Metadata.builder() |
| .setSizeBytes(100) |
| .setLastModifiedMillis(lastModifiedMillis) |
| .setResourceId(path) |
| .setIsReadSeekEfficient(true) |
| .build()))); |
| } |
| |
| @Test |
| public void matchNonGlobNotFound() { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/testdirectory/nonexistentfile"); |
| AmazonS3Exception exception = new AmazonS3Exception("mock exception"); |
| exception.setStatusCode(404); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(path.getBucket(), path.getKey()))))) |
| .thenThrow(exception); |
| |
| MatchResult result = s3FileSystem.matchNonGlobPath(path); |
| assertThat( |
| result, |
| MatchResultMatcher.create(MatchResult.Status.NOT_FOUND, new FileNotFoundException())); |
| } |
| |
| @Test |
| public void matchNonGlobForbidden() { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| AmazonS3Exception exception = new AmazonS3Exception("mock exception"); |
| exception.setStatusCode(403); |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/testdirectory/keyname"); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(path.getBucket(), path.getKey()))))) |
| .thenThrow(exception); |
| |
| assertThat( |
| s3FileSystem.matchNonGlobPath(path), |
| MatchResultMatcher.create(MatchResult.Status.ERROR, new IOException(exception))); |
| } |
| |
| static class ListObjectsV2RequestArgumentMatches extends ArgumentMatcher<ListObjectsV2Request> { |
| private final ListObjectsV2Request expected; |
| |
| ListObjectsV2RequestArgumentMatches(ListObjectsV2Request expected) { |
| this.expected = checkNotNull(expected); |
| } |
| |
| @Override |
| public boolean matches(Object argument) { |
| if (argument instanceof ListObjectsV2Request) { |
| ListObjectsV2Request actual = (ListObjectsV2Request) argument; |
| return expected.getBucketName().equals(actual.getBucketName()) |
| && expected.getPrefix().equals(actual.getPrefix()) |
| && (expected.getContinuationToken() == null |
| ? actual.getContinuationToken() == null |
| : expected.getContinuationToken().equals(actual.getContinuationToken())); |
| } |
| return false; |
| } |
| } |
| |
| @Test |
| public void matchGlob() throws IOException { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/foo/bar*baz"); |
| |
| ListObjectsV2Request firstRequest = |
| new ListObjectsV2Request() |
| .withBucketName(path.getBucket()) |
| .withPrefix(path.getKeyNonWildcardPrefix()) |
| .withContinuationToken(null); |
| |
| // Expected to be returned; prefix and wildcard/regex match |
| S3ObjectSummary firstMatch = new S3ObjectSummary(); |
| firstMatch.setBucketName(path.getBucket()); |
| firstMatch.setKey("foo/bar0baz"); |
| firstMatch.setSize(100); |
| firstMatch.setLastModified(new Date(1540000000001L)); |
| |
| // Expected to not be returned; prefix matches, but substring after wildcard does not |
| S3ObjectSummary secondMatch = new S3ObjectSummary(); |
| secondMatch.setBucketName(path.getBucket()); |
| secondMatch.setKey("foo/bar1qux"); |
| secondMatch.setSize(200); |
| secondMatch.setLastModified(new Date(1540000000002L)); |
| |
| // Expected first request returns continuation token |
| ListObjectsV2Result firstResult = new ListObjectsV2Result(); |
| firstResult.setNextContinuationToken("token"); |
| firstResult.getObjectSummaries().add(firstMatch); |
| firstResult.getObjectSummaries().add(secondMatch); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .listObjectsV2(argThat(new ListObjectsV2RequestArgumentMatches(firstRequest)))) |
| .thenReturn(firstResult); |
| |
| // Expect second request with continuation token |
| ListObjectsV2Request secondRequest = |
| new ListObjectsV2Request() |
| .withBucketName(path.getBucket()) |
| .withPrefix(path.getKeyNonWildcardPrefix()) |
| .withContinuationToken("token"); |
| |
| // Expected to be returned; prefix and wildcard/regex match |
| S3ObjectSummary thirdMatch = new S3ObjectSummary(); |
| thirdMatch.setBucketName(path.getBucket()); |
| thirdMatch.setKey("foo/bar2baz"); |
| thirdMatch.setSize(300); |
| thirdMatch.setLastModified(new Date(1540000000003L)); |
| |
| // Expected second request returns third prefix match and no continuation token |
| ListObjectsV2Result secondResult = new ListObjectsV2Result(); |
| secondResult.setNextContinuationToken(null); |
| secondResult.getObjectSummaries().add(thirdMatch); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .listObjectsV2(argThat(new ListObjectsV2RequestArgumentMatches(secondRequest)))) |
| .thenReturn(secondResult); |
| |
| // Expect object metadata queries for content encoding |
| ObjectMetadata metadata = new ObjectMetadata(); |
| metadata.setContentEncoding(""); |
| when(s3FileSystem.getAmazonS3Client().getObjectMetadata(anyObject())).thenReturn(metadata); |
| |
| assertThat( |
| s3FileSystem.matchGlobPaths(ImmutableList.of(path)).get(0), |
| MatchResultMatcher.create( |
| ImmutableList.of( |
| MatchResult.Metadata.builder() |
| .setIsReadSeekEfficient(true) |
| .setResourceId( |
| S3ResourceId.fromComponents( |
| firstMatch.getBucketName(), firstMatch.getKey())) |
| .setSizeBytes(firstMatch.getSize()) |
| .setLastModifiedMillis(firstMatch.getLastModified().getTime()) |
| .build(), |
| MatchResult.Metadata.builder() |
| .setIsReadSeekEfficient(true) |
| .setResourceId( |
| S3ResourceId.fromComponents( |
| thirdMatch.getBucketName(), thirdMatch.getKey())) |
| .setSizeBytes(thirdMatch.getSize()) |
| .setLastModifiedMillis(thirdMatch.getLastModified().getTime()) |
| .build()))); |
| } |
| |
| @Test |
| public void matchGlobWithSlashes() throws IOException { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/foo/bar\\baz*"); |
| |
| ListObjectsV2Request request = |
| new ListObjectsV2Request() |
| .withBucketName(path.getBucket()) |
| .withPrefix(path.getKeyNonWildcardPrefix()) |
| .withContinuationToken(null); |
| |
| // Expected to be returned; prefix and wildcard/regex match |
| S3ObjectSummary firstMatch = new S3ObjectSummary(); |
| firstMatch.setBucketName(path.getBucket()); |
| firstMatch.setKey("foo/bar\\baz0"); |
| firstMatch.setSize(100); |
| firstMatch.setLastModified(new Date(1540000000001L)); |
| |
| // Expected to not be returned; prefix matches, but substring after wildcard does not |
| S3ObjectSummary secondMatch = new S3ObjectSummary(); |
| secondMatch.setBucketName(path.getBucket()); |
| secondMatch.setKey("foo/bar/baz1"); |
| secondMatch.setSize(200); |
| secondMatch.setLastModified(new Date(1540000000002L)); |
| |
| // Expected first request returns continuation token |
| ListObjectsV2Result result = new ListObjectsV2Result(); |
| result.getObjectSummaries().add(firstMatch); |
| result.getObjectSummaries().add(secondMatch); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .listObjectsV2(argThat(new ListObjectsV2RequestArgumentMatches(request)))) |
| .thenReturn(result); |
| |
| // Expect object metadata queries for content encoding |
| ObjectMetadata metadata = new ObjectMetadata(); |
| metadata.setContentEncoding(""); |
| when(s3FileSystem.getAmazonS3Client().getObjectMetadata(anyObject())).thenReturn(metadata); |
| |
| assertThat( |
| s3FileSystem.matchGlobPaths(ImmutableList.of(path)).get(0), |
| MatchResultMatcher.create( |
| ImmutableList.of( |
| MatchResult.Metadata.builder() |
| .setIsReadSeekEfficient(true) |
| .setResourceId( |
| S3ResourceId.fromComponents( |
| firstMatch.getBucketName(), firstMatch.getKey())) |
| .setSizeBytes(firstMatch.getSize()) |
| .setLastModifiedMillis(firstMatch.getLastModified().getTime()) |
| .build()))); |
| } |
| |
| @Test |
| public void matchVariousInvokeThreadPool() throws IOException { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options()); |
| |
| AmazonS3Exception notFoundException = new AmazonS3Exception("mock exception"); |
| notFoundException.setStatusCode(404); |
| S3ResourceId pathNotExist = |
| S3ResourceId.fromUri("s3://testbucket/testdirectory/nonexistentfile"); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest( |
| pathNotExist.getBucket(), pathNotExist.getKey()))))) |
| .thenThrow(notFoundException); |
| |
| AmazonS3Exception forbiddenException = new AmazonS3Exception("mock exception"); |
| forbiddenException.setStatusCode(403); |
| S3ResourceId pathForbidden = |
| S3ResourceId.fromUri("s3://testbucket/testdirectory/forbiddenfile"); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest( |
| pathForbidden.getBucket(), pathForbidden.getKey()))))) |
| .thenThrow(forbiddenException); |
| |
| S3ResourceId pathExist = S3ResourceId.fromUri("s3://testbucket/testdirectory/filethatexists"); |
| ObjectMetadata s3ObjectMetadata = new ObjectMetadata(); |
| s3ObjectMetadata.setContentLength(100); |
| s3ObjectMetadata.setLastModified(new Date(1540000000000L)); |
| s3ObjectMetadata.setContentEncoding("not-gzip"); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(pathExist.getBucket(), pathExist.getKey()))))) |
| .thenReturn(s3ObjectMetadata); |
| |
| S3ResourceId pathGlob = S3ResourceId.fromUri("s3://testbucket/path/part*"); |
| |
| S3ObjectSummary foundListObject = new S3ObjectSummary(); |
| foundListObject.setBucketName(pathGlob.getBucket()); |
| foundListObject.setKey("path/part-0"); |
| foundListObject.setSize(200); |
| foundListObject.setLastModified(new Date(1541000000000L)); |
| |
| ListObjectsV2Result listObjectsResult = new ListObjectsV2Result(); |
| listObjectsResult.setNextContinuationToken(null); |
| listObjectsResult.getObjectSummaries().add(foundListObject); |
| when(s3FileSystem.getAmazonS3Client().listObjectsV2(notNull(ListObjectsV2Request.class))) |
| .thenReturn(listObjectsResult); |
| |
| ObjectMetadata metadata = new ObjectMetadata(); |
| metadata.setContentEncoding(""); |
| when(s3FileSystem |
| .getAmazonS3Client() |
| .getObjectMetadata( |
| argThat( |
| new GetObjectMetadataRequestMatcher( |
| new GetObjectMetadataRequest(pathGlob.getBucket(), "path/part-0"))))) |
| .thenReturn(metadata); |
| |
| assertThat( |
| s3FileSystem.match( |
| ImmutableList.of( |
| pathNotExist.toString(), |
| pathForbidden.toString(), |
| pathExist.toString(), |
| pathGlob.toString())), |
| contains( |
| MatchResultMatcher.create(MatchResult.Status.NOT_FOUND, new FileNotFoundException()), |
| MatchResultMatcher.create( |
| MatchResult.Status.ERROR, new IOException(forbiddenException)), |
| MatchResultMatcher.create(100, 1540000000000L, pathExist, true), |
| MatchResultMatcher.create( |
| 200, |
| 1541000000000L, |
| S3ResourceId.fromComponents(pathGlob.getBucket(), foundListObject.getKey()), |
| true))); |
| } |
| |
| @Test |
| public void testWriteAndRead() throws IOException { |
| S3FileSystem s3FileSystem = buildMockedS3FileSystem(s3Options(), client); |
| |
| client.createBucket("testbucket"); |
| |
| byte[] writtenArray = new byte[] {0}; |
| ByteBuffer bb = ByteBuffer.allocate(writtenArray.length); |
| bb.put(writtenArray); |
| |
| // First create an object and write data to it |
| S3ResourceId path = S3ResourceId.fromUri("s3://testbucket/foo/bar.txt"); |
| WritableByteChannel writableByteChannel = |
| s3FileSystem.create(path, builder().setMimeType("application/text").build()); |
| writableByteChannel.write(bb); |
| writableByteChannel.close(); |
| |
| // Now read the same object |
| ByteBuffer bb2 = ByteBuffer.allocate(writtenArray.length); |
| ReadableByteChannel open = s3FileSystem.open(path); |
| open.read(bb2); |
| |
| // And compare the content with the one that was written |
| byte[] readArray = bb2.array(); |
| assertArrayEquals(readArray, writtenArray); |
| open.close(); |
| } |
| |
| /** A mockito argument matcher to implement equality on GetObjectMetadataRequest. */ |
| private static class GetObjectMetadataRequestMatcher |
| extends ArgumentMatcher<GetObjectMetadataRequest> { |
| private final GetObjectMetadataRequest expected; |
| |
| GetObjectMetadataRequestMatcher(GetObjectMetadataRequest expected) { |
| this.expected = expected; |
| } |
| |
| @Override |
| public boolean matches(Object obj) { |
| if (!(obj instanceof GetObjectMetadataRequest)) { |
| return false; |
| } |
| GetObjectMetadataRequest actual = (GetObjectMetadataRequest) obj; |
| return actual.getBucketName().equals(expected.getBucketName()) |
| && actual.getKey().equals(expected.getKey()); |
| } |
| } |
| } |