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

smengcl 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 84fc9f52ee7 HDDS-15350. SCM crashes with ArithmeticException when 
topology reports zero racks during DN decommission (#10339)
84fc9f52ee7 is described below

commit 84fc9f52ee709992e88ca745770f031c4466e13b
Author: Siyao Meng <[email protected]>
AuthorDate: Fri May 29 23:25:34 2026 -0700

    HDDS-15350. SCM crashes with ArithmeticException when topology reports zero 
racks during DN decommission (#10339)
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    Co-authored-by: Claude Opus 4.7 <[email protected]>
    Co-authored-by: Wei-Chiu Chuang <[email protected]>
---
 .../hadoop/hdds/scm/SCMCommonPlacementPolicy.java  | 27 ++++++++++++---
 .../hdds/scm/TestSCMCommonPlacementPolicy.java     | 40 ++++++++++++++++++++++
 2 files changed, 62 insertions(+), 5 deletions(-)

diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java
index c51f6d0429f..93940b7770f 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java
@@ -411,6 +411,18 @@ protected int getRequiredRackCount(int numReplicas, int 
excludedRackCount) {
    * @return The max number of replicas per rack
    */
   protected int getMaxReplicasPerRack(int numReplicas, int numberOfRacks) {
+    if (numberOfRacks <= 0) {
+      // No rack information means there is no per-rack constraint to
+      // enforce. Callers are expected to short-circuit before reaching
+      // here, but guard the divide site against transient empty-topology
+      // windows (HDDS-15350). The WARN makes the silent path observable;
+      // configure log4j appender-side filtering if it floods.
+      LOG.warn("Empty rack topology in placement validation: numReplicas={} "
+          + "numberOfRacks={}; returning numReplicas to avoid divide-by-zero "
+          + "(HDDS-15350).",
+          numReplicas, numberOfRacks);
+      return numReplicas;
+    }
     return numReplicas / numberOfRacks
             + Math.min(numReplicas % numberOfRacks, 1);
   }
@@ -436,7 +448,16 @@ public ContainerPlacementStatus validateContainerPlacement(
     NetworkTopology topology = nodeManager.getClusterNetworkTopologyMap();
     // We have a network topology so calculate if it is satisfied or not.
     int requiredRacks = getRequiredRackCount(replicas, 0);
-    if (topology == null || replicas == 1 || requiredRacks == 1) {
+    // The leaf nodes are all at max level, so the number of nodes at
+    // maxLevel - 1 is the rack count. Compute up front so we can
+    // short-circuit when the topology has no rack information, which
+    // would otherwise reach getMaxReplicasPerRack with numberOfRacks
+    // == 0 (HDDS-15350: transient empty-topology window during a DN
+    // decommission crashed the ReplicationMonitor with "/ by zero").
+    final int numRacks = topology == null ? 0
+        : topology.getNumOfNodes(topology.getMaxLevel() - 1);
+    if (topology == null || replicas == 1 || requiredRacks <= 1
+        || numRacks <= 0) {
       if (!dns.isEmpty()) {
         // placement is always satisfied if there is at least one DN.
         return validPlacement;
@@ -471,10 +492,6 @@ public ContainerPlacementStatus validateContainerPlacement(
             Function.identity(),
             Collectors.reducing(0, e -> 1, Integer::sum)))
         .values());
-    final int maxLevel = topology.getMaxLevel();
-    // The leaf nodes are all at max level, so the number of nodes at
-    // leafLevel - 1 is the rack count
-    int numRacks = topology.getNumOfNodes(maxLevel - 1);
     if (replicas < requiredRacks) {
       requiredRacks = replicas;
     }
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java
index dba2d60b98c..5ecd3fa1c74 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java
@@ -566,6 +566,46 @@ public void testValidatePlacementWithDeadMaintenanceNode() 
throws NodeNotFoundEx
     assertTrue(placementStatus.isPolicySatisfied());
   }
 
+  /**
+   * HDDS-15350: when the network topology transiently reports zero racks
+   * (due to DNS resolution problems), validateContainerPlacement must
+   * not crash with ArithmeticException ("/ by zero") in
+   * getMaxReplicasPerRack. Without the fix this test throws and SCM's
+   * ReplicationMonitor thread dies along with it.
+   */
+  @Test
+  public void testValidateContainerPlacementWithZeroRackTopology() {
+    List<DatanodeDetails> nodes = ImmutableList.of(
+        MockDatanodeDetails.randomDatanodeDetails(),
+        MockDatanodeDetails.randomDatanodeDetails(),
+        MockDatanodeDetails.randomDatanodeDetails());
+    NodeManager mockNodeManager = mock(NodeManager.class);
+    when(mockNodeManager.getAllNodes()).thenAnswer(inv -> nodes);
+
+    // Topology that reports zero racks at the rack level during the
+    // empty-topology window observed during DN decommission.
+    NetworkTopology topology = mock(NetworkTopology.class);
+    when(topology.getMaxLevel()).thenReturn(3);
+    when(topology.getNumOfNodes(anyInt())).thenReturn(0);
+    when(mockNodeManager.getClusterNetworkTopologyMap()).thenReturn(topology);
+
+    // rackCnt=2 makes DummyPlacementPolicy.getRequiredRackCount return
+    // min(replicas, 2) > 1, so the original early-return guard does NOT
+    // fire and execution proceeds to the divide site.
+    Map<Integer, Integer> rackMap = new HashMap<>();
+    rackMap.put(0, 0);
+    rackMap.put(1, 0);
+    rackMap.put(2, 0);
+    DummyPlacementPolicy policy = new DummyPlacementPolicy(
+        mockNodeManager, conf, rackMap, 2);
+
+    ContainerPlacementStatus status =
+        policy.validateContainerPlacement(nodes, 3);
+    assertTrue(status.isPolicySatisfied(),
+        "placement should not crash and should be considered satisfied "
+            + "when the topology reports no racks");
+  }
+
   private static class DummyPlacementPolicy extends SCMCommonPlacementPolicy {
     private Map<DatanodeDetails, Node> rackMap;
     private List<Node> racks;


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

Reply via email to