From dba64df8da5e3109b83faa2cd5f3d71d1d58c9ef Mon Sep 17 00:00:00 2001 From: ashishk Date: Fri, 6 Mar 2026 12:14:33 +0530 Subject: [PATCH 1/3] HDDS-14772. Delete EC replica when container state is already DELETED. --- .../AbstractContainerReportHandler.java | 4 +++ .../container/TestContainerReportHandler.java | 4 +++ .../container/TestContainerStateManager.java | 28 ------------------- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java index f79507e1e660..4d886c85f795 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java @@ -313,6 +313,10 @@ private boolean updateContainerState(final DatanodeDetails datanode, deleteReplica(containerId, datanode, publisher, "DELETED", false, detailsForLogging); return false; } + if (container.getReplicationType().equals(HddsProtos.ReplicationType.EC)) { + // In case of EC container, delete its replica to avoid orphan replica + deleteReplica(containerId, datanode, publisher, "DELETED", true, detailsForLogging); + } // HDDS-12421: fall-through to case DELETING case DELETING: // HDDS-11136: If a DELETING container has a non-empty CLOSED replica, transition the container to CLOSED diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReportHandler.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReportHandler.java index 0e8eee799bcd..264a429960fc 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReportHandler.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReportHandler.java @@ -209,6 +209,10 @@ static Stream containerAndReplicaStates() { containerState.equals(HddsProtos.LifeCycleState.DELETING))) { continue; } + if (replicationType == HddsProtos.ReplicationType.EC && + containerState.equals(HddsProtos.LifeCycleState.DELETED)) { + continue; + } for (ContainerReplicaProto.State invalidState : invalidReplicaStates) { combinations.add(Arguments.of(replicationType, containerState, replicaState, invalidState)); } diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java index 869b7dd5bb9d..3c6a4133dc62 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java @@ -17,9 +17,7 @@ package org.apache.hadoop.hdds.scm.container; -import static org.apache.hadoop.hdds.protocol.MockDatanodeDetails.randomDatanodeDetails; import static org.apache.hadoop.hdds.scm.HddsTestUtils.getContainer; -import static org.apache.hadoop.hdds.scm.HddsTestUtils.getECContainer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.fail; @@ -39,7 +37,6 @@ import java.util.Set; import java.util.concurrent.TimeoutException; import org.apache.hadoop.hdds.HddsConfigKeys; -import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; import org.apache.hadoop.hdds.conf.OzoneConfiguration; @@ -300,31 +297,6 @@ public void testDeletedContainerWithLowerBcsidStaleReplicaRatis() "Delete command should have force=true for stale RATIS replica with lower BCSID"); } - /** - * DELETED EC container + CLOSED replica with BCSID <= container seqId. - * Expected: Should NOT send force delete - * Should transition to CLOSED instead - */ - @Test - public void testDeletedECContainerWithStaleClosedReplicaShouldNotForceDelete() - throws IOException { - final DatanodeDetails datanode = randomDatanodeDetails(); - nodeManager.register(datanode, null, null); - // Create a DELETED EC container - ECReplicationConfig repConfig = new ECReplicationConfig(3, 2); - final ContainerInfo ecContainer = getECContainer( - HddsProtos.LifeCycleState.DELETED, - PipelineID.randomId(), - repConfig); - containerStateManager.addContainer(ecContainer.getProtobuf()); - assertEquals(HddsProtos.ReplicationType.EC, ecContainer.getReplicationType()); - // Report CLOSED replica with BCSID = container's seqId - sendReportAndCaptureDeleteCommand(ecContainer, datanode, - ecContainer.getSequenceId(), false, 1, false); - // Container should transition to CLOSED - verifyContainerState(ecContainer.containerID(), HddsProtos.LifeCycleState.CLOSED); - } - private DeleteContainerCommand sendReportAndCaptureDeleteCommand( ContainerInfo container, DatanodeDetails datanode, long bcsId, boolean isEmpty, int replicaIndex, boolean reqCommandSend) { From ed8d3452d9c89012d8f77d29159904c306733ae1 Mon Sep 17 00:00:00 2001 From: ashishk Date: Fri, 6 Mar 2026 12:36:23 +0530 Subject: [PATCH 2/3] HDDS-14772. Return after delete. --- .../hdds/scm/container/AbstractContainerReportHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java index 4d886c85f795..6028bbfd90e0 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java @@ -316,6 +316,7 @@ private boolean updateContainerState(final DatanodeDetails datanode, if (container.getReplicationType().equals(HddsProtos.ReplicationType.EC)) { // In case of EC container, delete its replica to avoid orphan replica deleteReplica(containerId, datanode, publisher, "DELETED", true, detailsForLogging); + return false; } // HDDS-12421: fall-through to case DELETING case DELETING: From 85e772365ee2857ccbdc3c987c62841e4a441328 Mon Sep 17 00:00:00 2001 From: ashishk Date: Fri, 6 Mar 2026 18:37:35 +0530 Subject: [PATCH 3/3] Add test case --- .../container/TestContainerStateManager.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java index 3c6a4133dc62..dfb4f38deefc 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerStateManager.java @@ -17,7 +17,9 @@ package org.apache.hadoop.hdds.scm.container; +import static org.apache.hadoop.hdds.protocol.MockDatanodeDetails.randomDatanodeDetails; import static org.apache.hadoop.hdds.scm.HddsTestUtils.getContainer; +import static org.apache.hadoop.hdds.scm.HddsTestUtils.getECContainer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.fail; @@ -37,6 +39,7 @@ import java.util.Set; import java.util.concurrent.TimeoutException; import org.apache.hadoop.hdds.HddsConfigKeys; +import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; import org.apache.hadoop.hdds.conf.OzoneConfiguration; @@ -297,6 +300,31 @@ public void testDeletedContainerWithLowerBcsidStaleReplicaRatis() "Delete command should have force=true for stale RATIS replica with lower BCSID"); } + /** + * DELETED EC container in SCM. + * Expected: Should send force delete to DN + */ + @Test + public void testDeletedECContainerWithStaleClosedReplicaShouldNotForceDelete() + throws IOException { + final DatanodeDetails datanode = randomDatanodeDetails(); + nodeManager.register(datanode, null, null); + // Create a DELETED EC container + ECReplicationConfig repConfig = new ECReplicationConfig(3, 2); + final ContainerInfo ecContainer = getECContainer( + HddsProtos.LifeCycleState.DELETED, + PipelineID.randomId(), + repConfig); + containerStateManager.addContainer(ecContainer.getProtobuf()); + assertEquals(HddsProtos.ReplicationType.EC, ecContainer.getReplicationType()); + + // Verify delete command sent + sendReportAndCaptureDeleteCommand(ecContainer, datanode, + ecContainer.getSequenceId(), false, 1, true); + // Container should remain as DELETED + verifyContainerState(ecContainer.containerID(), HddsProtos.LifeCycleState.DELETED); + } + private DeleteContainerCommand sendReportAndCaptureDeleteCommand( ContainerInfo container, DatanodeDetails datanode, long bcsId, boolean isEmpty, int replicaIndex, boolean reqCommandSend) {