Skip to content

Commit 38d1f75

Browse files
committed
HADOOP-15183: more work on DDB tests cleaning up/pruning; the AncestorState failure on a file found is now optionally downgradeable to a warn.
Still unsure what to do there, as its a symptom of a problem: there's a file in the store which is a parent of another file. For prune, we can downgrade this to a warn and then rely on FSCK to fix Change-Id: I2f2cf9b85bbd3acd8a606e669c1b7e27a9db16f4
1 parent 3270859 commit 38d1f75

File tree

8 files changed

+163
-55
lines changed

8 files changed

+163
-55
lines changed

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/impl/StoreContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,13 @@ public File createTempFile(String prefix, long size) throws IOException {
316316
return tempFileFactory.apply(prefix, size);
317317
}
318318

319+
/**
320+
* Get the location of the bucket.
321+
* @return the bucket location.
322+
* @throws IOException failure.
323+
*/
324+
public String getBucketLocation() throws IOException {
325+
return getBucketLocation.apply();
326+
}
327+
319328
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DynamoDBMetadataStore.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ private Collection<DDBPathMetadata> completeAncestry(
818818
ancestorsToAdd.add(entry);
819819
Path parent = path.getParent();
820820
while (!parent.isRoot()) {
821-
if (ancestorState.findDirectory(parent)) {
821+
if (ancestorState.findEntry(parent, true)) {
822822
break;
823823
}
824824
LOG.debug("auto-create ancestor path {} for child path {}",
@@ -1180,7 +1180,7 @@ List<DDBPathMetadata> fullPathsToPut(DDBPathMetadata meta,
11801180
Path path = meta.getFileStatus().getPath().getParent();
11811181
while (path != null && !path.isRoot()) {
11821182
synchronized (ancestorState) {
1183-
if (ancestorState.findDirectory(path)) {
1183+
if (ancestorState.findEntry(path, true)) {
11841184
break;
11851185
}
11861186
}
@@ -1336,6 +1336,7 @@ public void prune(long modTime) throws IOException {
13361336
@Override
13371337
@Retries.RetryTranslated
13381338
public void prune(long modTime, String keyPrefix) throws IOException {
1339+
LOG.debug("Prune files under {} with oldes time {}", keyPrefix, modTime);
13391340
int itemCount = 0;
13401341
try {
13411342
Collection<Path> deletionBatch =
@@ -2187,41 +2188,47 @@ public String toString() {
21872188
* @param p path to check
21882189
* @return true if the state has an entry
21892190
*/
2190-
public boolean contains(Path p) {
2191+
boolean contains(Path p) {
21912192
return ancestry.containsKey(p);
21922193
}
21932194

2194-
public DDBPathMetadata put(Path p, DDBPathMetadata md) {
2195+
DDBPathMetadata put(Path p, DDBPathMetadata md) {
21952196
return ancestry.put(p, md);
21962197
}
21972198

2198-
public DDBPathMetadata put(DDBPathMetadata md) {
2199+
DDBPathMetadata put(DDBPathMetadata md) {
21992200
return ancestry.put(md.getFileStatus().getPath(), md);
22002201
}
22012202

2202-
public DDBPathMetadata get(Path p) {
2203+
DDBPathMetadata get(Path p) {
22032204
return ancestry.get(p);
22042205
}
22052206

22062207
/**
2207-
* Find a directory entry, raising an exception if there is a file
2208-
* at the path.
2208+
* Find an entry in the ancestor state, warning and optionally
2209+
* raising an exception if there is a file at the path.
22092210
* @param path path to look up
2211+
* @param failOnFile fail if a file was found.
22102212
* @return true iff a directory was found in the ancestor state.
22112213
* @throws PathIOException if there was a file at the path.
22122214
*/
2213-
public boolean findDirectory(Path path) throws PathIOException {
2215+
boolean findEntry(
2216+
final Path path,
2217+
final boolean failOnFile) throws PathIOException {
22142218
final DDBPathMetadata ancestor = get(path);
22152219
if (ancestor != null) {
22162220
// there's an entry in the ancestor state
22172221
if (!ancestor.getFileStatus().isDirectory()) {
22182222
// but: its a file, which means this update is now inconsistent.
2219-
throw new PathIOException(path.toString(), E_INCONSISTENT_UPDATE);
2220-
} else {
2221-
// the ancestor is a directory, so break out the ancestor creation
2222-
// loop.
2223-
return true;
2223+
final String message = E_INCONSISTENT_UPDATE + " entry is " + ancestor
2224+
.getFileStatus();
2225+
LOG.error(message);
2226+
if (failOnFile) {
2227+
// errors trigger failure
2228+
throw new PathIOException(path.toString(), message);
2229+
}
22242230
}
2231+
return true;
22252232
} else {
22262233
return false;
22272234
}

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/AbstractS3GuardToolTestBase.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,13 @@ private void testPruneCommand(Configuration cmdConf, Path parent,
264264
String...args) throws Exception {
265265
Path keepParent = path("prune-cli-keep");
266266
StopWatch timer = new StopWatch();
267+
final S3AFileSystem fs = getFileSystem();
267268
try {
268269
S3GuardTool.Prune cmd = new S3GuardTool.Prune(cmdConf);
269270
cmd.setMetadataStore(ms);
270271

271-
getFileSystem().mkdirs(parent);
272-
getFileSystem().mkdirs(keepParent);
272+
fs.mkdirs(parent);
273+
fs.mkdirs(keepParent);
273274
createFile(new Path(parent, "stale"), true, true);
274275
createFile(new Path(keepParent, "stale-to-keep"), true, true);
275276

@@ -291,8 +292,10 @@ private void testPruneCommand(Configuration cmdConf, Path parent,
291292
assertMetastoreListingCount(keepParent,
292293
"This child should have been kept (prefix restriction).", 1);
293294
} finally {
294-
getFileSystem().delete(parent, true);
295-
ms.prune(Long.MAX_VALUE);
295+
fs.delete(parent, true);
296+
fs.delete(keepParent, true);
297+
ms.prune(Long.MAX_VALUE, fs.pathToKey(parent));
298+
ms.prune(Long.MAX_VALUE, fs.pathToKey(keepParent));
296299
}
297300
}
298301

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestDynamoDBMetadataStore.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,26 +263,49 @@ public void tearDown() throws Exception {
263263
private void deleteAllMetadata() throws IOException {
264264
// The following is a way to be sure the table will be cleared and there
265265
// will be no leftovers after the test.
266-
ThrottleTracker throttleTracker = new ThrottleTracker(ddbmsStatic);
267-
final Path root = strToPath("/");
266+
deleteMetadataUnderPath(ddbmsStatic, strToPath("/"), true);
267+
}
268+
269+
/**
270+
* Delete all metadata under a path.
271+
* Attempt to use prune first as it scales slightly better.
272+
* @param ms store
273+
* @param path path to prune under
274+
* @param suppressErrors should errors be suppressed?
275+
* @throws IOException if there is a failure and suppressErrors == false
276+
*/
277+
public static void deleteMetadataUnderPath(final DynamoDBMetadataStore ms,
278+
final Path path, final boolean suppressErrors) throws IOException {
279+
ThrottleTracker throttleTracker = new ThrottleTracker(ms);
268280
try (DurationInfo ignored = new DurationInfo(LOG, true, "prune")) {
269-
ddbmsStatic.prune(System.currentTimeMillis(),
270-
PathMetadataDynamoDBTranslation.pathToParentKey(root));
281+
ms.prune(System.currentTimeMillis(),
282+
PathMetadataDynamoDBTranslation.pathToParentKey(path));
271283
LOG.info("Throttle statistics: {}", throttleTracker);
284+
} catch (IOException ioe) {
285+
// prune failed. warn and then fall back to forget.
286+
LOG.warn("Failed to prune {}", path, ioe);
287+
if (!suppressErrors) {
288+
throw ioe;
289+
}
272290
}
273291
// and after the pruning, make sure all other metadata is gone
274292
int forgotten = 0;
275293
try (DurationInfo ignored = new DurationInfo(LOG, true, "forget")) {
276-
PathMetadata meta = ddbmsStatic.get(root);
294+
PathMetadata meta = ms.get(path);
277295
if (meta != null) {
278-
for (DescendantsIterator desc = new DescendantsIterator(ddbmsStatic,
296+
for (DescendantsIterator desc = new DescendantsIterator(ms,
279297
meta);
280298
desc.hasNext(); ) {
281299
forgotten++;
282-
ddbmsStatic.forgetMetadata(desc.next().getPath());
300+
ms.forgetMetadata(desc.next().getPath());
283301
}
284302
LOG.info("Forgot {} entries", forgotten);
285303
}
304+
} catch (IOException ioe) {
305+
LOG.warn("Failed to forget entries under {}", path, ioe);
306+
if (!suppressErrors) {
307+
throw ioe;
308+
}
286309
}
287310
LOG.info("Throttle statistics: {}", throttleTracker);
288311
}
@@ -491,7 +514,9 @@ private void doBatchWriteForOneSet(int index) throws IOException {
491514
numNewMetas,
492515
getDynamoMetadataStore());
493516
}
494-
deleteAllMetadata();
517+
// The following is a way to be sure the table will be cleared and there
518+
// will be no leftovers after the test.
519+
deleteMetadataUnderPath(ddbmsStatic, strToPath("/"), false);
495520
}
496521

497522
/**

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestDynamoDBMetadataStoreScale.java

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ public void test_030_BatchedWrite() throws Exception {
229229
try {
230230
describe("Running %d iterations of batched put, size %d", iterations,
231231
BATCH_SIZE);
232+
Path base = path(getMethodName());
233+
final String pathKey = base.toUri().getPath();
232234

233235
ThrottleTracker result = execute("prune",
234236
1,
@@ -237,7 +239,8 @@ public void test_030_BatchedWrite() throws Exception {
237239
ThrottleTracker tracker = new ThrottleTracker(ddbms);
238240
long pruneItems = 0;
239241
for (long i = 0; i < iterations; i++) {
240-
Path longPath = pathOfDepth(BATCH_SIZE, String.valueOf(i));
242+
Path longPath = pathOfDepth(BATCH_SIZE,
243+
pathKey, String.valueOf(i));
241244
S3AFileStatus status = basicFileStatus(longPath, 0, false,
242245
12345);
243246
PathMetadata pm = new PathMetadata(status);
@@ -251,7 +254,7 @@ public void test_030_BatchedWrite() throws Exception {
251254

252255
if (pruneItems == BATCH_SIZE) {
253256
describe("pruning files");
254-
ddbms.prune(Long.MAX_VALUE /* all files */);
257+
ddbms.prune(Long.MAX_VALUE, pathKey);
255258
pruneItems = 0;
256259
}
257260
if (tracker.probe()) {
@@ -365,23 +368,28 @@ public void test_080_fullPathsToPut() throws Throwable {
365368
Path base = new Path("s3a://example.org/test_080_fullPathsToPut");
366369
Path child = new Path(base, "child");
367370
List<PathMetadata> pms = new ArrayList<>();
368-
try(BulkOperationState bulkUpdate
369-
= ddbms.initiateBulkWrite(child)) {
370-
ddbms.put(new PathMetadata(makeDirStatus(base)), bulkUpdate);
371-
ddbms.put(new PathMetadata(makeDirStatus(child)), bulkUpdate);
372-
ddbms.getInvoker().retry("set up directory tree",
373-
base.toString(),
374-
true,
375-
() -> ddbms.put(pms, bulkUpdate));
376-
}
377-
try(BulkOperationState bulkUpdate
378-
= ddbms.initiateBulkWrite(child)) {
379-
DDBPathMetadata dirData = ddbms.get(child, true);
380-
execute("put",
381-
OPERATIONS_PER_THREAD,
382-
expectThrottling(),
383-
() -> ddbms.fullPathsToPut(dirData, bulkUpdate)
384-
);
371+
try {
372+
try (BulkOperationState bulkUpdate
373+
= ddbms.initiateBulkWrite(child)) {
374+
ddbms.put(new PathMetadata(makeDirStatus(base)), bulkUpdate);
375+
ddbms.put(new PathMetadata(makeDirStatus(child)), bulkUpdate);
376+
ddbms.getInvoker().retry("set up directory tree",
377+
base.toString(),
378+
true,
379+
() -> ddbms.put(pms, bulkUpdate));
380+
}
381+
try (BulkOperationState bulkUpdate
382+
= ddbms.initiateBulkWrite(child)) {
383+
DDBPathMetadata dirData = ddbms.get(child, true);
384+
execute("put",
385+
OPERATIONS_PER_THREAD,
386+
expectThrottling(),
387+
() -> ddbms.fullPathsToPut(dirData, bulkUpdate)
388+
);
389+
}
390+
} finally {
391+
ddbms.forgetMetadata(child);
392+
ddbms.forgetMetadata(base);
385393
}
386394
}
387395

@@ -533,22 +541,21 @@ public ThrottleTracker execute(String operation,
533541
* @param ms store
534542
* @param pm path to clean up
535543
*/
536-
private void cleanupMetadata(MetadataStore ms, PathMetadata pm) {
544+
private void cleanupMetadata(DynamoDBMetadataStore ms, PathMetadata pm) {
537545
Path path = pm.getFileStatus().getPath();
538546
try {
539-
ddbms.getInvoker().retry("clean up", path.toString(), true,
540-
() -> ms.forgetMetadata(path));
547+
ITestDynamoDBMetadataStore.deleteMetadataUnderPath(ms, path, true);
541548
} catch (IOException ioe) {
542549
// Ignore.
543550
LOG.info("Ignoring error while cleaning up {} in database", path, ioe);
544551
}
545552
}
546553

547-
private Path pathOfDepth(long n, @Nullable String fileSuffix) {
554+
private Path pathOfDepth(long n,
555+
String name, @Nullable String fileSuffix) {
548556
StringBuilder sb = new StringBuilder();
549557
for (long i = 0; i < n; i++) {
550-
sb.append(i == 0 ? "/" + this.getClass().getSimpleName() : "lvl");
551-
sb.append(i);
558+
sb.append(i == 0 ? "/" + name : String.format("level-%03d", i));
552559
if (i == n - 1 && fileSuffix != null) {
553560
sb.append(fileSuffix);
554561
}

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestS3GuardToolLocal.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,8 @@ private void uploadCommandAssertCount(S3AFileSystem fs, String options[],
417417
String[] fields = line.split("\\s");
418418
if (fields.length == 4 && fields[0].equals(Uploads.TOTAL)) {
419419
int parsedUploads = Integer.valueOf(fields[1]);
420-
LOG.debug("Matched CLI output: {} {} {} {}", fields);
420+
LOG.debug("Matched CLI output: {} {} {} {}",
421+
fields[0], fields[1], fields[2], fields[3]);
421422
assertEquals("Unexpected number of uploads", numUploads,
422423
parsedUploads);
423424
return;

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestDynamoDBMiscOperations.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
import com.amazonaws.waiters.WaiterTimedOutException;
2727
import org.junit.Test;
2828

29+
import org.apache.hadoop.fs.FileStatus;
30+
import org.apache.hadoop.fs.PathIOException;
2931
import org.apache.hadoop.fs.s3a.AWSClientIOException;
32+
import org.apache.hadoop.fs.s3a.S3AFileStatus;
3033
import org.apache.hadoop.test.HadoopTestBase;
3134
import org.apache.hadoop.fs.Path;
3235

@@ -109,4 +112,56 @@ public void testInnerListChildrenDirectoryNpe() throws Exception {
109112
ddbms.getDirListingMetadataFromDirMetaAndList(p, metas, dirPathMeta));
110113
}
111114

115+
@Test
116+
public void testAncestorStateForDir() throws Throwable {
117+
final DynamoDBMetadataStore.AncestorState ancestorState
118+
= new DynamoDBMetadataStore.AncestorState(null);
119+
120+
// path 1 is a directory
121+
final Path path1 = new Path("s3a://bucket/1");
122+
final S3AFileStatus status1 = new S3AFileStatus(true,
123+
path1, "hadoop");
124+
final DDBPathMetadata md1 = new DDBPathMetadata(
125+
status1);
126+
ancestorState.put(md1);
127+
assertTrue("Status not found in ancestors",
128+
ancestorState.contains(path1));
129+
final DDBPathMetadata result = ancestorState.get(path1);
130+
assertEquals(status1, result.getFileStatus());
131+
assertTrue("Lookup failed",
132+
ancestorState.findEntry(path1, true));
133+
final Path path2 = new Path("s3a://bucket/2");
134+
assertFalse("Lookup didn't fail",
135+
ancestorState.findEntry(path2, true));
136+
assertFalse("Lookup didn't fail",
137+
ancestorState.contains(path2));
138+
assertNull("Lookup didn't fail",
139+
ancestorState.get(path2));
140+
}
141+
142+
@Test
143+
public void testAncestorStateForFile() throws Throwable {
144+
final DynamoDBMetadataStore.AncestorState ancestorState
145+
= new DynamoDBMetadataStore.AncestorState(null);
146+
147+
// path 1 is a file
148+
final Path path1 = new Path("s3a://bucket/1");
149+
final S3AFileStatus status1 = new S3AFileStatus(
150+
1024_1024_1024L,
151+
0,
152+
path1,
153+
32_000_000,
154+
"hadoop",
155+
"e4",
156+
"f5");
157+
final DDBPathMetadata md1 = new DDBPathMetadata(
158+
status1);
159+
ancestorState.put(md1);
160+
assertTrue("Lookup failed",
161+
ancestorState.findEntry(path1, false));
162+
intercept(PathIOException.class,
163+
DynamoDBMetadataStore.E_INCONSISTENT_UPDATE,
164+
() -> ancestorState.findEntry(path1, true));
165+
}
166+
112167
}

0 commit comments

Comments
 (0)