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

lhotari pushed a commit to branch 
lh-support-webservice-bindaddresses-and-advertised-listeners
in repository https://gitbox.apache.org/repos/asf/pulsar.git

commit 10c370b57f410ca40b8673b27cb1b7829e740b3e
Author: Lari Hotari <[email protected]>
AuthorDate: Thu May 21 09:35:11 2026 +0300

    [improve][broker] Fall back to first advertised listener when 
internalListenerName is blank
    
    When the user explicitly sets `internalListenerName=` (blank) and configures
    `advertisedListeners` with custom listener names, the validator now uses the
    first parsed listener as the internal listener instead of forcing the 
constant
    default `internal`. This preserves the pre-PR behavior for users who relied 
on
    "first listener becomes the internal one".
    
    Resolution order in `MultipleListenerValidator`:
    1. Parse `advertisedListeners`.
    2. If `internalListenerName` is blank, fall back to the first listener 
parsed
       from `advertisedListeners`; if that map is empty too, fall back to the
       constant default `internal`.
    3. Synthesize / merge the legacy-port URLs as before.
    
    Cleanup:
    - Remove the duplicate fallback that lived inside `parseAdvertisedListeners`
      (and the now-unused `Optional` import) so the outer method is the single
      source of truth for the resolution rule.
    
    Tests:
    - `MultipleListenerValidatorTest`: two new cases —
      `testBlankInternalListenerNameFallsBackToFirstAdvertisedListener` and
      
`testBlankInternalListenerNameFallsBackToDefaultWhenNoAdvertisedListeners`.
    - `AdvertisedListenersTest`: update to exercise the new fallback by setting
      `internalListenerName=null` and collapsing three differently-named entries
      into one listener (`public`) with three schemes.
---
 .../validator/MultipleListenerValidator.java       | 26 +++++++----------
 .../validator/MultipleListenerValidatorTest.java   | 34 ++++++++++++++++++++++
 .../loadbalance/AdvertisedListenersTest.java       |  7 +++--
 3 files changed, 50 insertions(+), 17 deletions(-)

diff --git 
a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/validator/MultipleListenerValidator.java
 
b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/validator/MultipleListenerValidator.java
index c300da9682f..73fbef9305d 100644
--- 
a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/validator/MultipleListenerValidator.java
+++ 
b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/validator/MultipleListenerValidator.java
@@ -23,7 +23,6 @@ import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.TreeSet;
 import org.apache.commons.lang3.StringUtils;
@@ -56,13 +55,19 @@ public final class MultipleListenerValidator {
      * @return the parsed and validated advertised listeners, keyed by 
listener name.
      */
     public static Map<String, AdvertisedListener> 
validateAndAnalysisAdvertisedListener(ServiceConfiguration config) {
-        String internalListenerName = 
StringUtils.defaultIfBlank(config.getInternalListenerName(),
-                ServiceConfiguration.DEFAULT_INTERNAL_LISTENER_NAME);
+        Map<String, AdvertisedListener> result = 
parseAdvertisedListeners(config);
+
+        // Resolve `internalListenerName` if the user left it blank. For 
backward compatibility,
+        // prefer the first entry parsed from `advertisedListeners`; otherwise 
fall back to the
+        // constant default ("internal"). The field's own default is also 
"internal", so this
+        // branch is only taken when the user explicitly set the property to 
an empty string.
         if (StringUtils.isBlank(config.getInternalListenerName())) {
-            config.setInternalListenerName(internalListenerName);
+            String fallback = result.isEmpty()
+                    ? ServiceConfiguration.DEFAULT_INTERNAL_LISTENER_NAME
+                    : result.keySet().iterator().next();
+            config.setInternalListenerName(fallback);
         }
-
-        Map<String, AdvertisedListener> result = 
parseAdvertisedListeners(config);
+        String internalListenerName = config.getInternalListenerName();
 
         // Merge the legacy-port-derived internal listener URLs into any 
explicit configuration.
         // Explicit URLs in `advertisedListeners` take precedence; the 
legacy-port URLs fill in the
@@ -111,7 +116,6 @@ public final class MultipleListenerValidator {
         if (StringUtils.isBlank(config.getAdvertisedListeners())) {
             return new LinkedHashMap<>();
         }
-        Optional<String> firstListenerName = Optional.empty();
         Map<String, List<String>> listeners = new LinkedHashMap<>();
         for (final String str : 
StringUtils.split(config.getAdvertisedListeners(), ",")) {
             int index = str.indexOf(":");
@@ -120,18 +124,10 @@ public final class MultipleListenerValidator {
                         + str + " do not contain listener name");
             }
             String listenerName = StringUtils.trim(str.substring(0, index));
-            if (firstListenerName.isEmpty()) {
-                firstListenerName = Optional.of(listenerName);
-            }
             String value = StringUtils.trim(str.substring(index + 1));
             listeners.computeIfAbsent(listenerName, k -> new ArrayList<>(2));
             listeners.get(listenerName).add(value);
         }
-        // For backward compatibility, if `internalListenerName` was left 
blank, default it to the first
-        // listener parsed from `advertisedListeners`.
-        if (StringUtils.isBlank(config.getInternalListenerName())) {
-            config.setInternalListenerName(firstListenerName.get());
-        }
         final Map<String, AdvertisedListener> result = new LinkedHashMap<>();
         final Map<String, Set<String>> reverseMappings = new LinkedHashMap<>();
         for (final Map.Entry<String, List<String>> entry : 
listeners.entrySet()) {
diff --git 
a/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/validator/MultipleListenerValidatorTest.java
 
b/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/validator/MultipleListenerValidatorTest.java
index 438490d70c1..98fbe6cc1e8 100644
--- 
a/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/validator/MultipleListenerValidatorTest.java
+++ 
b/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/validator/MultipleListenerValidatorTest.java
@@ -75,6 +75,40 @@ public class MultipleListenerValidatorTest {
         assertEquals("internal", config.getInternalListenerName());
     }
 
+    @Test
+    public void 
testBlankInternalListenerNameFallsBackToFirstAdvertisedListener() {
+        // Backwards compatibility: if the user explicitly leaves 
`internalListenerName` blank but
+        // configures `advertisedListeners` with custom names, the first 
listener parsed from
+        // `advertisedListeners` is used as the internal listener — the 
previous Pulsar behavior.
+        ServiceConfiguration config = newConfigWithNoPorts();
+        config.setInternalListenerName("");
+        config.setAdvertisedListeners(
+                
"region1:pulsar://region1.example.com:6650,region2:pulsar://region2.example.com:6650");
+        Map<String, AdvertisedListener> listeners =
+                
MultipleListenerValidator.validateAndAnalysisAdvertisedListener(config);
+        // `region1` is the first declared, so it becomes the internal 
listener.
+        assertEquals(config.getInternalListenerName(), "region1");
+        // Both region entries are preserved in the result map.
+        assertEquals(listeners.size(), 2);
+        assertNotNull(listeners.get("region1"));
+        assertNotNull(listeners.get("region2"));
+    }
+
+    @Test
+    public void 
testBlankInternalListenerNameFallsBackToDefaultWhenNoAdvertisedListeners() {
+        // When both `internalListenerName` and `advertisedListeners` are 
blank, the validator falls
+        // back to the constant default `"internal"`.
+        ServiceConfiguration config = newConfigWithNoPorts();
+        config.setInternalListenerName("");
+        config.setBrokerServicePort(Optional.of(6650));
+        config.setWebServicePort(Optional.of(8080));
+        Map<String, AdvertisedListener> listeners =
+                
MultipleListenerValidator.validateAndAnalysisAdvertisedListener(config);
+        assertEquals(config.getInternalListenerName(), 
ServiceConfiguration.DEFAULT_INTERNAL_LISTENER_NAME);
+        
assertNotNull(listeners.get(ServiceConfiguration.DEFAULT_INTERNAL_LISTENER_NAME),
+                "the legacy-port-synthesized internal listener should be 
present");
+    }
+
     @Test(expectedExceptions = IllegalArgumentException.class)
     public void testMalformedListener() {
         ServiceConfiguration config = newConfigWithNoPorts();
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/loadbalance/AdvertisedListenersTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/loadbalance/AdvertisedListenersTest.java
index 64c0ee3f47a..2b6137ce221 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/loadbalance/AdvertisedListenersTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/loadbalance/AdvertisedListenersTest.java
@@ -73,10 +73,13 @@ public class AdvertisedListenersTest extends 
MultiBrokerBaseTest {
 
         // Use invalid domain name as identifier and instead make sure the 
advertised listeners work as intended
         conf.setAdvertisedAddress(advertisedAddress);
+        // let it be detected from advertised listeners
+        conf.setInternalListenerName(null);
+        // set advertised listeners
         conf.setAdvertisedListeners(
                 "public:pulsar://localhost:" + pulsarPort
-                        + ",public_http:http://localhost:"; + httpPort
-                        + ",public_https:https://localhost:"; + httpsPort);
+                        + ",public:http://localhost:"; + httpPort
+                        + ",public:https://localhost:"; + httpsPort);
         conf.setBrokerServicePort(Optional.of(pulsarPort));
         conf.setWebServicePort(Optional.of(httpPort));
     }

Reply via email to