This is an automated email from the ASF dual-hosted git repository.

sammichen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 9c932b01221 HDDS-13664. Persist pendingDeleteBytes only when feature 
is finalized. (#9330)
9c932b01221 is described below

commit 9c932b01221300510eadd746ebad9e95cc523b2f
Author: Priyesh Karatha <[email protected]>
AuthorDate: Thu Nov 20 18:30:51 2025 +0530

    HDDS-13664. Persist pendingDeleteBytes only when feature is finalized. 
(#9330)
---
 .../commandhandler/DeleteBlocksCommandHandler.java |  23 +-
 .../container/keyvalue/KeyValueContainerData.java  |  12 +-
 .../KeyValueContainerMetadataInspector.java        |  54 ++--
 .../ozone/container/keyvalue/PendingDelete.java    |   2 +-
 .../keyvalue/helpers/KeyValueContainerUtil.java    | 143 +++++----
 .../TestDNDataDistributionFinalization.java        | 326 +++++++++++++++++++++
 6 files changed, 467 insertions(+), 93 deletions(-)

diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/DeleteBlocksCommandHandler.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/DeleteBlocksCommandHandler.java
index 6d59e812f93..78a8db03c6b 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/DeleteBlocksCommandHandler.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/DeleteBlocksCommandHandler.java
@@ -45,6 +45,7 @@
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
 import org.apache.hadoop.hdds.utils.db.BatchOperation;
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.metrics2.lib.MetricsRegistry;
@@ -64,6 +65,7 @@
 import org.apache.hadoop.ozone.container.keyvalue.helpers.BlockUtils;
 import org.apache.hadoop.ozone.container.metadata.DeleteTransactionStore;
 import org.apache.hadoop.ozone.container.ozoneimpl.OzoneContainer;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
 import org.apache.hadoop.ozone.protocol.commands.CommandStatus;
 import org.apache.hadoop.ozone.protocol.commands.DeleteBlockCommandStatus;
 import org.apache.hadoop.ozone.protocol.commands.DeleteBlocksCommand;
@@ -643,14 +645,19 @@ private void updateMetaData(KeyValueContainerData 
containerData,
               containerData.getPendingDeleteBlockCountKey(),
               pendingDeleteBlocks);
 
-      // update pending deletion blocks count and delete transaction ID in
-      // in-memory container status
-      long pendingBytes = containerData.getBlockPendingDeletionBytes() + 
delTX.getTotalBlockSize();
-      metadataTable
-          .putWithBatch(batchOperation,
-              containerData.getPendingDeleteBlockBytesKey(),
-              pendingBytes);
-      containerData.incrPendingDeletionBlocks(newDeletionBlocks, 
delTX.getTotalBlockSize());
+      // Update pending deletion blocks count, blocks bytes and delete 
transaction ID in in-memory container status.
+      // Persist pending bytes only if the feature is finalized.
+      if (VersionedDatanodeFeatures.isFinalized(
+          HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION) && 
delTX.hasTotalBlockSize()) {
+        long pendingBytes = containerData.getBlockPendingDeletionBytes();
+        pendingBytes += delTX.getTotalBlockSize();
+        metadataTable
+            .putWithBatch(batchOperation,
+                containerData.getPendingDeleteBlockBytesKey(),
+                pendingBytes);
+      }
+      containerData.incrPendingDeletionBlocks(newDeletionBlocks,
+          delTX.hasTotalBlockSize() ? delTX.getTotalBlockSize() : 0);
       containerData.updateDeleteTransactionId(delTX.getTxID());
     }
   }
diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
index 800424076f9..e80655e0248 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
@@ -51,6 +51,7 @@
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerDataProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
 import org.apache.hadoop.hdds.utils.MetadataKeyFilters.KeyPrefixFilter;
 import org.apache.hadoop.hdds.utils.db.BatchOperation;
 import org.apache.hadoop.hdds.utils.db.Table;
@@ -58,6 +59,7 @@
 import org.apache.hadoop.ozone.container.common.impl.ContainerLayoutVersion;
 import org.apache.hadoop.ozone.container.common.interfaces.DBHandle;
 import 
org.apache.hadoop.ozone.container.keyvalue.helpers.KeyValueContainerUtil;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
 import org.yaml.snakeyaml.nodes.Tag;
 
 /**
@@ -385,8 +387,10 @@ public void updateAndCommitDBCounters(DBHandle db,
     metadataTable.putWithBatch(batchOperation, getBlockCountKey(), 
b.getCount() - deletedBlockCount);
     metadataTable.putWithBatch(batchOperation, getPendingDeleteBlockCountKey(),
         b.getPendingDeletion() - deletedBlockCount);
-    metadataTable.putWithBatch(batchOperation, getPendingDeleteBlockBytesKey(),
-        b.getPendingDeletionBytes() - releasedBytes);
+    if 
(VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION))
 {
+      metadataTable.putWithBatch(batchOperation, 
getPendingDeleteBlockBytesKey(),
+          b.getPendingDeletionBytes() - releasedBytes);
+    }
 
     db.getStore().getBatchHandler().commitBatchOperation(batchOperation);
   }
@@ -397,7 +401,9 @@ public void resetPendingDeleteBlockCount(DBHandle db) 
throws IOException {
     // Reset the metadata on disk.
     Table<String, Long> metadataTable = db.getStore().getMetadataTable();
     metadataTable.put(getPendingDeleteBlockCountKey(), 0L);
-    metadataTable.put(getPendingDeleteBlockBytesKey(), 0L);
+    if 
(VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION))
 {
+      metadataTable.put(getPendingDeleteBlockBytesKey(), 0L);
+    }
   }
 
   // NOTE: Below are some helper functions to format keys according
diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerMetadataInspector.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerMetadataInspector.java
index bf3c3909f1c..08e6b40039a 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerMetadataInspector.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerMetadataInspector.java
@@ -34,6 +34,7 @@
 import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
 import org.apache.hadoop.hdds.server.JsonUtils;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.hdds.utils.db.TableIterator;
 import org.apache.hadoop.ozone.OzoneConsts;
@@ -45,6 +46,7 @@
 import org.apache.hadoop.ozone.container.metadata.DatanodeStoreSchemaThreeImpl;
 import org.apache.hadoop.ozone.container.metadata.DatanodeStoreSchemaTwoImpl;
 import 
org.apache.hadoop.ozone.container.metadata.DatanodeStoreWithIncrementalChunkList;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -239,8 +241,10 @@ static ObjectNode getDBMetadataJson(Table<String, Long> 
metadataTable,
         metadataTable.get(containerData.getBytesUsedKey()));
     dBMetadata.put(OzoneConsts.PENDING_DELETE_BLOCK_COUNT,
         metadataTable.get(containerData.getPendingDeleteBlockCountKey()));
-    dBMetadata.put(OzoneConsts.PENDING_DELETE_BLOCK_BYTES,
-        metadataTable.get(containerData.getPendingDeleteBlockBytesKey()));
+    if (metadataTable.get(containerData.getPendingDeleteBlockBytesKey()) != 
null) {
+      dBMetadata.put(OzoneConsts.PENDING_DELETE_BLOCK_BYTES,
+          metadataTable.get(containerData.getPendingDeleteBlockBytesKey()));
+    }
     dBMetadata.put(OzoneConsts.DELETE_TRANSACTION_KEY,
         metadataTable.get(containerData.getLatestDeleteTxnKey()));
     dBMetadata.put(OzoneConsts.BLOCK_COMMIT_SEQUENCE_ID,
@@ -434,28 +438,30 @@ private boolean checkAndRepair(ObjectNode parent,
       errors.add(deleteCountError);
     }
 
-    // check and repair if db delete bytes mismatches delete transaction
-    JsonNode pendingDeletionBlockSize = dBMetadata.path(
-        OzoneConsts.PENDING_DELETE_BLOCK_BYTES);
-    final long dbDeleteBytes = jsonToLong(pendingDeletionBlockSize);
-    final JsonNode pendingDeleteBytesAggregate = 
aggregates.path(PendingDelete.BYTES);
-    final long deleteTransactionBytes = 
jsonToLong(pendingDeleteBytesAggregate);
-    if (dbDeleteBytes != deleteTransactionBytes) {
-      passed = false;
-      final BooleanSupplier deleteBytesRepairAction = () -> {
-        final String key = containerData.getPendingDeleteBlockBytesKey();
-        try {
-          metadataTable.put(key, deleteTransactionBytes);
-        } catch (IOException ex) {
-          LOG.error("Failed to reset {} for container {}.",
-              key, containerData.getContainerID(), ex);
-        }
-        return false;
-      };
-      final ObjectNode deleteBytesError = buildErrorAndRepair(
-          "dBMetadata." + OzoneConsts.PENDING_DELETE_BLOCK_BYTES,
-          pendingDeleteBytesAggregate, pendingDeletionBlockSize, 
deleteBytesRepairAction);
-      errors.add(deleteBytesError);
+    if 
(VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION))
 {
+      // check and repair if db delete bytes mismatches delete transaction
+      JsonNode pendingDeletionBlockSize = dBMetadata.path(
+          OzoneConsts.PENDING_DELETE_BLOCK_BYTES);
+      final long dbDeleteBytes = jsonToLong(pendingDeletionBlockSize);
+      final JsonNode pendingDeleteBytesAggregate = 
aggregates.path(PendingDelete.BYTES);
+      final long deleteTransactionBytes = 
jsonToLong(pendingDeleteBytesAggregate);
+      if (dbDeleteBytes != deleteTransactionBytes) {
+        passed = false;
+        final BooleanSupplier deleteBytesRepairAction = () -> {
+          final String key = containerData.getPendingDeleteBlockBytesKey();
+          try {
+            metadataTable.put(key, deleteTransactionBytes);
+          } catch (IOException ex) {
+            LOG.error("Failed to reset {} for container {}.",
+                key, containerData.getContainerID(), ex);
+          }
+          return false;
+        };
+        final ObjectNode deleteBytesError = buildErrorAndRepair(
+            "dBMetadata." + OzoneConsts.PENDING_DELETE_BLOCK_BYTES,
+            pendingDeleteBytesAggregate, pendingDeletionBlockSize, 
deleteBytesRepairAction);
+        errors.add(deleteBytesError);
+      }
     }
 
     // check and repair chunks dir.
diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/PendingDelete.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/PendingDelete.java
index 0f72d3f37c8..f3d518ae6cc 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/PendingDelete.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/PendingDelete.java
@@ -29,7 +29,7 @@ public class PendingDelete {
   private final long count;
   private final long bytes;
 
-  PendingDelete(long count, long bytes) {
+  public PendingDelete(long count, long bytes) {
     this.count = count;
     this.bytes = bytes;
   }
diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/KeyValueContainerUtil.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/KeyValueContainerUtil.java
index 13a01acd491..1dc699b2d2e 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/KeyValueContainerUtil.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/KeyValueContainerUtil.java
@@ -31,6 +31,7 @@
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerChecksumInfo;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.container.checksum.ContainerChecksumTreeManager;
@@ -47,6 +48,7 @@
 import org.apache.hadoop.ozone.container.metadata.DatanodeStore;
 import org.apache.hadoop.ozone.container.metadata.DatanodeStoreSchemaOneImpl;
 import org.apache.hadoop.ozone.container.metadata.DatanodeStoreSchemaTwoImpl;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -318,69 +320,24 @@ private static void populateContainerMetadata(
       throws IOException {
     Table<String, Long> metadataTable = store.getMetadataTable();
 
-    // Set pending deleted block count.
-    final long blockPendingDeletion;
-    long blockPendingDeletionBytes = 0L;
-    Long pendingDeletionBlockBytes = metadataTable.get(kvContainerData
-        .getPendingDeleteBlockBytesKey());
-    Long pendingDeleteBlockCount =
-        metadataTable.get(kvContainerData
-            .getPendingDeleteBlockCountKey());
-    if (pendingDeleteBlockCount != null) {
-      blockPendingDeletion = pendingDeleteBlockCount;
-      if (pendingDeletionBlockBytes != null) {
-        blockPendingDeletionBytes = pendingDeletionBlockBytes;
-      } else {
-        LOG.warn("Missing pendingDeleteBlocksize from {}: recalculate them 
from delete txn tables",
-            metadataTable.getName());
-        PendingDelete pendingDeletions = getAggregatePendingDelete(
-            store, kvContainerData, kvContainerData.getSchemaVersion());
-        blockPendingDeletionBytes = pendingDeletions.getBytes();
-      }
-    } else {
-      LOG.warn("Missing pendingDeleteBlockCount/size from {}: recalculate them 
from delete txn tables",
-          metadataTable.getName());
-      PendingDelete pendingDeletions = getAggregatePendingDelete(
-          store, kvContainerData, kvContainerData.getSchemaVersion());
-      blockPendingDeletion = pendingDeletions.getCount();
-      blockPendingDeletionBytes = pendingDeletions.getBytes();
-    }
-    // Set delete transaction id.
-    Long delTxnId =
-        metadataTable.get(kvContainerData.getLatestDeleteTxnKey());
+    // Set pending deleted block count and bytes
+    PendingDelete pendingDeletions = 
populatePendingDeletionMetadata(kvContainerData, metadataTable, store);
+
+    // Set delete transaction id
+    Long delTxnId = metadataTable.get(kvContainerData.getLatestDeleteTxnKey());
     if (delTxnId != null) {
-      kvContainerData
-          .updateDeleteTransactionId(delTxnId);
+      kvContainerData.updateDeleteTransactionId(delTxnId);
     }
 
-    // Set BlockCommitSequenceId.
-    Long bcsId = metadataTable.get(
-        kvContainerData.getBcsIdKey());
+    // Set BlockCommitSequenceId
+    Long bcsId = metadataTable.get(kvContainerData.getBcsIdKey());
     if (bcsId != null) {
-      kvContainerData
-          .updateBlockCommitSequenceId(bcsId);
-    }
-
-    // Set bytes used.
-    // commitSpace for Open Containers relies on usedBytes
-    final long blockBytes;
-    final long blockCount;
-    final Long metadataTableBytesUsed = 
metadataTable.get(kvContainerData.getBytesUsedKey());
-    // Set block count.
-    final Long metadataTableBlockCount = 
metadataTable.get(kvContainerData.getBlockCountKey());
-    if (metadataTableBytesUsed != null && metadataTableBlockCount != null) {
-      blockBytes = metadataTableBytesUsed;
-      blockCount = metadataTableBlockCount;
-    } else {
-      LOG.warn("Missing bytesUsed={} or blockCount={} from {}: recalculate 
them from block table",
-          metadataTableBytesUsed, metadataTableBlockCount, 
metadataTable.getName());
-      final ContainerData.BlockByteAndCounts b = 
getUsedBytesAndBlockCount(store, kvContainerData);
-      blockBytes = b.getBytes();
-      blockCount = b.getCount();
+      kvContainerData.updateBlockCommitSequenceId(bcsId);
     }
 
-    kvContainerData.getStatistics().updateBlocks(blockBytes, blockCount);
-    
kvContainerData.getStatistics().setBlockPendingDeletion(blockPendingDeletion, 
blockPendingDeletionBytes);
+    // Set block statistics
+    populateBlockStatistics(kvContainerData, metadataTable, store);
+    
kvContainerData.getStatistics().setBlockPendingDeletion(pendingDeletions.getCount(),
 pendingDeletions.getBytes());
 
     // If the container is missing a chunks directory, possibly due to the
     // bug fixed by HDDS-6235, create it here.
@@ -404,6 +361,78 @@ private static void populateContainerMetadata(
     populateContainerFinalizeBlock(kvContainerData, store);
   }
 
+  private static PendingDelete populatePendingDeletionMetadata(
+      KeyValueContainerData kvContainerData, Table<String, Long> metadataTable,
+      DatanodeStore store) throws IOException {
+
+    Long pendingDeletionBlockBytes = 
metadataTable.get(kvContainerData.getPendingDeleteBlockBytesKey());
+    Long pendingDeleteBlockCount = 
metadataTable.get(kvContainerData.getPendingDeleteBlockCountKey());
+
+    if 
(!VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION))
 {
+      return handlePreDataDistributionFeature(pendingDeleteBlockCount, 
metadataTable, store, kvContainerData);
+    } else if (pendingDeleteBlockCount != null) {
+      return handlePostDataDistributionFeature(pendingDeleteBlockCount, 
pendingDeletionBlockBytes,
+          metadataTable, store, kvContainerData);
+    } else {
+      LOG.warn("Missing pendingDeleteBlockCount/size from {}: recalculate them 
from delete txn tables",
+          metadataTable.getName());
+      return getAggregatePendingDelete(store, kvContainerData, 
kvContainerData.getSchemaVersion());
+    }
+  }
+
+  private static PendingDelete handlePreDataDistributionFeature(
+      Long pendingDeleteBlockCount, Table<String, Long> metadataTable,
+      DatanodeStore store, KeyValueContainerData kvContainerData) throws 
IOException {
+
+    if (pendingDeleteBlockCount != null) {
+      return new PendingDelete(pendingDeleteBlockCount, 0L);
+    } else {
+      LOG.warn("Missing pendingDeleteBlockCount/size from {}: recalculate them 
from delete txn tables",
+          metadataTable.getName());
+      return getAggregatePendingDelete(store, kvContainerData, 
kvContainerData.getSchemaVersion());
+    }
+  }
+
+  private static PendingDelete handlePostDataDistributionFeature(
+      Long pendingDeleteBlockCount, Long pendingDeletionBlockBytes,
+      Table<String, Long> metadataTable, DatanodeStore store,
+      KeyValueContainerData kvContainerData) throws IOException {
+
+    if (pendingDeletionBlockBytes != null) {
+      return new PendingDelete(pendingDeleteBlockCount, 
pendingDeletionBlockBytes);
+    } else {
+      LOG.warn("Missing pendingDeleteBlockSize from {}: recalculate them from 
delete txn tables",
+          metadataTable.getName());
+      PendingDelete pendingDeletions = getAggregatePendingDelete(
+          store, kvContainerData, kvContainerData.getSchemaVersion());
+      return new PendingDelete(pendingDeleteBlockCount, 
pendingDeletions.getBytes());
+    }
+  }
+
+  private static void populateBlockStatistics(
+      KeyValueContainerData kvContainerData, Table<String, Long> metadataTable,
+      DatanodeStore store) throws IOException {
+
+    final Long metadataTableBytesUsed = 
metadataTable.get(kvContainerData.getBytesUsedKey());
+    final Long metadataTableBlockCount = 
metadataTable.get(kvContainerData.getBlockCountKey());
+
+    final long blockBytes;
+    final long blockCount;
+
+    if (metadataTableBytesUsed != null && metadataTableBlockCount != null) {
+      blockBytes = metadataTableBytesUsed;
+      blockCount = metadataTableBlockCount;
+    } else {
+      LOG.warn("Missing bytesUsed={} or blockCount={} from {}: recalculate 
them from block table",
+          metadataTableBytesUsed, metadataTableBlockCount, 
metadataTable.getName());
+      final ContainerData.BlockByteAndCounts blockData = 
getUsedBytesAndBlockCount(store, kvContainerData);
+      blockBytes = blockData.getBytes();
+      blockCount = blockData.getCount();
+    }
+
+    kvContainerData.getStatistics().updateBlocks(blockBytes, blockCount);
+  }
+
   /**
    * Loads finalizeBlockLocalIds for container in memory.
    * @param kvContainerData - KeyValueContainerData
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/TestDNDataDistributionFinalization.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/TestDNDataDistributionFinalization.java
new file mode 100644
index 00000000000..d714a955b0a
--- /dev/null
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/TestDNDataDistributionFinalization.java
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.upgrade;
+
+import static 
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SCM_WAIT_TIME_AFTER_SAFE_MODE_EXIT;
+import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HEARTBEAT_PROCESS_INTERVAL;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLOCK_DELETING_SERVICE_INTERVAL;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.scm.ScmConfig;
+import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol;
+import org.apache.hadoop.hdds.scm.server.SCMConfigurator;
+import org.apache.hadoop.hdds.scm.server.SCMStorageConfig;
+import org.apache.hadoop.ozone.HddsDatanodeService;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
+import org.apache.hadoop.ozone.UniformDatanodesFactory;
+import org.apache.hadoop.ozone.client.BucketArgs;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientFactory;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
+import org.apache.hadoop.ozone.container.common.impl.ContainerSet;
+import org.apache.hadoop.ozone.container.common.interfaces.Container;
+import 
org.apache.hadoop.ozone.container.common.statemachine.DatanodeConfiguration;
+import org.apache.hadoop.ozone.container.keyvalue.KeyValueContainer;
+import org.apache.hadoop.ozone.container.keyvalue.KeyValueContainerData;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests upgrade finalization failure scenarios and corner cases specific to 
DN data distribution feature.
+ */
+public class TestDNDataDistributionFinalization {
+  private static final String CLIENT_ID = UUID.randomUUID().toString();
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestDNDataDistributionFinalization.class);
+
+  private StorageContainerLocationProtocol scmClient;
+  private MiniOzoneHAClusterImpl cluster;
+
+  private static final int NUM_DATANODES = 3;
+  private static final int NUM_SCMS = 3;
+  private final String volumeName = UUID.randomUUID().toString();
+  private final String bucketName = UUID.randomUUID().toString();
+  private OzoneBucket bucket;
+
+  @AfterEach
+  public void cleanup() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  public void init(OzoneConfiguration conf) throws Exception {
+
+    SCMConfigurator configurator = new SCMConfigurator();
+    configurator.setUpgradeFinalizationExecutor(null);
+
+    conf.setInt(SCMStorageConfig.TESTING_INIT_LAYOUT_VERSION_KEY, 
HDDSLayoutFeature.HBASE_SUPPORT.layoutVersion());
+    conf.setTimeDuration(OZONE_BLOCK_DELETING_SERVICE_INTERVAL, 100,
+        TimeUnit.MILLISECONDS);
+    conf.setTimeDuration(OZONE_BLOCK_DELETING_SERVICE_INTERVAL, 100,
+        TimeUnit.MILLISECONDS);
+    conf.setTimeDuration(OZONE_SCM_HEARTBEAT_PROCESS_INTERVAL,
+        100, TimeUnit.MILLISECONDS);
+    ScmConfig scmConfig = conf.getObject(ScmConfig.class);
+    scmConfig.setBlockDeletionInterval(Duration.ofMillis(100));
+    conf.setFromObject(scmConfig);
+    conf.set(HDDS_SCM_WAIT_TIME_AFTER_SAFE_MODE_EXIT, "0s");
+
+    DatanodeConfiguration dnConf =
+        conf.getObject(DatanodeConfiguration.class);
+    dnConf.setBlockDeletionInterval(Duration.ofMillis(100));
+    conf.setFromObject(dnConf);
+
+    MiniOzoneHAClusterImpl.Builder clusterBuilder = 
MiniOzoneCluster.newHABuilder(conf);
+    clusterBuilder.setNumOfStorageContainerManagers(NUM_SCMS)
+        .setNumOfActiveSCMs(NUM_SCMS)
+        .setSCMServiceId("scmservice")
+        .setOMServiceId("omServiceId")
+        .setNumOfOzoneManagers(1)
+        .setSCMConfigurator(configurator)
+        .setNumDatanodes(NUM_DATANODES)
+        .setDatanodeFactory(UniformDatanodesFactory.newBuilder()
+            
.setLayoutVersion(HDDSLayoutFeature.INITIAL_VERSION.layoutVersion())
+            .build());
+    this.cluster = clusterBuilder.build();
+
+    scmClient = cluster.getStorageContainerLocationClient();
+    cluster.waitForClusterToBeReady();
+    assertEquals(HDDSLayoutFeature.HBASE_SUPPORT.layoutVersion(),
+        
cluster.getStorageContainerManager().getLayoutVersionManager().getMetadataLayoutVersion());
+
+    // Create Volume and Bucket
+    try (OzoneClient ozoneClient = OzoneClientFactory.getRpcClient(conf)) {
+      ObjectStore store = ozoneClient.getObjectStore();
+      store.createVolume(volumeName);
+      OzoneVolume volume = store.getVolume(volumeName);
+      BucketArgs.Builder builder = BucketArgs.newBuilder();
+      volume.createBucket(bucketName, builder.build());
+      bucket = volume.getBucket(bucketName);
+    }
+  }
+
+  /**
+   * Test that validates the upgrade scenario for DN data distribution feature.
+   * This test specifically checks the conditions in 
populatePendingDeletionMetadata:
+   * 1. Pre-finalization: handlePreDataDistributionFeature path
+   * 2. Post-finalization: handlePostDataDistributionFeature path
+   * 3. Missing metadata: getAggregatePendingDelete path
+   */
+  @Test
+  public void testDataDistributionUpgradeScenario() throws Exception {
+    init(new OzoneConfiguration());
+
+    // Verify initial state - STORAGE_SPACE_DISTRIBUTION should not be 
finalized yet
+    assertEquals(HDDSLayoutFeature.HBASE_SUPPORT.layoutVersion(),
+        
cluster.getStorageContainerManager().getLayoutVersionManager().getMetadataLayoutVersion());
+
+    // Create some data and delete operations to trigger pending deletion logic
+    String keyName1 = "testKey1";
+    String keyName2 = "testKey2";
+    byte[] data = new byte[1024];
+
+    // Write some keys
+    try (OzoneOutputStream out = bucket.createKey(keyName1, data.length)) {
+      out.write(data);
+    }
+    try (OzoneOutputStream out = bucket.createKey(keyName2, data.length)) {
+      out.write(data);
+    }
+
+    // Delete one key to create pending deletion blocks
+    bucket.deleteKey(keyName1);
+
+    // Validate pre-finalization state
+    validatePreDataDistributionFeatureState();
+
+    // Now trigger finalization
+    Future<?> finalizationFuture = Executors.newSingleThreadExecutor().submit(
+        () -> {
+          try {
+            scmClient.finalizeScmUpgrade(CLIENT_ID);
+          } catch (IOException ex) {
+            LOG.info("finalization client failed. This may be expected if the" 
+
+                " test injected failures.", ex);
+          }
+        });
+
+    // Wait for finalization to complete
+    finalizationFuture.get();
+    TestHddsUpgradeUtils.waitForFinalizationFromClient(scmClient, CLIENT_ID);
+
+    // Verify finalization completed
+    assertEquals(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION.layoutVersion(),
+        
cluster.getStorageContainerManager().getLayoutVersionManager().getMetadataLayoutVersion());
+
+    // Create more data and deletions to test post-finalization behavior
+    String keyName3 = "testKey3";
+    try (OzoneOutputStream out = bucket.createKey(keyName3, data.length)) {
+      out.write(data);
+    }
+    bucket.deleteKey(keyName2);
+    bucket.deleteKey(keyName3);
+
+    // Validate post-finalization state
+    validatePostDataDistributionFeatureState();
+  }
+
+  /**
+   * Test specifically for the missing metadata scenario that triggers
+   * the getAggregatePendingDelete code path.
+   */
+  @Test
+  public void testMissingPendingDeleteMetadataRecalculation() throws Exception 
{
+    init(new OzoneConfiguration());
+
+
+    // Create and delete keys to generate some pending deletion data
+    String keyName = "testKeyForRecalc";
+    byte[] data = new byte[2048];
+
+    try (OzoneOutputStream out = bucket.createKey(keyName, data.length)) {
+      out.write(data);
+    }
+    bucket.deleteKey(keyName);
+    Future<?> finalizationFuture = Executors.newSingleThreadExecutor().submit(
+        () -> {
+          try {
+            scmClient.finalizeScmUpgrade(CLIENT_ID);
+          } catch (IOException ex) {
+            LOG.info("finalization client failed. This may be expected if the" 
+
+                " test injected failures.", ex);
+          }
+        });
+    // Wait for finalization
+    finalizationFuture.get();
+    TestHddsUpgradeUtils.waitForFinalizationFromClient(scmClient, CLIENT_ID);
+
+    assertEquals(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION.layoutVersion(),
+        
cluster.getStorageContainerManager().getLayoutVersionManager().getMetadataLayoutVersion());
+
+    // Verify the system can handle scenarios where pendingDeleteBlockCount
+    // might be missing and needs recalculation
+    validateRecalculationScenario();
+  }
+
+  private void validatePreDataDistributionFeatureState() {
+    // Before finalization, STORAGE_SPACE_DISTRIBUTION should not be finalized
+    boolean isDataDistributionFinalized =
+        
VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION);
+    assertTrue(!isDataDistributionFinalized ||
+            // In test environment, version manager might be null
+            cluster.getHddsDatanodes().get(0).getDatanodeStateMachine()
+                .getLayoutVersionManager() == null,
+        "STORAGE_SPACE_DISTRIBUTION should not be finalized in pre-upgrade 
state");
+
+    // Verify containers exist and have pending deletion metadata
+    validateContainerPendingDeletions(false);
+  }
+
+  private void validatePostDataDistributionFeatureState() {
+    // After finalization, STORAGE_SPACE_DISTRIBUTION should be finalized
+    boolean isDataDistributionFinalized =
+        
VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION);
+    assertTrue(isDataDistributionFinalized ||
+            // In test environment, version manager might be null
+            cluster.getHddsDatanodes().get(0).getDatanodeStateMachine()
+                .getLayoutVersionManager() == null,
+        "STORAGE_SPACE_DISTRIBUTION should be finalized in post-upgrade 
state");
+
+    // Verify containers can handle post-finalization pending deletion logic
+    validateContainerPendingDeletions(true);
+  }
+
+  private void validateContainerPendingDeletions(boolean isPostFinalization) {
+    // Get containers from datanodes and validate their pending deletion 
handling
+    List<HddsDatanodeService> datanodes = cluster.getHddsDatanodes();
+
+    for (HddsDatanodeService datanode : datanodes) {
+      ContainerSet containerSet = datanode.getDatanodeStateMachine()
+          .getContainer().getContainerSet();
+
+      // Iterate through containers
+      for (Container<?> container : containerSet.getContainerMap().values()) {
+        if (container instanceof KeyValueContainer) {
+          KeyValueContainerData containerData =
+              (KeyValueContainerData) container.getContainerData();
+
+          // Verify the container has been processed through the appropriate
+          // code path in populatePendingDeletionMetadata
+          assertNotNull(containerData.getStatistics());
+
+          // The exact validation will depend on whether we're in pre or post
+          // finalization state, but we should always have valid statistics
+          assertTrue(containerData.getStatistics().getBlockPendingDeletion() 
>= 0);
+
+          if (isPostFinalization) {
+            // Post-finalization should have both block count and bytes
+            
assertTrue(containerData.getStatistics().getBlockPendingDeletionBytes() >= 0);
+          } else {
+            assertEquals(0, 
containerData.getStatistics().getBlockPendingDeletionBytes());
+          }
+        }
+      }
+    }
+  }
+
+  private void validateRecalculationScenario() {
+    // This validates that the system properly handles the case where
+    // pendingDeleteBlockCount is null and needs to be recalculated
+    // from delete transaction tables via getAggregatePendingDelete
+
+    List<HddsDatanodeService> datanodes = cluster.getHddsDatanodes();
+
+    for (HddsDatanodeService datanode : datanodes) {
+      ContainerSet containerSet = datanode.getDatanodeStateMachine()
+          .getContainer().getContainerSet();
+
+      // Verify containers have proper pending deletion statistics
+      // even in recalculation scenarios
+      for (Container<?> container : containerSet.getContainerMap().values()) {
+        if (container instanceof KeyValueContainer) {
+          KeyValueContainerData containerData =
+              ((KeyValueContainer) container).getContainerData();
+
+          // Statistics should be valid even after recalculation
+          assertNotNull(containerData.getStatistics());
+          assertTrue(containerData.getStatistics().getBlockPendingDeletion() 
>= 0);
+          
assertTrue(containerData.getStatistics().getBlockPendingDeletionBytes() >= 0);
+        }
+      }
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to