Skip to content

Commit cda8b2a

Browse files
zhjwpkujojochuang
authored andcommitted
HDFS-14925. Rename operation should check nest snapshot (apache#1670)
If the src directory or any of its descendant is snapshottable and the dst directory or any of its ancestors is snapshottable, we consider this as nested snapshot, which should be denied. Reviewed-by: Shashikant Banerjee <[email protected]> (cherry picked from commit de6b8b0) (cherry picked from commit c9fc118)
1 parent 9956d18 commit cda8b2a

File tree

3 files changed

+101
-13
lines changed

3 files changed

+101
-13
lines changed

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirRenameOp.java

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ private static INodesInPath dstForRenameTo(
143143
* Change a path name
144144
*
145145
* @param fsd FSDirectory
146-
* @param src source path
147-
* @param dst destination path
146+
* @param srcIIP source path
147+
* @param dstIIP destination path
148148
* @return true INodesInPath if rename succeeds; null otherwise
149149
* @deprecated See {@link #renameToInt(FSDirectory, String, String,
150150
* boolean, Options.Rename...)}
@@ -155,8 +155,9 @@ static INodesInPath unprotectedRenameTo(FSDirectory fsd,
155155
throws IOException {
156156
assert fsd.hasWriteLock();
157157
final INode srcInode = srcIIP.getLastINode();
158+
List<INodeDirectory> snapshottableDirs = new ArrayList<>();
158159
try {
159-
validateRenameSource(fsd, srcIIP);
160+
validateRenameSource(fsd, srcIIP, snapshottableDirs);
160161
} catch (SnapshotException e) {
161162
throw e;
162163
} catch (IOException ignored) {
@@ -190,6 +191,8 @@ static INodesInPath unprotectedRenameTo(FSDirectory fsd,
190191
return null;
191192
}
192193

194+
validateNestSnapshot(fsd, src, dstParent.asDirectory(), snapshottableDirs);
195+
193196
fsd.ezManager.checkMoveValidity(srcIIP, dstIIP);
194197
// Ensure dst has quota to accommodate rename
195198
verifyFsLimitsForRename(fsd, srcIIP, dstIIP);
@@ -342,8 +345,8 @@ static void renameForEditLog(
342345
* for details related to rename semantics and exceptions.
343346
*
344347
* @param fsd FSDirectory
345-
* @param src source path
346-
* @param dst destination path
348+
* @param srcIIP source path
349+
* @param dstIIP destination path
347350
* @param timestamp modification time
348351
* @param collectedBlocks blocks to be removed
349352
* @param options Rename options
@@ -361,7 +364,8 @@ static RenameResult unprotectedRenameTo(FSDirectory fsd,
361364
final String dst = dstIIP.getPath();
362365
final String error;
363366
final INode srcInode = srcIIP.getLastINode();
364-
validateRenameSource(fsd, srcIIP);
367+
List<INodeDirectory> srcSnapshottableDirs = new ArrayList<>();
368+
validateRenameSource(fsd, srcIIP, srcSnapshottableDirs);
365369

366370
// validate the destination
367371
if (dst.equals(src)) {
@@ -380,10 +384,10 @@ static RenameResult unprotectedRenameTo(FSDirectory fsd,
380384
BlockStoragePolicySuite bsps = fsd.getBlockStoragePolicySuite();
381385
fsd.ezManager.checkMoveValidity(srcIIP, dstIIP);
382386
final INode dstInode = dstIIP.getLastINode();
383-
List<INodeDirectory> snapshottableDirs = new ArrayList<>();
387+
List<INodeDirectory> dstSnapshottableDirs = new ArrayList<>();
384388
if (dstInode != null) { // Destination exists
385389
validateOverwrite(src, dst, overwrite, srcInode, dstInode);
386-
FSDirSnapshotOp.checkSnapshot(fsd, dstIIP, snapshottableDirs);
390+
FSDirSnapshotOp.checkSnapshot(fsd, dstIIP, dstSnapshottableDirs);
387391
}
388392

389393
INode dstParent = dstIIP.getINode(-2);
@@ -400,6 +404,9 @@ static RenameResult unprotectedRenameTo(FSDirectory fsd,
400404
throw new ParentNotDirectoryException(error);
401405
}
402406

407+
validateNestSnapshot(fsd, src,
408+
dstParent.asDirectory(), srcSnapshottableDirs);
409+
403410
// Ensure dst has quota to accommodate rename
404411
verifyFsLimitsForRename(fsd, srcIIP, dstIIP);
405412
verifyQuotaForRename(fsd, srcIIP, dstIIP);
@@ -439,10 +446,10 @@ static RenameResult unprotectedRenameTo(FSDirectory fsd,
439446
}
440447
}
441448

442-
if (snapshottableDirs.size() > 0) {
449+
if (dstSnapshottableDirs.size() > 0) {
443450
// There are snapshottable directories (without snapshots) to be
444451
// deleted. Need to update the SnapshotManager.
445-
fsd.getFSNamesystem().removeSnapshottableDirs(snapshottableDirs);
452+
fsd.getFSNamesystem().removeSnapshottableDirs(dstSnapshottableDirs);
446453
}
447454

448455
tx.updateQuotasInSourceTree(bsps);
@@ -556,7 +563,8 @@ private static void validateOverwrite(
556563
}
557564

558565
private static void validateRenameSource(FSDirectory fsd,
559-
INodesInPath srcIIP) throws IOException {
566+
INodesInPath srcIIP, List<INodeDirectory> snapshottableDirs)
567+
throws IOException {
560568
String error;
561569
final INode srcInode = srcIIP.getLastINode();
562570
// validate source
@@ -574,7 +582,32 @@ private static void validateRenameSource(FSDirectory fsd,
574582
}
575583
// srcInode and its subtree cannot contain snapshottable directories with
576584
// snapshots
577-
FSDirSnapshotOp.checkSnapshot(fsd, srcIIP, null);
585+
FSDirSnapshotOp.checkSnapshot(fsd, srcIIP, snapshottableDirs);
586+
}
587+
588+
private static void validateNestSnapshot(FSDirectory fsd, String srcPath,
589+
INodeDirectory dstParent, List<INodeDirectory> snapshottableDirs)
590+
throws SnapshotException {
591+
592+
if (fsd.getFSNamesystem().getSnapshotManager().isAllowNestedSnapshots()) {
593+
return;
594+
}
595+
596+
/*
597+
* snapshottableDirs is a list of snapshottable directory (child of rename
598+
* src) which do not have snapshots yet. If this list is not empty, that
599+
* means rename src has snapshottable descendant directories.
600+
*/
601+
if (snapshottableDirs != null && snapshottableDirs.size() > 0) {
602+
if (fsd.getFSNamesystem().getSnapshotManager()
603+
.isDescendantOfSnapshotRoot(dstParent)) {
604+
String dstPath = dstParent.getFullPathName();
605+
throw new SnapshotException("Unable to rename because " + srcPath
606+
+ " has snapshottable descendant directories and " + dstPath
607+
+ " is a descent of a snapshottable directory, and HDFS does"
608+
+ " not support nested snapshottable directory.");
609+
}
610+
}
578611
}
579612

580613
private static class RenameOperation {

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ void setAllowNestedSnapshots(boolean allowNestedSnapshots) {
155155
this.allowNestedSnapshots = allowNestedSnapshots;
156156
}
157157

158+
public boolean isAllowNestedSnapshots() {
159+
return allowNestedSnapshots;
160+
}
161+
158162
private void checkNestedSnapshottable(INodeDirectory dir, String path)
159163
throws SnapshotException {
160164
if (allowNestedSnapshots) {
@@ -288,6 +292,19 @@ public INodeDirectory getSnapshottableAncestorDir(final INodesInPath iip)
288292
}
289293
}
290294

295+
public boolean isDescendantOfSnapshotRoot(INodeDirectory dir) {
296+
if (dir.isSnapshottable()) {
297+
return true;
298+
} else {
299+
for (INodeDirectory p = dir; p != null; p = p.getParent()) {
300+
if (this.snapshottables.containsValue(p)) {
301+
return true;
302+
}
303+
}
304+
return false;
305+
}
306+
}
307+
291308
/**
292309
* Create a snapshot of the given path.
293310
* It is assumed that the caller will perform synchronization.

hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestRenameWithSnapshots.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,45 @@ public void testRenameAndUpdateSnapshottableDirs() throws Exception {
11091109
assertEquals(bar, dirs[0].getFullPath());
11101110
assertEquals(fooId, dirs[0].getDirStatus().getFileId());
11111111
}
1112-
1112+
1113+
/**
1114+
* Test rename where src has snapshottable descendant directories and
1115+
* dst is a descent of a snapshottable directory. Such case will cause
1116+
* nested snapshot which HDFS currently not fully supported.
1117+
*/
1118+
@Test
1119+
public void testRenameWithNestedSnapshottableDirs() throws Exception {
1120+
final Path sdir1 = new Path("/dir1");
1121+
final Path sdir2 = new Path("/dir2");
1122+
final Path foo = new Path(sdir1, "foo");
1123+
final Path bar = new Path(sdir2, "bar");
1124+
1125+
hdfs.mkdirs(foo);
1126+
hdfs.mkdirs(bar);
1127+
1128+
hdfs.allowSnapshot(foo);
1129+
hdfs.allowSnapshot(sdir2);
1130+
1131+
try {
1132+
hdfs.rename(foo, bar, Rename.OVERWRITE);
1133+
fail("Except exception since " + "Unable to rename because "
1134+
+ foo.toString() + " has snapshottable descendant directories and "
1135+
+ sdir2.toString() + " is a descent of a snapshottable directory, "
1136+
+ "and HDFS does not support nested snapshottable directory.");
1137+
} catch (IOException e) {
1138+
GenericTestUtils.assertExceptionContains("Unable to rename because "
1139+
+ foo.toString() + " has snapshottable descendant directories and "
1140+
+ sdir2.toString() + " is a descent of a snapshottable directory, "
1141+
+ "and HDFS does not support nested snapshottable directory.", e);
1142+
}
1143+
1144+
hdfs.disallowSnapshot(foo);
1145+
hdfs.rename(foo, bar, Rename.OVERWRITE);
1146+
SnapshottableDirectoryStatus[] dirs = fsn.getSnapshottableDirListing();
1147+
assertEquals(1, dirs.length);
1148+
assertEquals(sdir2, dirs[0].getFullPath());
1149+
}
1150+
11131151
/**
11141152
* After rename, delete the snapshot in src
11151153
*/

0 commit comments

Comments
 (0)