TestS3AGetFileStatus.java
/**
* 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.fs.s3a;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import software.amazon.awssdk.services.s3.model.CommonPrefix;
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
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.S3Object;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.contract.ContractTestUtils;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
/**
* S3A tests for getFileStatus using mock S3 client.
*/
public class TestS3AGetFileStatus extends AbstractS3AMockTest {
@Test
public void testFile() throws Exception {
Path path = new Path("/file");
String key = path.toUri().getPath().substring(1);
HeadObjectResponse objectMetadata =
HeadObjectResponse.builder().contentLength(1L).lastModified(new Date(2L).toInstant())
.build();
when(s3.headObject(argThat(correctGetMetadataRequest(BUCKET, key)))).thenReturn(objectMetadata);
FileStatus stat = fs.getFileStatus(path);
assertNotNull(stat);
assertEquals(fs.makeQualified(path), stat.getPath());
assertTrue(stat.isFile());
assertEquals(objectMetadata.contentLength().longValue(), stat.getLen());
assertEquals(Date.from(objectMetadata.lastModified()).getTime(), stat.getModificationTime());
ContractTestUtils.assertNotErasureCoded(fs, path);
assertTrue(stat.toString().contains("isErasureCoded=false"),
path + " should have erasure coding unset in " +
"FileStatus#toString(): " + stat);
}
@Test
public void testFakeDirectory() throws Exception {
Path path = new Path("/dir");
String key = path.toUri().getPath().substring(1);
when(s3.headObject(argThat(correctGetMetadataRequest(BUCKET, key))))
.thenThrow(NOT_FOUND);
String keyDir = key + "/";
List<S3Object> s3Objects = new ArrayList<>(1);
s3Objects.add(S3Object.builder().key(keyDir).size(0L).build());
ListObjectsV2Response listObjectsV2Response =
ListObjectsV2Response.builder().contents(s3Objects).build();
when(s3.listObjectsV2(argThat(
matchListV2Request(BUCKET, keyDir))
)).thenReturn(listObjectsV2Response);
FileStatus stat = fs.getFileStatus(path);
assertNotNull(stat);
assertEquals(fs.makeQualified(path), stat.getPath());
assertTrue(stat.isDirectory());
}
@Test
public void testImplicitDirectory() throws Exception {
Path path = new Path("/dir");
String key = path.toUri().getPath().substring(1);
when(s3.headObject(argThat(correctGetMetadataRequest(BUCKET, key))))
.thenThrow(NOT_FOUND);
when(s3.headObject(argThat(
correctGetMetadataRequest(BUCKET, key + "/"))
)).thenThrow(NOT_FOUND);
setupListMocks(Collections.singletonList(CommonPrefix.builder().prefix("dir/").build()),
Collections.emptyList());
FileStatus stat = fs.getFileStatus(path);
assertNotNull(stat);
assertEquals(fs.makeQualified(path), stat.getPath());
assertTrue(stat.isDirectory());
ContractTestUtils.assertNotErasureCoded(fs, path);
assertTrue(stat.toString().contains("isErasureCoded=false"),
path + " should have erasure coding unset in " +
"FileStatus#toString(): " + stat);
}
@Test
public void testRoot() throws Exception {
Path path = new Path("/");
String key = path.toUri().getPath().substring(1);
when(s3.headObject(argThat(correctGetMetadataRequest(BUCKET, key))))
.thenThrow(NOT_FOUND);
when(s3.headObject(argThat(
correctGetMetadataRequest(BUCKET, key + "/")
))).thenThrow(NOT_FOUND);
setupListMocks(Collections.emptyList(), Collections.emptyList());
FileStatus stat = fs.getFileStatus(path);
assertNotNull(stat);
assertEquals(fs.makeQualified(path), stat.getPath());
assertTrue(stat.isDirectory());
assertTrue(stat.getPath().isRoot());
}
@Test
public void testNotFound() throws Exception {
assertThrows(FileNotFoundException.class, () -> {
Path path = new Path("/dir");
String key = path.toUri().getPath().substring(1);
when(s3.headObject(argThat(correctGetMetadataRequest(BUCKET, key))))
.thenThrow(NOT_FOUND);
when(s3.headObject(argThat(
correctGetMetadataRequest(BUCKET, key + "/")
))).thenThrow(NOT_FOUND);
setupListMocks(Collections.emptyList(), Collections.emptyList());
fs.getFileStatus(path);
});
}
private void setupListMocks(List<CommonPrefix> prefixes,
List<S3Object> s3Objects) {
// V1 list API mock
ListObjectsResponse v1Response = ListObjectsResponse.builder()
.commonPrefixes(prefixes)
.contents(s3Objects)
.build();
when(s3.listObjects(any(ListObjectsRequest.class))).thenReturn(v1Response);
// V2 list API mock
ListObjectsV2Response v2Result = ListObjectsV2Response.builder()
.commonPrefixes(prefixes)
.contents(s3Objects)
.build();
when(s3.listObjectsV2(
any(software.amazon.awssdk.services.s3.model.ListObjectsV2Request.class))).thenReturn(
v2Result);
}
private ArgumentMatcher<HeadObjectRequest> correctGetMetadataRequest(
String bucket, String key) {
return request -> request != null
&& request.bucket().equals(bucket)
&& request.key().equals(key);
}
private ArgumentMatcher<ListObjectsV2Request> matchListV2Request(
String bucket, String key) {
return (ListObjectsV2Request request) -> {
return request != null
&& request.bucket().equals(bucket)
&& request.prefix().equals(key);
};
}
}