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

adoroszlai 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 841d82bef5e HDDS-15514. Pass Ratis peer addresses as hostnames (#10485)
841d82bef5e is described below

commit 841d82bef5e2e2a55364a44d06ac7935cac4d89d
Author: Ritesh H Shukla <[email protected]>
AuthorDate: Fri Jun 12 22:29:43 2026 -0700

    HDDS-15514. Pass Ratis peer addresses as hostnames (#10485)
---
 .../hadoop/hdds/scm/ha/SCMHAManagerImpl.java       | 13 +++--
 .../hadoop/hdds/scm/ha/SCMRatisServerImpl.java     | 12 ++++-
 .../ozone/om/ratis/OzoneManagerRatisServer.java    | 63 +++++++++-------------
 .../om/ratis/TestOzoneManagerRatisServer.java      | 40 ++++++++++++++
 4 files changed, 86 insertions(+), 42 deletions(-)

diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
index 01f1fee0d36..d78e4a8d526 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
@@ -118,9 +118,16 @@ public void start() throws IOException {
       final boolean success = HAUtils.addSCM(ozoneConf,
           new AddSCMRequest.Builder().setClusterId(scm.getClusterId())
               .setScmId(scm.getScmId())
-              .setRatisAddr(nodeDetails
-                  // TODO : Should we use IP instead of hostname??
-                  .getRatisHostPortStr()).build(), scm.getSCMNodeId());
+              // Pass the configured host:port string verbatim. Do NOT
+              // resolve it into an InetSocketAddress first -- that bakes
+              // a resolved IP into Ratis's peer address for the channel's
+              // lifetime. With the string passed through, gRPC's
+              // DnsNameResolver re-resolves hostname addresses on
+              // connection failure (peer pod restarts recover
+              // automatically), and IP-literal configs are still honored
+              // exactly as configured. See HDDS-15514.
+              .setRatisAddr(nodeDetails.getRatisHostPortStr())
+              .build(), scm.getSCMNodeId());
       if (!success) {
         throw new IOException("Adding SCM to existing HA group failed");
       } else {
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
index 81ebf1c2cc7..cfc5898bb95 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
@@ -396,8 +396,18 @@ private static RaftGroup buildRaftGroup(SCMNodeDetails 
details,
     final RaftGroupId groupId = buildRaftGroupId(clusterId);
     RaftPeerId selfPeerId = getSelfPeerId(scmId);
 
+    // Pass the configured host:port string through verbatim. The
+    // invariant is "do not pre-resolve" -- never construct an
+    // InetSocketAddress from the configured address and hand the
+    // resolved form to RaftPeer.setAddress, which would freeze the peer
+    // at one IP for the channel's lifetime. Ratis routes the string to
+    // gRPC's NettyChannelBuilder, whose default DnsNameResolver
+    // re-resolves hostname addresses on connection failure (peer pod
+    // restarts recover automatically in Kubernetes-style environments
+    // where DNS names are stable but IPs are not). IP-literal configs
+    // are still honored exactly as configured. See HDDS-15514
+    // (DNS-refresh-on-failure for all RPC paths).
     RaftPeer localRaftPeer = RaftPeer.newBuilder().setId(selfPeerId)
-        // TODO : Should we use IP instead of hostname??
         .setAddress(details.getRatisHostPortStr()).build();
 
     List<RaftPeer> raftPeers = new ArrayList<>();
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerRatisServer.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerRatisServer.java
index dab93e75900..18defcf808a 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerRatisServer.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerRatisServer.java
@@ -227,10 +227,8 @@ public static OzoneManagerRatisServer newOMRatisServer(
       // On regular startup, add all OMs to Ratis ring
       raftPeers.add(localRaftPeer);
 
-      for (Map.Entry<String, OMNodeDetails> peerInfo : peerNodes.entrySet()) {
-        String peerNodeId = peerInfo.getKey();
-        OMNodeDetails peerNode = peerInfo.getValue();
-        RaftPeer raftPeer = OzoneManagerRatisServer.createRaftPeer(peerNode, 
peerNodeId);
+      for (OMNodeDetails peerNode : peerNodes.values()) {
+        RaftPeer raftPeer = OzoneManagerRatisServer.createRaftPeer(peerNode);
 
         // Add other OM nodes belonging to the same OM service to the Ratis 
ring
         raftPeers.add(raftPeer);
@@ -435,42 +433,31 @@ private void updateRatisConfiguration(List<RaftPeer> 
followers, List<RaftPeer> l
     }
   }
 
-  private static RaftPeer createRaftPeer(OMNodeDetails omNode) {
-    String nodeId = omNode.getNodeId();
-    RaftPeerId raftPeerId = RaftPeerId.valueOf(nodeId);
-    InetSocketAddress ratisAddr = new InetSocketAddress(
-        omNode.getHostAddress(), omNode.getRatisPort());
-    RaftPeerRole startRole = omNode.isRatisListener() ?
-        RaftPeerRole.LISTENER : RaftPeerRole.FOLLOWER;
-
-    return RaftPeer.newBuilder()
-        .setId(raftPeerId)
-        .setAddress(ratisAddr)
-        .setStartupRole(startRole)
-        .build();
-  }
-
   /**
-   * Helper method to create a RaftPeer from OMNodeDetails, handling 
unresolved hosts.
-   * @param omNode the OM node details
-   * @param nodeId the node ID to use
-   * @return the created RaftPeer
+   * Build a RaftPeer for the given OM node. The peer address is set from
+   * {@link OMNodeDetails#getRatisHostPortStr()} -- the configured host
+   * string (hostname or IP literal) paired with the Ratis port. The
+   * configured string is passed through verbatim; this method never
+   * resolves it into an {@link InetSocketAddress} (which would bake the
+   * resolved IP into the peer address).
+   * <p>
+   * Why this matters: Ratis hands the address string to gRPC's
+   * {@code NettyChannelBuilder.forTarget(...)}, whose default
+   * {@code DnsNameResolver} re-resolves hostnames on connection failure
+   * / refresh. If the address is a hostname, gRPC recovers automatically
+   * from peer pod restarts in environments like Kubernetes where DNS
+   * names are stable but IPs are not. If the operator configured an IP
+   * literal, gRPC of course uses that IP directly -- the invariant is
+   * "don't pre-resolve", not "must be a hostname". See HDDS-15514
+   * (DNS-refresh-on-failure for all RPC paths).
    */
-  private static RaftPeer createRaftPeer(OMNodeDetails omNode, String nodeId) {
-    RaftPeerId raftPeerId = RaftPeerId.valueOf(nodeId);
-    RaftPeer.Builder builder = RaftPeer.newBuilder()
-        .setId(raftPeerId)
-        .setStartupRole(omNode.isRatisListener() ? RaftPeerRole.LISTENER : 
RaftPeerRole.FOLLOWER);
-    
-    if (omNode.isHostUnresolved()) {
-      builder.setAddress(omNode.getRatisHostPortStr());
-    } else {
-      InetSocketAddress ratisAddr = new InetSocketAddress(
-          omNode.getInetAddress(), omNode.getRatisPort());
-      builder.setAddress(ratisAddr);
-    }
-    
-    return builder.build();
+  static RaftPeer createRaftPeer(OMNodeDetails omNode) {
+    return RaftPeer.newBuilder()
+        .setId(RaftPeerId.valueOf(omNode.getNodeId()))
+        .setAddress(omNode.getRatisHostPortStr())
+        .setStartupRole(omNode.isRatisListener()
+            ? RaftPeerRole.LISTENER : RaftPeerRole.FOLLOWER)
+        .build();
   }
 
   /**
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisServer.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisServer.java
index f5e1b2dce81..6eda7a6ba1e 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisServer.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisServer.java
@@ -145,6 +145,46 @@ public void testStartOMRatisServer() throws Exception {
         "Ratis Server should be in running state");
   }
 
+  /**
+   * RaftPeer.address must preserve the configured host string
+   * verbatim -- not a Java-resolved {@code InetSocketAddress} form. When
+   * the operator configured a hostname, the hostname must survive into
+   * RaftPeer.address so gRPC's {@code DnsNameResolver} can re-resolve it
+   * on connection failure (Kubernetes pod restarts). When the operator
+   * configured an IP literal, that literal must survive too. The
+   * regression this test guards is "{@code createRaftPeer} pre-resolved
+   * a hostname into a numeric IP and handed the resolved form to
+   * RaftPeer," which would freeze the gRPC channel at that IP for the
+   * channel's lifetime.
+   */
+  @Test
+  public void testCreateRaftPeerUsesHostnameAddress() {
+    String hostname = "om-2.om.example.svc.cluster.local";
+    int rpcPort = 9862;
+    int ratisPort = 9872;
+    OMNodeDetails peer = new OMNodeDetails.Builder()
+        .setOMServiceId("test-service")
+        .setOMNodeId("om2")
+        .setHostAddress(hostname)
+        .setRpcPort(rpcPort)
+        .setRatisPort(ratisPort)
+        .build();
+
+    org.apache.ratis.protocol.RaftPeer raftPeer =
+        OzoneManagerRatisServer.createRaftPeer(peer);
+    String addr = raftPeer.getAddress();
+    assertEquals(hostname + ":" + ratisPort, addr,
+        "RaftPeer address must preserve the configured host string "
+            + "verbatim. The configured hostname must survive into "
+            + "RaftPeer so gRPC can re-resolve it -- pre-resolving into a "
+            + "numeric IP would freeze the channel at that IP for its "
+            + "lifetime.");
+    // Defensive: the configured hostname must not have been pre-resolved
+    // into a numeric IPv4 octet form before reaching RaftPeer.
+    String host = addr.substring(0, addr.lastIndexOf(':'));
+    assertThat(host).doesNotMatch("^\\d{1,3}(\\.\\d{1,3}){3}$");
+  }
+
   @Test
   public void testLoadSnapshotInfoOnStart() throws Exception {
     // Stop the Ratis server and manually update the snapshotInfo.


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

Reply via email to