-
Notifications
You must be signed in to change notification settings - Fork 9.1k
HDFS-17839. Rename skips path without valid 'DirectoryWithQuotaFeature' in FSDirRenameOp #7989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from 2 commits
c0040fe
3c60bf0
1e9321b
8f570c9
4f46b2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ | |
*/ | ||
package org.apache.hadoop.hdfs.server.namenode; | ||
|
||
import org.apache.commons.lang3.tuple.Pair; | ||
import org.apache.commons.lang3.tuple.Triple; | ||
import org.apache.hadoop.hdfs.protocol.HdfsConstants; | ||
import org.apache.hadoop.util.Preconditions; | ||
import org.apache.hadoop.fs.FileAlreadyExistsException; | ||
|
@@ -72,14 +72,24 @@ static RenameResult renameToInt( | |
* Verify quota for rename operation where srcInodes[srcInodes.length-1] moves | ||
* dstInodes[dstInodes.length-1] | ||
*/ | ||
private static Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> verifyQuotaForRename( | ||
FSDirectory fsd, INodesInPath src, INodesInPath dst) throws QuotaExceededException { | ||
private static Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> verifyQuotaForRename( | ||
FSDirectory fsd, INodesInPath src, INodesInPath dst, boolean overwrite) | ||
throws QuotaExceededException { | ||
Optional<QuotaCounts> srcDelta = Optional.empty(); | ||
Optional<QuotaCounts> dstDelta = Optional.empty(); | ||
if (!fsd.getFSNamesystem().isImageLoaded() || fsd.shouldSkipQuotaChecks()) { | ||
// Do not check quota if edits log is still being processed | ||
return Pair.of(srcDelta, dstDelta); | ||
return Triple.of(false, srcDelta, dstDelta); | ||
} | ||
|
||
// Verify path without valid 'DirectoryWithQuotaFeature' | ||
// Note: In overwrite scenarios, quota calculation is still required, | ||
// overwrite operations delete existing content, which affects the root directory's quota. | ||
if (!overwrite && FSDirectory.verifyPathWithoutValidFeature(src) | ||
&& FSDirectory.verifyPathWithoutValidFeature(dst)) { | ||
return Triple.of(false, srcDelta, dstDelta); | ||
} | ||
|
||
int i = 0; | ||
while (src.getINode(i) == dst.getINode(i)) { | ||
i++; | ||
|
@@ -108,7 +118,7 @@ private static Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> verifyQuotaFor | |
delta.subtract(counts); | ||
} | ||
FSDirectory.verifyQuota(dst, dst.length() - 1, delta, src.getINode(i - 1)); | ||
return Pair.of(srcDelta, dstDelta); | ||
return Triple.of(true, srcDelta, dstDelta); | ||
} | ||
|
||
/** | ||
|
@@ -216,10 +226,10 @@ static INodesInPath unprotectedRenameTo(FSDirectory fsd, | |
fsd.ezManager.checkMoveValidity(srcIIP, dstIIP); | ||
// Ensure dst has quota to accommodate rename | ||
verifyFsLimitsForRename(fsd, srcIIP, dstIIP); | ||
Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> countPair = | ||
verifyQuotaForRename(fsd, srcIIP, dstIIP); | ||
Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> countTriple = | ||
verifyQuotaForRename(fsd, srcIIP, dstIIP, false); | ||
|
||
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, countPair); | ||
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, countTriple); | ||
|
||
boolean added = false; | ||
|
||
|
@@ -436,10 +446,10 @@ static RenameResult unprotectedRenameTo(FSDirectory fsd, | |
|
||
// Ensure dst has quota to accommodate rename | ||
verifyFsLimitsForRename(fsd, srcIIP, dstIIP); | ||
Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> quotaPair = | ||
verifyQuotaForRename(fsd, srcIIP, dstIIP); | ||
Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> countTriple = | ||
verifyQuotaForRename(fsd, srcIIP, dstIIP, overwrite); | ||
|
||
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, quotaPair); | ||
RenameOperation tx = new RenameOperation(fsd, srcIIP, dstIIP, countTriple); | ||
|
||
boolean undoRemoveSrc = true; | ||
tx.removeSrc(); | ||
|
@@ -656,13 +666,14 @@ private static class RenameOperation { | |
private final boolean srcChildIsReference; | ||
private final QuotaCounts oldSrcCountsInSnapshot; | ||
private final boolean sameStoragePolicy; | ||
private final boolean updateQuota; | ||
private final Optional<QuotaCounts> srcSubTreeCount; | ||
private final Optional<QuotaCounts> dstSubTreeCount; | ||
private INode srcChild; | ||
private INode oldDstChild; | ||
|
||
RenameOperation(FSDirectory fsd, INodesInPath srcIIP, INodesInPath dstIIP, | ||
Pair<Optional<QuotaCounts>, Optional<QuotaCounts>> quotaPair) { | ||
Triple<Boolean, Optional<QuotaCounts>, Optional<QuotaCounts>> quotaPair) { | ||
this.fsd = fsd; | ||
this.srcIIP = srcIIP; | ||
this.dstIIP = dstIIP; | ||
|
@@ -712,8 +723,9 @@ private static class RenameOperation { | |
withCount = null; | ||
} | ||
// Set quota for src and dst, ignore src is in Snapshot or is Reference | ||
this.updateQuota = quotaPair.getLeft() || isSrcInSnapshot || srcChildIsReference; | ||
this.srcSubTreeCount = withCount == null ? | ||
quotaPair.getLeft() : Optional.empty(); | ||
quotaPair.getMiddle() : Optional.empty(); | ||
this.dstSubTreeCount = quotaPair.getRight(); | ||
} | ||
|
||
|
||
|
@@ -755,9 +767,11 @@ long removeSrc() throws IOException { | |
throw new IOException(error); | ||
} else { | ||
// update the quota count if necessary | ||
Optional<QuotaCounts> countOp = sameStoragePolicy ? | ||
srcSubTreeCount : Optional.empty(); | ||
fsd.updateCountForDelete(srcChild, srcIIP, countOp); | ||
if (updateQuota) { | ||
Optional<QuotaCounts> countOp = sameStoragePolicy ? | ||
srcSubTreeCount : Optional.empty(); | ||
fsd.updateCountForDelete(srcChild, srcIIP, countOp); | ||
} | ||
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null); | ||
return removedNum; | ||
} | ||
|
@@ -772,9 +786,11 @@ boolean removeSrc4OldRename() { | |
return false; | ||
} else { | ||
// update the quota count if necessary | ||
Optional<QuotaCounts> countOp = sameStoragePolicy ? | ||
srcSubTreeCount : Optional.empty(); | ||
fsd.updateCountForDelete(srcChild, srcIIP, countOp); | ||
if (updateQuota) { | ||
Optional<QuotaCounts> countOp = sameStoragePolicy ? | ||
srcSubTreeCount : Optional.empty(); | ||
fsd.updateCountForDelete(srcChild, srcIIP, countOp); | ||
} | ||
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null); | ||
return true; | ||
} | ||
|
@@ -785,7 +801,9 @@ long removeDst() { | |
if (removedNum != -1) { | ||
oldDstChild = dstIIP.getLastINode(); | ||
// update the quota count if necessary | ||
fsd.updateCountForDelete(oldDstChild, dstIIP, dstSubTreeCount); | ||
if (updateQuota) { | ||
fsd.updateCountForDelete(oldDstChild, dstIIP, dstSubTreeCount); | ||
} | ||
dstIIP = INodesInPath.replace(dstIIP, dstIIP.length() - 1, null); | ||
} | ||
return removedNum; | ||
|
@@ -803,7 +821,7 @@ INodesInPath addSourceToDestination() { | |
toDst = new INodeReference.DstReference(dstParent.asDirectory(), | ||
withCount, dstIIP.getLatestSnapshotId()); | ||
} | ||
return fsd.addLastINodeNoQuotaCheck(dstParentIIP, toDst, srcSubTreeCount); | ||
return fsd.addLastINodeNoQuotaCheck(dstParentIIP, toDst, srcSubTreeCount, updateQuota); | ||
} | ||
|
||
void updateMtimeAndLease(long timestamp) { | ||
|
@@ -837,7 +855,7 @@ void restoreSource() { | |
// the srcChild back | ||
Optional<QuotaCounts> countOp = sameStoragePolicy ? | ||
srcSubTreeCount : Optional.empty(); | ||
fsd.addLastINodeNoQuotaCheck(srcParentIIP, srcChild, countOp); | ||
fsd.addLastINodeNoQuotaCheck(srcParentIIP, srcChild, countOp, updateQuota); | ||
} | ||
} | ||
|
||
|
@@ -847,7 +865,7 @@ void restoreDst(BlockStoragePolicySuite bsps) { | |
if (dstParent.isWithSnapshot()) { | ||
dstParent.undoRename4DstParent(bsps, oldDstChild, dstIIP.getLatestSnapshotId()); | ||
} else { | ||
fsd.addLastINodeNoQuotaCheck(dstParentIIP, oldDstChild, dstSubTreeCount); | ||
fsd.addLastINodeNoQuotaCheck(dstParentIIP, oldDstChild, dstSubTreeCount, updateQuota); | ||
} | ||
if (oldDstChild != null && oldDstChild.isReference()) { | ||
final INodeReference removedDstRef = oldDstChild.asReference(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1196,7 +1196,7 @@ INodesInPath addINode(INodesInPath existing, INode child, | |
cacheName(child); | ||
writeLock(); | ||
try { | ||
return addLastINode(existing, child, modes, true, Optional.empty()); | ||
return addLastINode(existing, child, modes, true, Optional.empty(), true); | ||
} finally { | ||
writeUnlock(); | ||
} | ||
|
@@ -1242,6 +1242,24 @@ static void verifyQuota(INodesInPath iip, int pos, QuotaCounts deltas, | |
} | ||
} | ||
|
||
/** | ||
* Verify that the path from the specified position to the root | ||
* (excluding the root itself) does not contain any valid quota features. | ||
* | ||
* @param iip the INodesInPath containing all the ancestral INodes. | ||
* @return true if no valid features are found along the path, | ||
* false if any directory in the path has an active feature. | ||
*/ | ||
static boolean verifyPathWithoutValidFeature(INodesInPath iip) { | ||
for (int i = iip.length() - 2; i >= 1; i--) { | ||
INodeDirectory d = iip.getINode(i).asDirectory(); | ||
if (d.isWithQuota()) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** Verify if the inode name is legal. */ | ||
void verifyINodeName(byte[] childName) throws HadoopIllegalArgumentException { | ||
if (Arrays.equals(HdfsServerConstants.DOT_SNAPSHOT_DIR_BYTES, childName)) { | ||
|
@@ -1343,11 +1361,12 @@ private void copyINodeDefaultAcl(INode child, FsPermission modes) { | |
* @param inode the new INode to add | ||
* @param modes create modes | ||
* @param checkQuota whether to check quota | ||
* @param updateQuota whether to update quota | ||
* @return an INodesInPath instance containing the new INode | ||
*/ | ||
@VisibleForTesting | ||
public INodesInPath addLastINode(INodesInPath existing, INode inode, | ||
FsPermission modes, boolean checkQuota, Optional<QuotaCounts> quotaCount) | ||
public INodesInPath addLastINode(INodesInPath existing, INode inode, FsPermission modes, | ||
boolean checkQuota, Optional<QuotaCounts> quotaCount, boolean updateQuota) | ||
throws QuotaExceededException { | ||
assert existing.getLastINode() != null && | ||
existing.getLastINode().isDirectory(); | ||
|
@@ -1379,16 +1398,22 @@ public INodesInPath addLastINode(INodesInPath existing, INode inode, | |
// always verify inode name | ||
verifyINodeName(inode.getLocalNameBytes()); | ||
|
||
final QuotaCounts counts = quotaCount.orElseGet(() -> inode. | ||
computeQuotaUsage(getBlockStoragePolicySuite(), | ||
parent.getStoragePolicyID(), false, | ||
Snapshot.CURRENT_STATE_ID)); | ||
updateCount(existing, pos, counts, checkQuota); | ||
if (updateQuota) { | ||
QuotaCounts counts = quotaCount.orElseGet(() -> inode. | ||
computeQuotaUsage(getBlockStoragePolicySuite(), | ||
parent.getStoragePolicyID(), false, | ||
Snapshot.CURRENT_STATE_ID)); | ||
updateCount(existing, pos, counts, checkQuota); | ||
} | ||
|
||
boolean isRename = (inode.getParent() != null); | ||
final boolean added = parent.addChild(inode, true, | ||
existing.getLatestSnapshotId()); | ||
if (!added) { | ||
if (!added && updateQuota) { | ||
|
||
QuotaCounts counts = quotaCount.orElseGet(() -> inode. | ||
computeQuotaUsage(getBlockStoragePolicySuite(), | ||
parent.getStoragePolicyID(), false, | ||
Snapshot.CURRENT_STATE_ID)); | ||
|
||
updateCountNoQuotaCheck(existing, pos, counts.negation()); | ||
return null; | ||
} else { | ||
|
@@ -1401,10 +1426,10 @@ public INodesInPath addLastINode(INodesInPath existing, INode inode, | |
} | ||
|
||
INodesInPath addLastINodeNoQuotaCheck(INodesInPath existing, INode i, | ||
Optional<QuotaCounts> quotaCount) { | ||
Optional<QuotaCounts> quotaCount, boolean updateQuota) { | ||
try { | ||
// All callers do not have create modes to pass. | ||
return addLastINode(existing, i, null, false, quotaCount); | ||
return addLastINode(existing, i, null, false, quotaCount, updateQuota); | ||
} catch (QuotaExceededException e) { | ||
NameNode.LOG.warn("FSDirectory.addChildNoQuotaCheck - unexpected", e); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method signature change from Pair to Triple with a Boolean parameter lacks clear documentation. The Boolean return value's meaning is not obvious from the method name or parameters. Consider renaming the method or adding clear JavaDoc to explain what the Boolean represents.
Copilot uses AI. Check for mistakes.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.