Skip to content

Commit cf10674

Browse files
committed
HADOOP-16697 preparing for review.
* tests for the CLI, command line parsing etc * tuning CLI based on manual use (mainly logging) S3GuardTool is now closeable, and will close its FS and MS in close. Without this, our tests can leak FS and MS instances. We just hadn't noticed. The CLI entry point of S3GuardTool does the right thing and closes the tool; for our tests we have to explicitly do it. This is done in a mixture of try-with-resources and building up a list of tools to close in test tear down. All tests which patch in an existing metastore need to replace it with a null one before the close operation. Are you Change-Id: I061eb0ece24e84aaffb4f12a3e7c920011146dd6 Testing: new tests are good, doing more regression testing. Can't run -Dlocal for some reason.
1 parent a9dd68b commit cf10674

File tree

11 files changed

+522
-188
lines changed

11 files changed

+522
-188
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3153,12 +3153,12 @@ protected synchronized void stopAllServices() {
31533153
HadoopExecutors.shutdown(unboundedThreadPool, LOG,
31543154
THREAD_POOL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
31553155
unboundedThreadPool = null;
3156-
closeAutocloseables(LOG, credentials);
31573156
cleanupWithLogger(LOG,
31583157
metadataStore,
31593158
instrumentation,
31603159
delegationTokens.orElse(null),
31613160
signerManager);
3161+
closeAutocloseables(LOG, credentials);
31623162
delegationTokens = Optional.empty();
31633163
signerManager = null;
31643164
credentials = null;

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

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@
3434
import org.apache.hadoop.fs.s3a.impl.StoreContext;
3535
import org.apache.hadoop.service.launcher.LauncherExitCodes;
3636
import org.apache.hadoop.util.DurationInfo;
37+
import org.apache.hadoop.util.ExitCodeProvider;
3738
import org.apache.hadoop.util.ExitUtil;
3839

40+
import static org.apache.hadoop.service.launcher.LauncherExitCodes.EXIT_NOT_ACCEPTABLE;
41+
3942
/**
4043
* Audit a directory tree for being authoritative.
4144
* One aspect of the audit to be aware of: the root directory is
@@ -48,9 +51,9 @@ public class AuthoritativeAuditOperation extends AbstractStoreOperation {
4851
AuthoritativeAuditOperation.class);
4952

5053
/**
51-
* Exception error code when a path is nonauth in the DB: {@value}.
54+
* Exception error code when a path is non-auth in the DB}.
5255
*/
53-
public static final int ERROR_ENTRY_NOT_AUTH_IN_DDB = 4;
56+
public static final int ERROR_ENTRY_NOT_AUTH_IN_DDB = EXIT_NOT_ACCEPTABLE;
5457

5558
/**
5659
* Exception error code when a path is not configured to be
@@ -70,19 +73,27 @@ public class AuthoritativeAuditOperation extends AbstractStoreOperation {
7073
/** require all directories to be authoritative. */
7174
private final boolean requireAuthoritative;
7275

76+
/**
77+
* Verbose switch.
78+
*/
79+
private final boolean verbose;
80+
7381
/**
7482
* Constructor.
7583
* @param storeContext store context.
7684
* @param metastore metastore
7785
* @param requireAuthoritative require all directories to be authoritative
86+
* @param verbose verbose output
7887
*/
7988
public AuthoritativeAuditOperation(
8089
final StoreContext storeContext,
8190
final DynamoDBMetadataStore metastore,
82-
final boolean requireAuthoritative) {
91+
final boolean requireAuthoritative,
92+
final boolean verbose) {
8393
super(storeContext);
8494
this.metastore = metastore;
8595
this.requireAuthoritative = requireAuthoritative;
96+
this.verbose = verbose;
8697
}
8798

8899
/**
@@ -122,11 +133,6 @@ public Pair<Integer, Integer> audit(Path path) throws IOException {
122133
try (DurationInfo ignored =
123134
new DurationInfo(LOG, "audit %s", path)) {
124135
return executeAudit(path, requireAuthoritative, true);
125-
} catch (NonAuthoritativeDirException p) {
126-
throw new ExitUtil.ExitException(
127-
ERROR_ENTRY_NOT_AUTH_IN_DDB,
128-
p.toString(),
129-
p);
130136
}
131137
}
132138

@@ -142,7 +148,8 @@ public Pair<Integer, Integer> audit(Path path) throws IOException {
142148
* @throws NonAuthoritativeDirException if a non-auth dir was found.
143149
*/
144150
@VisibleForTesting
145-
Pair<Integer, Integer> executeAudit(final Path path,
151+
Pair<Integer, Integer> executeAudit(
152+
final Path path,
146153
final boolean requireAuth,
147154
final boolean recursive) throws IOException {
148155
int dirs = 0;
@@ -166,16 +173,24 @@ Pair<Integer, Integer> executeAudit(final Path path,
166173
while (!queue.isEmpty()) {
167174
dirs++;
168175
final DDBPathMetadata dir = queue.poll();
169-
LOG.info("Directory {} {} authoritative",
170-
dir.getFileStatus().getPath(),
171-
dir.isAuthoritativeDir() ? "is" : "is not");
176+
final Path p = dir.getFileStatus().getPath();
177+
// log a message about the dir state, with root treated specially
178+
if (!p.isRoot()) {
179+
LOG.info("Directory {} {} authoritative",
180+
p,
181+
dir.isAuthoritativeDir() ? "is" : "is not");
182+
} else {
183+
// this is done to avoid the confusing message about root not being
184+
// authoritative
185+
LOG.info("Root directory {}", p);
186+
}
172187
LOG.debug("Directory {}", dir);
173188
verifyAuthDir(dir, requireAuth);
174189

175190
// list its children
176191
if (recursive) {
177192
final DirListingMetadata entry = metastore.listChildren(
178-
dir.getFileStatus().getPath());
193+
p);
179194

180195
if (entry != null) {
181196
final Collection<PathMetadata> listing = entry.getListing();
@@ -196,17 +211,42 @@ Pair<Integer, Integer> executeAudit(final Path path,
196211
}
197212
}
198213
}
214+
// end of scan
215+
if (dirs == 1 && isRoot) {
216+
LOG.info("The store has no directories to scan");
217+
} else {
218+
LOG.info("Scanned {} directories - {} were not marked as authoritative",
219+
dirs, nonauth);
220+
}
199221
return Pair.of(dirs, nonauth);
200222
}
201223

202224
/**
203225
* A directory was found which was non-authoritative.
226+
* The exit code for this operation is
227+
* {@link LauncherExitCodes#EXIT_NOT_ACCEPTABLE} -This is what the S3Guard
228+
* will return.
204229
*/
205-
public static class NonAuthoritativeDirException extends PathIOException {
206-
207-
public NonAuthoritativeDirException(final Path path) {
230+
public static class NonAuthoritativeDirException extends PathIOException
231+
implements ExitCodeProvider {
232+
233+
/**
234+
* Instantiate.
235+
* @param path the path which is non-authoritative.
236+
*/
237+
private NonAuthoritativeDirException(final Path path) {
208238
super(path.toString(), E_NONAUTH);
209239
}
240+
241+
@Override
242+
public int getExitCode() {
243+
return ERROR_ENTRY_NOT_AUTH_IN_DDB;
244+
}
245+
246+
@Override
247+
public String toString() {
248+
return getMessage();
249+
}
210250
}
211251

212252
}

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ public void initialize(FileSystem fs, ITtlTimeProvider ttlTp)
393393
throws IOException {
394394
Preconditions.checkNotNull(fs, "Null filesystem");
395395
Preconditions.checkArgument(fs instanceof S3AFileSystem,
396-
"DynamoDBMetadataStore only supports S3A filesystem.");
396+
"DynamoDBMetadataStore only supports S3A filesystem - not %s", fs );
397397
bindToOwnerFilesystem((S3AFileSystem) fs);
398398
final String bucket = owner.getBucket();
399399
String confRegion = conf.getTrimmed(S3GUARD_DDB_REGION_KEY);
@@ -776,7 +776,7 @@ private DDBPathMetadata innerGet(Path path, boolean wantEmptyDirectoryFlag)
776776

777777
// If directory is authoritative, we can set the empty directory flag
778778
// to TRUE or FALSE. Otherwise FALSE, or UNKNOWN.
779-
if(meta.isAuthoritativeDir()) {
779+
if (meta.isAuthoritativeDir()) {
780780
meta.setIsEmptyDirectory(
781781
hasChildren ? Tristate.FALSE : Tristate.TRUE);
782782
} else {
@@ -928,7 +928,7 @@ private Collection<DDBPathMetadata> completeAncestry(
928928
path, entry);
929929
}
930930
}
931-
// add the entry to the ancestry map as a requested value.
931+
// add the entry to the ancestry map as an explicitly requested entry.
932932
ancestry.put(path, Pair.of(EntryOrigin.Requested, entry));
933933
Path parent = path.getParent();
934934
while (!parent.isRoot() && !ancestry.containsKey(parent)) {
@@ -940,7 +940,8 @@ private Collection<DDBPathMetadata> completeAncestry(
940940
final Item item = getConsistentItem(parent);
941941
if (item != null && !itemToPathMetadata(item, username).isDeleted()) {
942942
// This is an undeleted entry found in the database.
943-
// register it in ancestor state and map of entries to create
943+
// register it in ancestor state and in the map of entries to create
944+
// as a retrieved entry
944945
md = itemToPathMetadata(item, username);
945946
LOG.debug("Found existing entry for parent: {}", md);
946947
newEntry = Pair.of(EntryOrigin.Retrieved, md);
@@ -951,6 +952,7 @@ private Collection<DDBPathMetadata> completeAncestry(
951952
final S3AFileStatus status = makeDirStatus(parent, username);
952953
md = new DDBPathMetadata(status, Tristate.FALSE,
953954
false, false, ttlTimeProvider.getNow());
955+
// declare to be a generated entry
954956
newEntry = Pair.of(EntryOrigin.Generated, md);
955957
}
956958
// insert into the ancestor state to avoid further checks
@@ -961,7 +963,9 @@ private Collection<DDBPathMetadata> completeAncestry(
961963
}
962964
}
963965
// we now have a list of entries which were not in the operation state.
964-
// sort in reverse order of existence
966+
// Filter out those which were retrieved, to produce a list of those
967+
// which must be written to the database.
968+
// TODO sort in reverse order of existence
965969
return ancestry.values().stream()
966970
.filter(p -> p.getLeft() != EntryOrigin.Retrieved)
967971
.map(Pair::getRight)
@@ -2076,19 +2080,28 @@ public RenameTracker initiateRenameOperation(
20762080

20772081
/**
20782082
* Mark the directories instantiated under the destination path
2079-
* as authoritative.
2083+
* as authoritative. That is: all entries in the
2084+
* operationState (which must be an AncestorState instance),
2085+
* that are under the destination path.
2086+
*
2087+
* The database update synchronized on the operationState, so all other
2088+
* threads trying to update that state will be blocked until completion.
2089+
*
2090+
* This operation is only used in import and at the end of a rename,
2091+
* so this is not considered an issue.
20802092
* @param dest destination path.
20812093
* @param operationState active state.
20822094
* @throws IOException failure.
20832095
* @return the number of directories marked.
20842096
*/
20852097
@Override
2086-
public int markAsAuthoritative(final Path dest,
2098+
public int markAsAuthoritative(
2099+
final Path dest,
20872100
final BulkOperationState operationState) throws IOException {
20882101
if (operationState == null) {
20892102
return 0;
20902103
}
2091-
AncestorState state = (AncestorState)operationState;
2104+
final AncestorState state = (AncestorState)operationState;
20922105
// only mark paths under the dest as auth
20932106
final String simpleDestKey = pathToParentKey(dest);
20942107
final String destPathKey = simpleDestKey + "/";
@@ -2152,7 +2165,6 @@ private static void logPut(
21522165
String stateStr = AncestorState.stateAsString(state);
21532166
for (Item item : items) {
21542167
boolean tombstone = !itemExists(item);
2155-
21562168
boolean isDir = hasBoolAttribute(item, IS_DIR, false);
21572169
boolean auth = hasBoolAttribute(item, IS_AUTHORITATIVE, false);
21582170
OPERATIONS_LOG.debug("{} {} {}{}{}",

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private long importDir() throws IOException {
166166
// instead we merge them
167167
if (!isDirectory) {
168168
final PathMetadata existingEntry = S3Guard.getWithTtl(ms, path, null,
169-
false);
169+
false, true);
170170
if (existingEntry != null) {
171171
final S3AFileStatus existingStatus = existingEntry.getFileStatus();
172172
if (existingStatus.isFile()) {
@@ -239,7 +239,7 @@ private void putParentsIfNotPresent(FileStatus f,
239239
final ITtlTimeProvider timeProvider
240240
= getFilesystem().getTtlTimeProvider();
241241
final PathMetadata pmd = S3Guard.getWithTtl(getStore(), parent,
242-
timeProvider, false);
242+
timeProvider, false, true);
243243
if (pmd == null || pmd.isDeleted()) {
244244
S3AFileStatus dir = DynamoDBMetadataStore.makeDirStatus(parent,
245245
f.getOwner());

0 commit comments

Comments
 (0)