You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by vj...@apache.org on 2026/09/12 22:42:38 UTC

(hbase) branch master updated: HBASE-30355 setupMiniKdc port-conflict retry never triggers because Kerby throws KrbException, not BindException (#8604)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 90c4d776bb0 HBASE-30355 setupMiniKdc port-conflict retry never triggers because Kerby throws KrbException, not BindException (#8604)
90c4d776bb0 is described below

commit 90c4d776bb0a78147bb32c56285365e98b3d6b3e
Author: Aman Poonia <ap...@salesforce.com>
AuthorDate: Sun Sep 13 04:12:31 2026 +0530

    HBASE-30355 setupMiniKdc port-conflict retry never triggers because Kerby throws KrbException, not BindException (#8604)
    
    Signed-off-by: Viraj Jasani <vj...@apache.org>
---
 .../apache/hadoop/hbase/http/log/TestLogLevel.java | 58 ++++++++++++++++++++--
 .../org/apache/hadoop/hbase/HBaseTestingUtil.java  | 37 ++++++++++++--
 2 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
index 9e470025c39..41ed421be91 100644
--- a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
+++ b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
@@ -30,6 +30,7 @@ import java.net.BindException;
 import java.net.SocketException;
 import java.net.URI;
 import java.security.PrivilegedExceptionAction;
+import java.util.Locale;
 import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -153,20 +154,71 @@ public class TestLogLevel {
         dir = new File(HTU.getDataTestDir("kdc").toUri().getPath());
         kdc = new MiniKdc(conf, dir);
         kdc.start();
-      } catch (BindException e) {
-        FileUtils.deleteDirectory(dir); // clean directory
+      } catch (Exception e) {
+        // Catch Exception, not BindException/KrbException: Kerby wraps the bind failure in a
+        // KrbException whose shape varies by version (see isBindException), so we recognise the
+        // port conflict via that predicate rather than a type. We also avoid importing kerby types
+        // here (see HBASE-29117).
+        FileUtils.deleteDirectory(dir); // clean directory regardless of failure type
+        if (!isBindException(e)) {
+          throw e; // not a port conflict, do not mask the real failure behind a retry
+        }
         numTries++;
         if (numTries == 3) {
           log.error("Failed setting up MiniKDC. Tried " + numTries + " times.");
           throw e;
         }
-        log.error("BindException encountered when setting up MiniKdc. Trying again.");
+        log.error("Bind conflict encountered when setting up MiniKdc, retrying (attempt " + numTries
+          + ").");
         bindException = true;
       }
     } while (bindException);
     return kdc;
   }
 
+  /**
+   * The Kerby-backed {@link MiniKdc} wraps a failure to bind the KDC port in a
+   * {@code org.apache.kerby...KrbException} rather than surfacing a {@link BindException} directly,
+   * so we inspect the whole cause chain plus the message to recognise a port conflict. Both checks
+   * are load-bearing across Kerby versions: kerby 1.x preserves the original {@link BindException}
+   * as the cause (caught by the cause-chain check) while kerby 2.x drops the cause and only appends
+   * the bind message (caught by the message check). The message match is lower-cased since the
+   * wording is JDK/OS specific.
+   */
+  static boolean isBindException(Throwable t) {
+    for (Throwable cause = t; cause != null; cause = cause.getCause()) {
+      if (cause instanceof BindException) {
+        return true;
+      }
+      String msg = cause.getMessage();
+      if (msg != null && msg.toLowerCase(Locale.ROOT).contains("address already in use")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Deterministic regression test for the retry predicate. A port conflict raised by the
+   * Kerby-backed {@link MiniKdc} must be recognised as a bind failure whether the
+   * {@link BindException} is preserved in the cause chain or only reflected in the message;
+   * unrelated failures must not be. We model the Kerby wrapper with a plain {@link Exception}
+   * rather than constructing a real Kerby exception, so the test does not import kerby types (see
+   * HBASE-29117).
+   */
+  @Test
+  public void testIsBindExceptionRecognizesKerbyWrappedBindFailure() {
+    assertTrue(
+      isBindException(new Exception("Failed to start DefaultKrbServer",
+        new BindException("Address already in use"))),
+      "a bind failure preserved in the cause chain should be recognised");
+    assertTrue(
+      isBindException(new Exception("Failed to start DefaultKrbServer. Address already in use")),
+      "a wrapper whose message reports the bind conflict should be recognised");
+    assertFalse(isBindException(new RuntimeException("boom")),
+      "an unrelated failure must not be treated as a retryable bind conflict");
+  }
+
   static private void setupSSL(File base) throws Exception {
     clientConf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTPS_ONLY.name());
     clientConf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0");
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtil.java
index 8bf46059177..1bb05119fb7 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtil.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtil.java
@@ -43,6 +43,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Properties;
@@ -3731,14 +3732,22 @@ public class HBaseTestingUtil extends HBaseZKTestingUtil {
         dir = new File(getDataTestDir("kdc").toUri().getPath());
         kdc = new MiniKdc(conf, dir);
         kdc.start();
-      } catch (BindException e) {
-        FileUtils.deleteDirectory(dir); // clean directory
+      } catch (Exception e) {
+        // Catch Exception, not BindException/KrbException: Kerby wraps the bind failure in a
+        // KrbException whose shape varies by version (see isBindException), so we recognise the
+        // port conflict via that predicate rather than a type. We also avoid importing kerby types
+        // here (see HBASE-29117).
+        FileUtils.deleteDirectory(dir); // clean directory regardless of failure type
+        if (!isBindException(e)) {
+          throw e; // not a port conflict, do not mask the real failure behind a retry
+        }
         numTries++;
         if (numTries == 3) {
           LOG.error("Failed setting up MiniKDC. Tried " + numTries + " times.");
           throw e;
         }
-        LOG.error("BindException encountered when setting up MiniKdc. Trying again.");
+        LOG.error("Bind conflict encountered when setting up MiniKdc, retrying (attempt " + numTries
+          + ").");
         bindException = true;
       }
     } while (bindException);
@@ -3746,6 +3755,28 @@ public class HBaseTestingUtil extends HBaseZKTestingUtil {
     return kdc;
   }
 
+  /**
+   * The Kerby-backed {@link MiniKdc} wraps a failure to bind the KDC port in a
+   * {@code org.apache.kerby...KrbException} rather than surfacing a {@link BindException} directly,
+   * so we inspect the whole cause chain plus the message to recognise a port conflict. Both checks
+   * are load-bearing across Kerby versions: kerby 1.x preserves the original {@link BindException}
+   * as the cause (caught by the cause-chain check) while kerby 2.x drops the cause and only appends
+   * the bind message (caught by the message check). The message match is lower-cased since the
+   * wording is JDK/OS specific.
+   */
+  static boolean isBindException(Throwable t) {
+    for (Throwable cause = t; cause != null; cause = cause.getCause()) {
+      if (cause instanceof BindException) {
+        return true;
+      }
+      String msg = cause.getMessage();
+      if (msg != null && msg.toLowerCase(Locale.ROOT).contains("address already in use")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public int getNumHFiles(final TableName tableName, final byte[] family) {
     int numHFiles = 0;
     for (RegionServerThread regionServerThread : getMiniHBaseCluster().getRegionServerThreads()) {