diff --git a/server/src/main/java/org/apache/druid/rpc/ServiceLocation.java b/server/src/main/java/org/apache/druid/rpc/ServiceLocation.java index 552e0d6e9ba3..97824aa408db 100644 --- a/server/src/main/java/org/apache/druid/rpc/ServiceLocation.java +++ b/server/src/main/java/org/apache/druid/rpc/ServiceLocation.java @@ -73,7 +73,7 @@ public ServiceLocation(final String host, final int plaintextPort, final int tls */ public static ServiceLocation fromDruidNode(final DruidNode druidNode) { - return new ServiceLocation(druidNode.getHost(), druidNode.getPlaintextPort(), druidNode.getTlsPort(), ""); + return new ServiceLocation(druidNode.getHost(), druidNode.getAdvertisedPlaintextPort(), druidNode.getTlsPort(), ""); } /** diff --git a/server/src/main/java/org/apache/druid/server/DruidNode.java b/server/src/main/java/org/apache/druid/server/DruidNode.java index 19252fde755c..bc59998b2fa8 100644 --- a/server/src/main/java/org/apache/druid/server/DruidNode.java +++ b/server/src/main/java/org/apache/druid/server/DruidNode.java @@ -100,6 +100,10 @@ public class DruidNode @JsonProperty private Map labels; + @JsonProperty + @Max(0xffff) + private int advertisedPlaintextPort = -1; + public DruidNode( String serviceName, String host, @@ -110,7 +114,36 @@ public DruidNode( boolean enableTlsPort ) { - this(serviceName, host, bindOnHost, plaintextPort, null, tlsPort, enablePlaintextPort, enableTlsPort, null); + this(serviceName, host, bindOnHost, plaintextPort, null, tlsPort, enablePlaintextPort, enableTlsPort, null, null); + } + + public DruidNode( + String serviceName, + String host, + boolean bindOnHost, + Integer plaintextPort, + Integer port, + Integer tlsPort, + Boolean enablePlaintextPort, + boolean enableTlsPort + ) + { + this(serviceName, host, bindOnHost, plaintextPort, port, tlsPort, enablePlaintextPort, enableTlsPort, null, null); + } + + public DruidNode( + String serviceName, + String host, + boolean bindOnHost, + Integer plaintextPort, + Integer port, + Integer tlsPort, + Boolean enablePlaintextPort, + boolean enableTlsPort, + Map labels + ) + { + this(serviceName, host, bindOnHost, plaintextPort, port, tlsPort, enablePlaintextPort, enableTlsPort, labels, null); } /** @@ -139,7 +172,8 @@ public DruidNode( @JacksonInject(useInput = OptBoolean.TRUE) @Named("tlsServicePort") @JsonProperty("tlsPort") Integer tlsPort, @JsonProperty("enablePlaintextPort") Boolean enablePlaintextPort, @JsonProperty("enableTlsPort") boolean enableTlsPort, - @JsonProperty("labels") @Nullable Map labels + @JsonProperty("labels") @Nullable Map labels, + @JsonProperty("advertisedPlaintextPort") Integer advertisedPlaintextPort ) { init( @@ -150,7 +184,8 @@ public DruidNode( tlsPort, enablePlaintextPort == null || enablePlaintextPort.booleanValue(), enableTlsPort, - labels + labels, + advertisedPlaintextPort ); } @@ -162,7 +197,8 @@ private void init( Integer tlsPort, boolean enablePlaintextPort, boolean enableTlsPort, - Map labels + Map labels, + Integer advertisedPlaintextPort ) { Preconditions.checkNotNull(serviceName); @@ -210,8 +246,12 @@ private void init( } } this.plaintextPort = plainTextPort; + this.advertisedPlaintextPort = advertisedPlaintextPort != null && advertisedPlaintextPort > 0 + ? advertisedPlaintextPort + : this.plaintextPort; } else { this.plaintextPort = -1; + this.advertisedPlaintextPort = -1; } if (enableTlsPort) { this.tlsPort = tlsPort; @@ -276,9 +316,14 @@ public String getBuildRevision() return buildRevision; } + public int getAdvertisedPlaintextPort() + { + return advertisedPlaintextPort; + } + public DruidNode withService(String service) { - return new DruidNode(service, host, bindOnHost, plaintextPort, tlsPort, enablePlaintextPort, enableTlsPort); + return new DruidNode(service, host, bindOnHost, plaintextPort, null, tlsPort, enablePlaintextPort, enableTlsPort, labels, advertisedPlaintextPort); } public String getServiceScheme() @@ -292,10 +337,10 @@ public String getServiceScheme() public String getHostAndPort() { if (enablePlaintextPort) { - if (plaintextPort < 0) { + if (advertisedPlaintextPort < 0) { return HostAndPort.fromString(host).toString(); } else { - return HostAndPort.fromParts(host, plaintextPort).toString(); + return HostAndPort.fromParts(host, advertisedPlaintextPort).toString(); } } return null; @@ -359,6 +404,7 @@ public boolean equals(Object o) enablePlaintextPort == druidNode.enablePlaintextPort && tlsPort == druidNode.tlsPort && enableTlsPort == druidNode.enableTlsPort && + advertisedPlaintextPort == druidNode.advertisedPlaintextPort && Objects.equals(serviceName, druidNode.serviceName) && Objects.equals(host, druidNode.host) && Objects.equals(labels, druidNode.labels); @@ -367,7 +413,7 @@ public boolean equals(Object o) @Override public int hashCode() { - return Objects.hash(serviceName, host, port, plaintextPort, enablePlaintextPort, tlsPort, enableTlsPort, labels); + return Objects.hash(serviceName, host, port, plaintextPort, enablePlaintextPort, tlsPort, enableTlsPort, labels, advertisedPlaintextPort); } @Override @@ -379,6 +425,7 @@ public String toString() ", bindOnHost=" + bindOnHost + ", port=" + port + ", plaintextPort=" + plaintextPort + + ", advertisedPlaintextPort=" + advertisedPlaintextPort + ", enablePlaintextPort=" + enablePlaintextPort + ", tlsPort=" + tlsPort + ", enableTlsPort=" + enableTlsPort + diff --git a/server/src/test/java/org/apache/druid/server/DruidNodeTest.java b/server/src/test/java/org/apache/druid/server/DruidNodeTest.java index 9b7762904d17..fc9a0f04a094 100644 --- a/server/src/test/java/org/apache/druid/server/DruidNodeTest.java +++ b/server/src/test/java/org/apache/druid/server/DruidNodeTest.java @@ -188,6 +188,118 @@ public void testDefaultsAndSanity() Assert.assertEquals(-1, node.getTlsPort()); } + @Test + public void testAdvertisedPlaintextPort() + { + // When not set, advertisedPlaintextPort defaults to plaintextPort + DruidNode node = new DruidNode("test", "host", false, 8082, null, true, false); + Assert.assertEquals(8082, node.getPlaintextPort()); + Assert.assertEquals(8082, node.getAdvertisedPlaintextPort()); + Assert.assertEquals("host:8082", node.getHostAndPort()); + Assert.assertEquals("host:8082", node.getHostAndPortToUse()); + + // When set, advertisedPlaintextPort overrides in getHostAndPort() but not getPlaintextPort() + node = new DruidNode("test", "host", false, 8082, null, 9443, true, false, null, 9443); + Assert.assertEquals(8082, node.getPlaintextPort()); + Assert.assertEquals(9443, node.getAdvertisedPlaintextPort()); + Assert.assertEquals("host:9443", node.getHostAndPort()); + Assert.assertEquals("host:9443", node.getHostAndPortToUse()); + // getPortToUse() still returns the bind port + Assert.assertEquals(8082, node.getPortToUse()); + } + + @Test + public void testAdvertisedPlaintextPortWithTls() + { + // advertisedPlaintextPort with TLS enabled — getHostAndPortToUse() prefers TLS + DruidNode node = new DruidNode("test", "host", false, 8082, null, 8443, true, true, null, 9443); + Assert.assertEquals(8082, node.getPlaintextPort()); + Assert.assertEquals(9443, node.getAdvertisedPlaintextPort()); + Assert.assertEquals(8443, node.getTlsPort()); + Assert.assertEquals("host:9443", node.getHostAndPort()); + Assert.assertEquals("host:8443", node.getHostAndTlsPort()); + Assert.assertEquals("host:8443", node.getHostAndPortToUse()); + } + + @Test + public void testAdvertisedPlaintextPortDisabledPlaintext() + { + // When plaintext is disabled, advertisedPlaintextPort is -1 regardless + DruidNode node = new DruidNode("test", "host", false, null, null, 8443, false, true, null, 9443); + Assert.assertEquals(-1, node.getPlaintextPort()); + Assert.assertEquals(-1, node.getAdvertisedPlaintextPort()); + Assert.assertNull(node.getHostAndPort()); + } + + @Test + public void testAdvertisedPlaintextPortSerde() throws Exception + { + // Serialization roundtrip preserves advertisedPlaintextPort + DruidNode original = new DruidNode("service", "host", true, 8082, null, 5678, true, true, null, 9443); + DruidNode actual = mapper.readValue(mapper.writeValueAsString(original), DruidNode.class); + Assert.assertEquals(8082, actual.getPlaintextPort()); + Assert.assertEquals(9443, actual.getAdvertisedPlaintextPort()); + Assert.assertEquals(5678, actual.getTlsPort()); + Assert.assertEquals("host:9443", actual.getHostAndPort()); + } + + @Test + public void testAdvertisedPlaintextPortBackwardCompatDeserialization() throws Exception + { + // Old JSON without advertisedPlaintextPort — should default to plaintextPort + String json = "{\n" + + " \"service\":\"service\",\n" + + " \"host\":\"host\",\n" + + " \"plaintextPort\":8082,\n" + + " \"enablePlaintextPort\":true,\n" + + " \"enableTlsPort\":false\n" + + "}\n"; + DruidNode actual = mapper.readValue(json, DruidNode.class); + Assert.assertEquals(8082, actual.getPlaintextPort()); + Assert.assertEquals(8082, actual.getAdvertisedPlaintextPort()); + Assert.assertEquals("host:8082", actual.getHostAndPort()); + } + + @Test + public void testAdvertisedPlaintextPortDeserialization() throws Exception + { + // JSON with advertisedPlaintextPort set + String json = "{\n" + + " \"service\":\"service\",\n" + + " \"host\":\"host\",\n" + + " \"plaintextPort\":8082,\n" + + " \"advertisedPlaintextPort\":9443,\n" + + " \"enablePlaintextPort\":true,\n" + + " \"enableTlsPort\":false\n" + + "}\n"; + DruidNode actual = mapper.readValue(json, DruidNode.class); + Assert.assertEquals(8082, actual.getPlaintextPort()); + Assert.assertEquals(9443, actual.getAdvertisedPlaintextPort()); + Assert.assertEquals("host:9443", actual.getHostAndPort()); + Assert.assertEquals("host:9443", actual.getHostAndPortToUse()); + } + + @Test + public void testAdvertisedPlaintextPortWithService() + { + DruidNode node = new DruidNode("test", "host", false, 8082, null, 9443, true, false, null, 9443); + DruidNode copy = node.withService("other"); + Assert.assertEquals("other", copy.getServiceName()); + Assert.assertEquals(8082, copy.getPlaintextPort()); + Assert.assertEquals(9443, copy.getAdvertisedPlaintextPort()); + } + + @Test + public void testAdvertisedPlaintextPortEquality() + { + DruidNode a = new DruidNode("test", "host", false, 8082, null, 9443, true, false, null, 9443); + DruidNode b = new DruidNode("test", "host", false, 8082, null, 9443, true, false, null, 9443); + DruidNode c = new DruidNode("test", "host", false, 8082, null, true, false); + Assert.assertEquals(a, b); + Assert.assertNotEquals(a, c); + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + @Test(expected = IllegalArgumentException.class) public void testConflictingPorts() {