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

sodonnell 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 0acb9ea54c HDDS-12232. Move container from QUASI_CLODED to CLOSED only 
when SCM sees all 3 origin node replicas (#7834)
0acb9ea54c is described below

commit 0acb9ea54c47299e9a0fae77afc28d9c7d1284ab
Author: Stephen O'Donnell <[email protected]>
AuthorDate: Fri Feb 7 16:57:29 2025 +0000

    HDDS-12232. Move container from QUASI_CLODED to CLOSED only when SCM sees 
all 3 origin node replicas (#7834)
    
    Co-authored-by: S O'Donnell <[email protected]>
---
 .../health/QuasiClosedContainerHandler.java        |  7 ++-
 .../health/TestQuasiClosedContainerHandler.java    | 50 +++++++++++++----
 .../replicationManagerTests/quasi_closed.json      | 62 ++++++++++++++++++++++
 3 files changed, 109 insertions(+), 10 deletions(-)

diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/replication/health/QuasiClosedContainerHandler.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/replication/health/QuasiClosedContainerHandler.java
index 94a93af107..1240a57a1f 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/replication/health/QuasiClosedContainerHandler.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/replication/health/QuasiClosedContainerHandler.java
@@ -104,7 +104,12 @@ private boolean canForceCloseContainer(final ContainerInfo 
container,
         .map(ContainerReplica::getOriginDatanodeId)
         .distinct()
         .count();
-    return uniqueQuasiClosedReplicaCount > (replicationFactor / 2);
+    // We can only force close the container if we have seen all the replicas 
from unique origins.
+    // Due to unexpected behavior when writing to ratis containers, it is 
possible for blocks to be committed
+    // on the ratis leader, but not on the followers. A failure on the leader 
can result in two replicas
+    // without the latest transactions, which are then force closed. This can 
result in data loss.
+    // Note that if the 3rd replica is permanently lost, the container will be 
stuck in QUASI_CLOSED state forever.
+    return uniqueQuasiClosedReplicaCount >= replicationFactor;
   }
 
   /**
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/replication/health/TestQuasiClosedContainerHandler.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/replication/health/TestQuasiClosedContainerHandler.java
index e57879a904..ce161127d9 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/replication/health/TestQuasiClosedContainerHandler.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/replication/health/TestQuasiClosedContainerHandler.java
@@ -108,13 +108,12 @@ public void testOpenContainerReturnsFalse() {
   }
 
   /**
-   * When a container is QUASI_CLOSED, and it has greater than 50% of its
+   * When a container is QUASI_CLOSED, and it has only 2
    * replicas in QUASI_CLOSED state with unique origin node id,
-   * the handler should send force close commands to the replica(s) with
-   * highest BCSID.
+   * the handler should not force close it as all 3 unique replicas are needed.
    */
   @Test
-  public void testQuasiClosedWithQuorumReturnsTrue() {
+  public void testQuasiClosedWithQuorumReturnsFalse() {
     ContainerInfo containerInfo = ReplicationTestUtil.createContainerInfo(
         ratisReplicationConfig, 1, QUASI_CLOSED);
     Set<ContainerReplica> containerReplicas = ReplicationTestUtil
@@ -140,14 +139,48 @@ public void testQuasiClosedWithQuorumReturnsTrue() {
 
     assertFalse(quasiClosedContainerHandler.handle(request));
     assertFalse(quasiClosedContainerHandler.handle(readRequest));
-    verify(replicationManager, times(2))
+    verify(replicationManager, times(0))
+        .sendCloseContainerReplicaCommand(any(), any(), anyBoolean());
+    assertEquals(1, request.getReport().getStat(
+        ReplicationManagerReport.HealthState.QUASI_CLOSED_STUCK));
+  }
+
+  /**
+   * When a container is QUASI_CLOSED, and all 3 replicas are reported with 
unique
+   * origins, it should be forced closed.
+   */
+  @Test
+  public void testQuasiClosedWithAllUniqueOriginSendsForceClose() {
+    ContainerInfo containerInfo = ReplicationTestUtil.createContainerInfo(
+        ratisReplicationConfig, 1, QUASI_CLOSED);
+    // These 3 replicas will have the same BCSID and unique origin node ids
+    Set<ContainerReplica> containerReplicas = ReplicationTestUtil
+        .createReplicas(containerInfo.containerID(),
+            State.QUASI_CLOSED, 0, 0, 0);
+    ContainerCheckRequest request = new ContainerCheckRequest.Builder()
+        .setPendingOps(Collections.emptyList())
+        .setReport(new ReplicationManagerReport())
+        .setContainerInfo(containerInfo)
+        .setContainerReplicas(containerReplicas)
+        .build();
+    ContainerCheckRequest readRequest = new ContainerCheckRequest.Builder()
+        .setPendingOps(Collections.emptyList())
+        .setReport(new ReplicationManagerReport())
+        .setContainerInfo(containerInfo)
+        .setContainerReplicas(containerReplicas)
+        .setReadOnly(true)
+        .build();
+
+    assertFalse(quasiClosedContainerHandler.handle(request));
+    assertFalse(quasiClosedContainerHandler.handle(readRequest));
+    verify(replicationManager, times(3))
         .sendCloseContainerReplicaCommand(any(), any(), anyBoolean());
   }
 
   /**
    * The replicas are QUASI_CLOSED, but all of them have the same origin node
-   * id. Since a quorum (greater than 50% of replicas with unique origin node
-   * ids in QUASI_CLOSED state) is not formed, the handler should return false.
+   * id. Since all replicas must have unique origin node ids, the handler
+   * should not force close it.
    */
   @Test
   public void testHealthyQuasiClosedContainerReturnsFalse() {
@@ -172,8 +205,7 @@ public void testHealthyQuasiClosedContainerReturnsFalse() {
 
   /**
    * Only one replica is in QUASI_CLOSED state. This fails the condition of
-   * having greater than 50% of replicas with unique origin nodes in
-   * QUASI_CLOSED state. The handler should return false.
+   * having all replicas with unique origin nodes in QUASI_CLOSED state.
    */
   @Test
   public void testQuasiClosedWithTwoOpenReplicasReturnsFalse() {
diff --git 
a/hadoop-hdds/server-scm/src/test/resources/replicationManagerTests/quasi_closed.json
 
b/hadoop-hdds/server-scm/src/test/resources/replicationManagerTests/quasi_closed.json
new file mode 100644
index 0000000000..3b7bfbc52b
--- /dev/null
+++ 
b/hadoop-hdds/server-scm/src/test/resources/replicationManagerTests/quasi_closed.json
@@ -0,0 +1,62 @@
+[
+  { "description": "Quasi-closed replicas with one open", "containerState": 
"QUASI_CLOSED", "replicationConfig": "RATIS:THREE", "sequenceId": 12,
+    "replicas": [
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d1", "sequenceId": 
12, "isEmpty": false, "origin": "o1"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d2", "sequenceId": 
12, "isEmpty": false, "origin": "o2"},
+      { "state": "OPEN",         "index": 0,   "datanode": "d3", "sequenceId": 
12, "isEmpty": false, "origin": "o3"}
+    ],
+    "expectation": { "overReplicated": 0, "overReplicatedQueue":  0, 
"quasiClosedStuck": 1},
+    "checkCommands": [
+      { "type": "closeContainerCommand", "datanode": "d3" }
+    ],
+    "commands": []
+  },
+  { "description": "Quasi-closed with 2 replicas", "containerState": 
"QUASI_CLOSED", "replicationConfig": "RATIS:THREE", "sequenceId": 12,
+    "replicas": [
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d1", "sequenceId": 
12, "isEmpty": false, "origin": "o1"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d2", "sequenceId": 
12, "isEmpty": false, "origin": "o2"}
+    ],
+    "expectation": { "underReplicated": 1, "underReplicatedQueue": 1, 
"overReplicated": 0, "overReplicatedQueue":  0, "quasiClosedStuck": 1},
+    "checkCommands": [],
+    "commands": [
+      { "type": "replicateContainerCommand" }
+    ]
+  },
+  { "description": "Quasi-closed with 3 replicas 2 origins", "containerState": 
"QUASI_CLOSED", "replicationConfig": "RATIS:THREE", "sequenceId": 12,
+    "replicas": [
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d1", "sequenceId": 
12, "isEmpty": false, "origin": "o1"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d2", "sequenceId": 
12, "isEmpty": false, "origin": "o2"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d3", "sequenceId": 
12, "isEmpty": false, "origin": "o2"}
+    ],
+    "expectation": { "underReplicated": 0, "underReplicatedQueue": 0, 
"overReplicated": 0, "overReplicatedQueue":  0, "quasiClosedStuck": 1},
+    "checkCommands": [],
+    "commands": []
+  },
+  { "description": "Quasi-closed with 3 replicas 3 origins", "containerState": 
"QUASI_CLOSED", "replicationConfig": "RATIS:THREE", "sequenceId": 12,
+    "replicas": [
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d1", "sequenceId": 
12, "isEmpty": false, "origin": "o1"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d2", "sequenceId": 
12, "isEmpty": false, "origin": "o2"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d3", "sequenceId": 
12, "isEmpty": false, "origin": "o3"}
+    ],
+    "expectation": { "underReplicated": 0, "underReplicatedQueue": 0, 
"overReplicated": 0, "overReplicatedQueue":  0, "quasiClosedStuck": 0 },
+    "checkCommands": [
+      { "type": "closeContainerCommand", "datanode": "d1" },
+      { "type": "closeContainerCommand", "datanode": "d2" },
+      { "type": "closeContainerCommand", "datanode": "d3" }
+    ],
+    "commands": []
+  },
+  { "description": "Quasi-closed with 3 replicas 3 origins different BCSID", 
"containerState": "QUASI_CLOSED", "replicationConfig": "RATIS:THREE", 
"sequenceId": 12,
+    "replicas": [
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d1", "sequenceId": 
12, "isEmpty": false, "origin": "o1"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d2", "sequenceId": 
12, "isEmpty": false, "origin": "o2"},
+      { "state": "QUASI_CLOSED", "index": 0,   "datanode": "d3", "sequenceId": 
11, "isEmpty": false, "origin": "o3"}
+    ],
+    "expectation": { "underReplicated": 0, "underReplicatedQueue": 0, 
"overReplicated": 0, "overReplicatedQueue":  0, "quasiClosedStuck": 0 },
+    "checkCommands": [
+      { "type": "closeContainerCommand", "datanode": "d1" },
+      { "type": "closeContainerCommand", "datanode": "d2" }
+    ],
+    "commands": []
+  }
+]
\ No newline at end of file


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

Reply via email to