Skip to content

Commit 647c6f7

Browse files
committed
HDFS-17839. Rename skips path without valid 'DirectoryWithQuotaFeature' in FSDirRenameOp
1 parent 3f1dc7d commit 647c6f7

File tree

5 files changed

+297
-37
lines changed

5 files changed

+297
-37
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ private static INodesInPath unprotectedMkdir(FSDirectory fsd, long inodeId,
224224
timestamp);
225225

226226
INodesInPath iip = fsd.addLastINode(parent, dir, permission.getPermission(),
227-
true, Optional.empty());
227+
true, Optional.empty(), true);
228228
if (iip != null && aclEntries != null) {
229229
AclStorage.updateINodeAcl(dir, aclEntries, Snapshot.CURRENT_STATE_ID);
230230
}

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

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818
package org.apache.hadoop.hdfs.server.namenode;
1919

20-
import org.apache.commons.lang3.tuple.Pair;
20+
import org.apache.commons.lang3.tuple.Triple;
2121
import org.apache.hadoop.hdfs.protocol.HdfsConstants;
2222
import org.apache.hadoop.util.Preconditions;
2323
import org.apache.hadoop.fs.FileAlreadyExistsException;
@@ -72,18 +72,28 @@ static RenameResult renameToInt(
7272
* Verify quota for rename operation where srcInodes[srcInodes.length-1] moves
7373
* dstInodes[dstInodes.length-1]
7474
*/
75-
private static Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> verifyQuotaForRename(
76-
FSDirectory fsd, INodesInPath src, INodesInPath dst) throws QuotaExceededException {
75+
private static Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> verifyQuotaForRename(
76+
FSDirectory fsd, INodesInPath src, INodesInPath dst, boolean overwrite)
77+
throws QuotaExceededException {
7778
Optional<QuotaCounts> srcDelta = Optional.empty();
7879
Optional<QuotaCounts> dstDelta = Optional.empty();
7980
if (!fsd.getFSNamesystem().isImageLoaded() || fsd.shouldSkipQuotaChecks()) {
8081
// Do not check quota if edits log is still being processed
81-
return Pair.of(srcDelta, dstDelta);
82+
return Triple.of(false, srcDelta, dstDelta);
8283
}
8384
int i = 0;
8485
while (src.getINode(i) == dst.getINode(i)) {
8586
i++;
8687
}
88+
89+
// Verify path without valid 'DirectoryWithQuotaFeature'
90+
// Note: In overwrite scenarios, quota calculation is still required,
91+
// overwrite operations delete existing content, which will affects the root directory's quota.
92+
// (i - 1) is common ancestor inode index.
93+
if (!overwrite && verifyPathWithoutValidQuotaFeature(src, dst, i - 1)) {
94+
return Triple.of(false, srcDelta, dstDelta);
95+
}
96+
8797
// src[i - 1] is the last common ancestor.
8898
BlockStoragePolicySuite bsps = fsd.getBlockStoragePolicySuite();
8999
// Assume dstParent existence check done by callers.
@@ -108,7 +118,7 @@ private static Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> verifyQuotaFor
108118
delta.subtract(counts);
109119
}
110120
FSDirectory.verifyQuota(dst, dst.length() - 1, delta, src.getINode(i - 1));
111-
return Pair.of(srcDelta, dstDelta);
121+
return Triple.of(true, srcDelta, dstDelta);
112122
}
113123

114124
/**
@@ -127,6 +137,25 @@ static void verifyFsLimitsForRename(FSDirectory fsd, INodesInPath srcIIP,
127137
}
128138
}
129139

140+
/**
141+
* Verify that the src and dst path does not contain valid quota feature.
142+
*
143+
* @param src source path.
144+
* @param dst destination path.
145+
* @param index common ancestor inode index.
146+
* @return true if no valid quota feature, otherwise false.
147+
*/
148+
static boolean verifyPathWithoutValidQuotaFeature(INodesInPath src, INodesInPath dst, int index) {
149+
// Excluding root directory
150+
if (!FSDirectory.verifyWithoutQuotaFeature(src, index, 1)) {
151+
return false;
152+
}
153+
if (!FSDirectory.verifyWithoutQuotaFeature(src, src.length() - 2, index - 1)) {
154+
return false;
155+
}
156+
return FSDirectory.verifyWithoutQuotaFeature(dst, dst.length() - 2, index - 1);
157+
}
158+
130159
/**
131160
* <br>
132161
* Note: This is to be used by {@link FSEditLogLoader} only.
@@ -216,10 +245,10 @@ static INodesInPath unprotectedRenameTo(FSDirectory fsd,
216245
fsd.ezManager.checkMoveValidity(srcIIP, dstIIP);
217246
// Ensure dst has quota to accommodate rename
218247
verifyFsLimitsForRename(fsd, srcIIP, dstIIP);
219-
Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> countPair =
220-
verifyQuotaForRename(fsd, srcIIP, dstIIP);
248+
Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> countTriple =
249+
verifyQuotaForRename(fsd, srcIIP, dstIIP, false);
221250

222-
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, countPair);
251+
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, countTriple);
223252

224253
boolean added = false;
225254

@@ -436,10 +465,10 @@ static RenameResult unprotectedRenameTo(FSDirectory fsd,
436465

437466
// Ensure dst has quota to accommodate rename
438467
verifyFsLimitsForRename(fsd, srcIIP, dstIIP);
439-
Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> quotaPair =
440-
verifyQuotaForRename(fsd, srcIIP, dstIIP);
468+
Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> countTriple =
469+
verifyQuotaForRename(fsd, srcIIP, dstIIP, overwrite);
441470

442-
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, quotaPair);
471+
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, countTriple);
443472

444473
boolean undoRemoveSrc = true;
445474
tx.removeSrc();
@@ -656,13 +685,14 @@ private static class RenameOperation {
656685
private final boolean srcChildIsReference;
657686
private final QuotaCounts oldSrcCountsInSnapshot;
658687
private final boolean sameStoragePolicy;
688+
private final boolean updateQuota;
659689
private final Optional<QuotaCounts> srcSubTreeCount;
660690
private final Optional<QuotaCounts> dstSubTreeCount;
661691
private INode srcChild;
662692
private INode oldDstChild;
663693

664694
RenameOperation(FSDirectory fsd, INodesInPath srcIIP, INodesInPath dstIIP,
665-
Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> quotaPair) {
695+
Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> countTriple) {
666696
this.fsd = fsd;
667697
this.srcIIP = srcIIP;
668698
this.dstIIP = dstIIP;
@@ -712,9 +742,10 @@ private static class RenameOperation {
712742
withCount = null;
713743
}
714744
// Set quota for src and dst, ignore src is in Snapshot or is Reference
745+
this.updateQuota = countTriple.getLeft() || withCount != null;
715746
this.srcSubTreeCount = withCount == null ?
716-
quotaPair.getLeft() : Optional.empty();
717-
this.dstSubTreeCount = quotaPair.getRight();
747+
countTriple.getMiddle() : Optional.empty();
748+
this.dstSubTreeCount = countTriple.getRight();
718749
}
719750

720751
boolean isSameStoragePolicy() {
@@ -755,9 +786,11 @@ long removeSrc() throws IOException {
755786
throw new IOException(error);
756787
} else {
757788
// update the quota count if necessary
758-
Optional<QuotaCounts> countOp = sameStoragePolicy ?
759-
srcSubTreeCount : Optional.empty();
760-
fsd.updateCountForDelete(srcChild, srcIIP, countOp);
789+
if (updateQuota) {
790+
Optional<QuotaCounts> countOp = sameStoragePolicy ?
791+
srcSubTreeCount : Optional.empty();
792+
fsd.updateCountForDelete(srcChild, srcIIP, countOp);
793+
}
761794
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null);
762795
return removedNum;
763796
}
@@ -772,9 +805,11 @@ boolean removeSrc4OldRename() {
772805
return false;
773806
} else {
774807
// update the quota count if necessary
775-
Optional<QuotaCounts> countOp = sameStoragePolicy ?
776-
srcSubTreeCount : Optional.empty();
777-
fsd.updateCountForDelete(srcChild, srcIIP, countOp);
808+
if (updateQuota) {
809+
Optional<QuotaCounts> countOp = sameStoragePolicy ?
810+
srcSubTreeCount : Optional.empty();
811+
fsd.updateCountForDelete(srcChild, srcIIP, countOp);
812+
}
778813
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null);
779814
return true;
780815
}
@@ -785,7 +820,9 @@ long removeDst() {
785820
if (removedNum != -1) {
786821
oldDstChild = dstIIP.getLastINode();
787822
// update the quota count if necessary
788-
fsd.updateCountForDelete(oldDstChild, dstIIP, dstSubTreeCount);
823+
if (updateQuota) {
824+
fsd.updateCountForDelete(oldDstChild, dstIIP, dstSubTreeCount);
825+
}
789826
dstIIP = INodesInPath.replace(dstIIP, dstIIP.length() - 1, null);
790827
}
791828
return removedNum;
@@ -803,7 +840,7 @@ INodesInPath addSourceToDestination() {
803840
toDst = new INodeReference.DstReference(dstParent.asDirectory(),
804841
withCount, dstIIP.getLatestSnapshotId());
805842
}
806-
return fsd.addLastINodeNoQuotaCheck(dstParentIIP, toDst, srcSubTreeCount);
843+
return fsd.addLastINodeNoQuotaCheck(dstParentIIP, toDst, srcSubTreeCount, updateQuota);
807844
}
808845

809846
void updateMtimeAndLease(long timestamp) {
@@ -837,7 +874,7 @@ void restoreSource() {
837874
// the srcChild back
838875
Optional<QuotaCounts> countOp = sameStoragePolicy ?
839876
srcSubTreeCount : Optional.empty();
840-
fsd.addLastINodeNoQuotaCheck(srcParentIIP, srcChild, countOp);
877+
fsd.addLastINodeNoQuotaCheck(srcParentIIP, srcChild, countOp, updateQuota);
841878
}
842879
}
843880

@@ -847,7 +884,7 @@ void restoreDst(BlockStoragePolicySuite bsps) {
847884
if (dstParent.isWithSnapshot()) {
848885
dstParent.undoRename4DstParent(bsps, oldDstChild, dstIIP.getLatestSnapshotId());
849886
} else {
850-
fsd.addLastINodeNoQuotaCheck(dstParentIIP, oldDstChild, dstSubTreeCount);
887+
fsd.addLastINodeNoQuotaCheck(dstParentIIP, oldDstChild, dstSubTreeCount, updateQuota);
851888
}
852889
if (oldDstChild != null && oldDstChild.isReference()) {
853890
final INodeReference removedDstRef = oldDstChild.asReference();

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

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,7 @@ INodesInPath addINode(INodesInPath existing, INode child,
11961196
cacheName(child);
11971197
writeLock();
11981198
try {
1199-
return addLastINode(existing, child, modes, true, Optional.empty());
1199+
return addLastINode(existing, child, modes, true, Optional.empty(), true);
12001200
} finally {
12011201
writeUnlock();
12021202
}
@@ -1242,6 +1242,25 @@ static void verifyQuota(INodesInPath iip, int pos, QuotaCounts deltas,
12421242
}
12431243
}
12441244

1245+
/**
1246+
* Verify that the iNodesInPath from the start position to the end position
1247+
* does not contain any valid quota feature.
1248+
*
1249+
* @param iip the INodesInPath instance containing all the INodes for the path.
1250+
* @param start path inode start index.
1251+
* @param end path inode end index.
1252+
* @return true if no valid quota feature, otherwise false.
1253+
*/
1254+
static boolean verifyWithoutQuotaFeature(INodesInPath iip, int start, int end) {
1255+
for (int i = start; i >= end; i--) {
1256+
INodeDirectory d = iip.getINode(i).asDirectory();
1257+
if (d.isWithQuota()) {
1258+
return false;
1259+
}
1260+
}
1261+
return true;
1262+
}
1263+
12451264
/** Verify if the inode name is legal. */
12461265
void verifyINodeName(byte[] childName) throws HadoopIllegalArgumentException {
12471266
if (Arrays.equals(HdfsServerConstants.DOT_SNAPSHOT_DIR_BYTES, childName)) {
@@ -1343,11 +1362,12 @@ private void copyINodeDefaultAcl(INode child, FsPermission modes) {
13431362
* @param inode the new INode to add
13441363
* @param modes create modes
13451364
* @param checkQuota whether to check quota
1365+
* @param updateQuota whether to update quota
13461366
* @return an INodesInPath instance containing the new INode
13471367
*/
13481368
@VisibleForTesting
1349-
public INodesInPath addLastINode(INodesInPath existing, INode inode,
1350-
FsPermission modes, boolean checkQuota, Optional<QuotaCounts> quotaCount)
1369+
public INodesInPath addLastINode(INodesInPath existing, INode inode, FsPermission modes,
1370+
boolean checkQuota, Optional<QuotaCounts> quotaCount, boolean updateQuota)
13511371
throws QuotaExceededException {
13521372
assert existing.getLastINode() != null &&
13531373
existing.getLastINode().isDirectory();
@@ -1379,17 +1399,23 @@ public INodesInPath addLastINode(INodesInPath existing, INode inode,
13791399
// always verify inode name
13801400
verifyINodeName(inode.getLocalNameBytes());
13811401

1382-
final QuotaCounts counts = quotaCount.orElseGet(() -> inode.
1383-
computeQuotaUsage(getBlockStoragePolicySuite(),
1384-
parent.getStoragePolicyID(), false,
1385-
Snapshot.CURRENT_STATE_ID));
1386-
updateCount(existing, pos, counts, checkQuota);
1402+
QuotaCounts counts = null;
1403+
if (updateQuota) {
1404+
counts = quotaCount.orElseGet(() -> inode.
1405+
computeQuotaUsage(getBlockStoragePolicySuite(),
1406+
parent.getStoragePolicyID(), false,
1407+
Snapshot.CURRENT_STATE_ID));
1408+
updateCount(existing, pos, counts, checkQuota);
1409+
}
13871410

13881411
boolean isRename = (inode.getParent() != null);
13891412
final boolean added = parent.addChild(inode, true,
13901413
existing.getLatestSnapshotId());
13911414
if (!added) {
1392-
updateCountNoQuotaCheck(existing, pos, counts.negation());
1415+
// When adding INode fails, if 'updateQuota' is true, rollback quota.
1416+
if (updateQuota) {
1417+
updateCountNoQuotaCheck(existing, pos, counts.negation());
1418+
}
13931419
return null;
13941420
} else {
13951421
if (!isRename) {
@@ -1401,10 +1427,10 @@ public INodesInPath addLastINode(INodesInPath existing, INode inode,
14011427
}
14021428

14031429
INodesInPath addLastINodeNoQuotaCheck(INodesInPath existing, INode i,
1404-
Optional<QuotaCounts> quotaCount) {
1430+
Optional<QuotaCounts> quotaCount, boolean updateQuota) {
14051431
try {
14061432
// All callers do not have create modes to pass.
1407-
return addLastINode(existing, i, null, false, quotaCount);
1433+
return addLastINode(existing, i, null, false, quotaCount, updateQuota);
14081434
} catch (QuotaExceededException e) {
14091435
NameNode.LOG.warn("FSDirectory.addChildNoQuotaCheck - unexpected", e);
14101436
}

0 commit comments

Comments
 (0)