Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.google.common.annotations.VisibleForTesting;

import org.apache.commons.lang3.tuple.Triple;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.LocatedFileStatus;
Expand Down Expand Up @@ -329,6 +330,55 @@ public RemoteIterator<S3ALocatedFileStatus> getLocatedFileStatusIteratorForDir(
tombstones);
}

/**
* Calculate list of file statuses assuming path
* to be a non-empty directory.
* @param path input path.
* @return Triple of file statuses, metaData, auth flag.
* @throws IOException Any IO problems.
*/
public Triple<S3AFileStatus[], DirListingMetadata, Boolean>
getFileStatusesAssumingNonEmptyDir(Path path)
throws IOException {
String key = pathToKey(path);
List<S3AFileStatus> result;
if (!key.isEmpty()) {
key = key + '/';
}

boolean allowAuthoritative = listingOperationCallbacks
.allowAuthoritative(path);
DirListingMetadata dirMeta =
S3Guard.listChildrenWithTtl(
getStoreContext().getMetadataStore(),
path,
listingOperationCallbacks.getUpdatedTtlTimeProvider(),
allowAuthoritative);
// In auth mode return directly with auth flag.
if (allowAuthoritative && dirMeta != null && dirMeta.isAuthoritative()) {
return Triple.of(S3Guard.dirMetaToStatuses(dirMeta),
dirMeta, Boolean.TRUE);
}

S3ListRequest request = createListObjectsRequest(key, "/");
LOG.debug("listStatus: doing listObjects for directory {}", key);

Listing.FileStatusListingIterator files = createFileStatusListingIterator(
path,
request,
ACCEPT_ALL,
new Listing.AcceptAllButSelfAndS3nDirs(path));
result = new ArrayList<>(files.getBatchSize());
while (files.hasNext()) {
result.add(files.next());
}
// return the results obtained from s3.
return Triple.of(
result.toArray(new S3AFileStatus[result.size()]),
dirMeta,
Boolean.FALSE);
}

public S3ListRequest createListObjectsRequest(String key, String delimiter) {
return listingOperationCallbacks.createListObjectsRequest(key, delimiter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
Expand Down Expand Up @@ -2666,50 +2667,44 @@ public FileStatus[] listStatus(Path f) throws FileNotFoundException,
* @throws AmazonClientException on failures inside the AWS SDK
*/
private S3AFileStatus[] innerListStatus(Path f) throws FileNotFoundException,
IOException, AmazonClientException {
IOException, AmazonClientException {
Path path = qualify(f);
String key = pathToKey(path);
LOG.debug("List status for path: {}", path);
entryPoint(INVOCATION_LIST_STATUS);

List<S3AFileStatus> result;
final S3AFileStatus fileStatus = innerGetFileStatus(path, false,
StatusProbeEnum.ALL);

if (fileStatus.isDirectory()) {
if (!key.isEmpty()) {
key = key + '/';
}

boolean allowAuthoritative = allowAuthoritative(f);
DirListingMetadata dirMeta =
S3Guard.listChildrenWithTtl(metadataStore, path, ttlTimeProvider,
allowAuthoritative);
if (allowAuthoritative && dirMeta != null && dirMeta.isAuthoritative()) {
return S3Guard.dirMetaToStatuses(dirMeta);
}

S3ListRequest request = createListObjectsRequest(key, "/");
LOG.debug("listStatus: doing listObjects for directory {}", key);

Listing.FileStatusListingIterator files =
listing.createFileStatusListingIterator(path,
request,
ACCEPT_ALL,
new Listing.AcceptAllButSelfAndS3nDirs(path));
result = new ArrayList<>(files.getBatchSize());
while (files.hasNext()) {
result.add(files.next());
Triple<S3AFileStatus[], DirListingMetadata, Boolean>
statusesAssumingNonEmptyDir = listing
.getFileStatusesAssumingNonEmptyDir(path);

if (statusesAssumingNonEmptyDir.getLeft().length == 0 &&
statusesAssumingNonEmptyDir.getRight()) {
// We are sure that this is an empty directory in auth mode.
return statusesAssumingNonEmptyDir.getLeft();
}
else if (statusesAssumingNonEmptyDir.getLeft().length == 0) {
// We may have an empty dir, or may have file or may have nothing.
// So we call innerGetFileStatus to get the status, this may throw
// FileNotFoundException if we have nothing.
// So We are guaranteed to have either a dir marker or a file.
final S3AFileStatus fileStatus = innerGetFileStatus(path, false,
StatusProbeEnum.ALL);
// If it is a file return directly.
if (fileStatus.isFile()) {
LOG.debug("Adding: rd (not a dir): {}", path);
S3AFileStatus[] stats = new S3AFileStatus[1];
stats[0] = fileStatus;
return stats;
}
// merge the results. This will update the store as needed
return S3Guard.dirListingUnion(metadataStore, path, result, dirMeta,
allowAuthoritative, ttlTimeProvider);
} else {
LOG.debug("Adding: rd (not a dir): {}", path);
S3AFileStatus[] stats = new S3AFileStatus[1];
stats[0]= fileStatus;
return stats;
}
// Here we have a directory which may or may not be empty.
// So we update the metastore and return.
return S3Guard.dirListingUnion(
metadataStore,
path,
Arrays.asList(statusesAssumingNonEmptyDir.getLeft()),
statusesAssumingNonEmptyDir.getMiddle(),
allowAuthoritative(path),
ttlTimeProvider);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,77 @@ public void testCostOfListFilesOnNonExistingDir() throws Throwable {
.plus(GET_FILE_STATUS_FNFE)));
}

@Test
public void testCostOfListStatusOnFile() throws Throwable {
describe("Performing listStatus() on a file");
Path file = path(getMethodName() + ".txt");
S3AFileSystem fs = getFileSystem();
touch(fs, file);
verifyMetrics(() ->
fs.listStatus(file),
whenRaw(LIST_STATUS_LIST_OP
.plus(GET_FILE_STATUS_ON_FILE)),
whenAuthoritative(LIST_STATUS_LIST_OP),
whenNonauth(LIST_STATUS_LIST_OP));
// resetMetricDiffs();
// fs.listStatus(file);
Comment thread
mukund-thakur marked this conversation as resolved.
Outdated
// if (!fs.hasMetadataStore()) {
// metadataRequests.assertDiffEquals(1);
// }
// listRequests.assertDiffEquals(1);
}

@Test
public void testCostOfListStatusOnEmptyDir() throws Throwable {
describe("Performing listStatus() on an empty dir");
Path dir = path(getMethodName());
S3AFileSystem fs = getFileSystem();
fs.mkdirs(dir);
verifyMetrics(() ->
fs.listStatus(dir),
whenRaw(LIST_STATUS_LIST_OP
Comment thread
mukund-thakur marked this conversation as resolved.
.plus(GET_FILE_STATUS_ON_EMPTY_DIR)),
whenAuthoritative(NO_IO),
whenNonauth(LIST_STATUS_LIST_OP));
// resetMetricDiffs();
// fs.listStatus(dir);
// if (!fs.hasMetadataStore()) {
// verifyOperationCount(2, 1);
// } else {
// if (fs.allowAuthoritative(dir)) {
// verifyOperationCount(0, 0);
// } else {
// verifyOperationCount(0, 1);
// }
// }
}

@Test
public void testCostOfListStatusOnNonEmptyDir() throws Throwable {
describe("Performing listStatus() on a non empty dir");
Path dir = path(getMethodName());
S3AFileSystem fs = getFileSystem();
fs.mkdirs(dir);
Path file = new Path(dir, "file.txt");
touch(fs, file);
verifyMetrics(() ->
fs.listStatus(dir),
whenRaw(LIST_STATUS_LIST_OP),
whenAuthoritative(NO_IO),
whenNonauth(LIST_STATUS_LIST_OP));
// resetMetricDiffs();
// fs.listStatus(dir);
// if (!fs.hasMetadataStore()) {
// verifyOperationCount(0, 1);
// } else {
// if (fs.allowAuthoritative(dir)) {
// verifyOperationCount(0, 0);
// } else {
// verifyOperationCount(0, 1);
// }
// }
}

@Test
public void testCostOfGetFileStatusOnFile() throws Throwable {
describe("performing getFileStatus on a file");
Expand Down Expand Up @@ -406,8 +477,7 @@ public void testCostOfGlobStatus() throws Throwable {
fs.globStatus(basePath.suffix("/*"));
// 2 head + 1 list from getFileStatus on path,
// plus 1 list to match the glob pattern
verifyRaw(GET_FILE_STATUS_ON_DIR
.plus(LIST_OPERATION),
verifyRaw(LIST_STATUS_LIST_OP,
() -> fs.globStatus(basePath.suffix("/*")));
}

Expand All @@ -426,8 +496,7 @@ public void testCostOfGlobStatusNoSymlinkResolution() throws Throwable {
// unguarded: 2 head + 1 list from getFileStatus on path,
// plus 1 list to match the glob pattern
// no additional operations from symlink resolution
verifyRaw(GET_FILE_STATUS_ON_DIR
.plus(LIST_OPERATION),
verifyRaw(LIST_STATUS_LIST_OP,
() -> fs.globStatus(basePath.suffix("/*")));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public final class OperationCost {
public static final OperationCost LIST_FILES_LIST_OP =
new OperationCost(0, 1);

/** listStatus always does a LIST. */
public static final OperationCost LIST_STATUS_LIST_OP =
new OperationCost(0, 1);
Comment thread
mukund-thakur marked this conversation as resolved.
Outdated
/**
* Metadata cost of a copy operation, as used during rename.
* This happens even if the store is guarded.
Expand Down