From 39b2d3f5f2e539d1a53dee87d53a1ef5c65d8fff Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Thu, 18 Jun 2026 15:15:36 -0700 Subject: [PATCH] HDDS-14435. Fix snapshotUsedNamespace underflow when FSO directory is deleted and purged Change-Id: I36ed18620c9aeecf53def5a8ad10bafa2ee567ad --- .../key/OMKeyDeleteRequestWithFSO.java | 2 +- .../ozone/om/request/OMRequestTestUtils.java | 7 ++ .../key/TestOMKeyDeleteRequestWithFSO.java | 69 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java index 4737b85373db..efe04a804b95 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java @@ -162,7 +162,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, Execut // Empty entries won't be added to deleted table so this key shouldn't get added to snapshotUsed space. boolean isKeyNonEmpty = !OmKeyInfo.isKeyEmpty(omKeyInfo); omBucketInfo.decrUsedBytes(quotaReleased, isKeyNonEmpty); - omBucketInfo.decrUsedNamespace(1L, isKeyNonEmpty); + omBucketInfo.decrUsedNamespace(1L, isKeyNonEmpty || keyStatus.isDirectory()); // If omKeyInfo has hsync metadata, delete its corresponding open key as well String dbOpenKey = null; diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java index c7e80f166ae9..0a7ad4862352 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java @@ -1030,6 +1030,13 @@ public static String deleteDir(String ozoneKey, String volume, String bucket, omDirectoryInfo.getName()); omMetadataManager.getDeletedDirTable().put(ozoneKey, omKeyInfo); omMetadataManager.getDirectoryTable().delete(ozoneKey); + + String bucketKey = omMetadataManager.getBucketKey(volume, bucket); + OmBucketInfo omBucketInfo = omMetadataManager.getBucketTable().get(bucketKey); + if (omBucketInfo != null) { + omBucketInfo.decrUsedNamespace(1L, true); + omMetadataManager.getBucketTable().put(bucketKey, omBucketInfo); + } return ozoneKey; } diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequestWithFSO.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequestWithFSO.java index c537b09c85b8..cbaa63446991 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequestWithFSO.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequestWithFSO.java @@ -34,6 +34,7 @@ import org.apache.hadoop.ozone.om.OzonePrefixPathImpl; import org.apache.hadoop.ozone.om.exceptions.OMException; import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus; @@ -333,4 +334,72 @@ public void testDeleteParentAfterChildDeleted() throws Exception { assertEquals(OzoneManagerProtocolProtos.Status.OK, response.getOMResponse().getStatus(), "Parent delete should succeed after children deleted"); } + + @Test + public void testSnapshotUsedNamespaceAfterDirectoryDeleteAndPurge() throws Exception { + OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName, omMetadataManager, getBucketLayout()); + + String dirName = "dir1"; + String dirKeyPath = addKeyToDirTable(volumeName, bucketName, dirName); + + long parentObjectID = 0L; + long dirObjectID = 12345L; + OmDirectoryInfo omDirectoryInfo = OMRequestTestUtils.createOmDirectoryInfo(dirName, dirObjectID, parentObjectID); + omMetadataManager.getDirectoryTable().put(dirKeyPath, omDirectoryInfo); + + String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName); + OmBucketInfo omBucketInfo = omMetadataManager.getBucketTable().get(bucketKey); + assertNotNull(omBucketInfo); + // Initialize used namespace and snapshot used namespace for test predictability + omBucketInfo.incrUsedNamespace(1); + omMetadataManager.getBucketTable().put(bucketKey, omBucketInfo); + + // Delete the directory + long txnId = 100L; + OMRequest deleteRequest = doPreExecute(createDeleteKeyRequest(dirName, false)); + OMKeyDeleteRequest omKeyDeleteRequest = getOmKeyDeleteRequest(deleteRequest); + OMClientResponse deleteResponse = omKeyDeleteRequest.validateAndUpdateCache(ozoneManager, txnId++); + assertEquals(OzoneManagerProtocolProtos.Status.OK, deleteResponse.getOMResponse().getStatus()); + + OmBucketInfo bucketInfoAfterDelete = omMetadataManager.getBucketTable().get(bucketKey); + + // Perform purge + OzoneManagerProtocolProtos.PurgeDirectoriesRequest.Builder purgeDirRequest = + OzoneManagerProtocolProtos.PurgeDirectoriesRequest.newBuilder(); + + long volumeId = omMetadataManager.getVolumeId(volumeName); + long bucketId = bucketInfoAfterDelete.getObjectID(); + + OzoneManagerProtocolProtos.PurgePathRequest purgePathRequest = + OzoneManagerProtocolProtos.PurgePathRequest.newBuilder() + .setVolumeId(volumeId) + .setBucketId(bucketId) + .setDeletedDir(dirKeyPath) + .build(); + + purgeDirRequest.addDeletedPath(purgePathRequest); + purgeDirRequest.addBucketNameInfos( + OzoneManagerProtocolProtos.BucketNameInfo.newBuilder() + .setVolumeName(volumeName) + .setBucketName(bucketName) + .setBucketId(bucketId) + .setVolumeId(volumeId) + .build()); + + OMRequest purgeRequest = OMRequest.newBuilder() + .setCmdType(OzoneManagerProtocolProtos.Type.PurgeDirectories) + .setPurgeDirectoriesRequest(purgeDirRequest) + .setClientId(UUID.randomUUID().toString()) + .build(); + + OMDirectoriesPurgeRequestWithFSO omPurgeRequest = new OMDirectoriesPurgeRequestWithFSO(purgeRequest); + OMClientResponse purgeResponse = omPurgeRequest.validateAndUpdateCache(ozoneManager, txnId); + assertEquals(OzoneManagerProtocolProtos.Status.OK, purgeResponse.getOMResponse().getStatus()); + + OmBucketInfo bucketInfoAfterPurge = omMetadataManager.getBucketTable().get(bucketKey); + + // We expect snapshotUsedNamespace to not go negative + assertTrue(bucketInfoAfterPurge.getSnapshotUsedNamespace() >= 0, + "SnapshotUsedNamespace went negative (" + bucketInfoAfterPurge.getSnapshotUsedNamespace() + ") due to bug."); + } }