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 bdff9801bb2 HDDS-16308. Validate advertised addresses before startup,
registration, and publication (#11272)
bdff9801bb2 is described below
commit bdff9801bb2bc4769bf6d1f1decd6a80908e1c72
Author: rjgoyln <[email protected]>
AuthorDate: Sat Oct 10 05:57:45 2026 +0800
HDDS-16308. Validate advertised addresses before startup, registration, and
publication (#11272)
---
.../hdds/scm/client/TestHddsClientUtils.java | 40 ++++-
.../java/org/apache/hadoop/hdds/HddsUtils.java | 152 +++++++++++++++++
.../java/org/apache/hadoop/hdds/NodeDetails.java | 3 +-
.../org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java | 6 +
.../java/org/apache/hadoop/hdds/TestHddsUtils.java | 190 +++++++++++++++++++++
.../apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java | 78 +++++++++
.../common/states/datanode/InitDatanodeState.java | 8 +-
.../container/common/TestDatanodeStateMachine.java | 12 ++
.../apache/hadoop/hdds/utils/HddsServerUtil.java | 5 +
.../hadoop/hdds/scm/ha/SCMHANodeDetails.java | 4 +-
.../apache/hadoop/hdds/scm/ha/SCMNodeDetails.java | 6 -
.../hadoop/hdds/scm/TestHddsServerUtils.java | 50 ++++++
.../hadoop/hdds/scm/ha/TestSCMConfiguration.java | 29 ++++
.../main/java/org/apache/hadoop/ozone/OmUtils.java | 8 +
.../java/org/apache/hadoop/ozone/TestOmUtils.java | 38 ++++-
...doopRpcOMFollowerReadFailoverProxyProvider.java | 2 +-
.../ozone/om/ha/TestOMFailoverProxyProvider.java | 2 +-
.../hadoop/ozone/om/helpers/TestOMNodeDetails.java | 19 +++
.../ozone/om/TestOzoneManagerConfiguration.java | 14 +-
.../apache/hadoop/ozone/om/ha/OMHANodeDetails.java | 4 +-
.../org/apache/hadoop/ozone/recon/ReconServer.java | 2 +
21 files changed, 641 insertions(+), 31 deletions(-)
diff --git
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
index 647ae727465..98013d891c1 100644
---
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
+++
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/client/TestHddsClientUtils.java
@@ -184,11 +184,6 @@ public void testClientFallbackToScmNamesWithPort() {
@Test
public void testClientAddressIPv6() {
- // Bare IPv6 literal without port: port falls back to the default and the
- // host must be re-bracketed before the address string is parsed.
- checkScmClientAddr(OZONE_SCM_CLIENT_ADDRESS_KEY, "2001:db8::1",
- "2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
-
// Bracketed IPv6 literal with explicit port.
checkScmClientAddr(OZONE_SCM_CLIENT_ADDRESS_KEY, "[2001:db8::1]:9876",
"2001:db8:0:0:0:0:0:1", 9876);
@@ -199,12 +194,26 @@ public void testClientAddressIPv6() {
"2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
}
+ /**
+ * A bare literal used to resolve to the default port (HDDS-15773). It is
+ * rejected since HDDS-16308, because the same text also reads as a shorter
+ * host with the trailing group for a port.
+ */
@Test
- public void testClientFallbackToScmNamesIPv6() {
- // Bare IPv6 literal in ozone.scm.names.
- checkScmClientAddr(OZONE_SCM_NAMES, "2001:db8::1",
- "2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
+ public void testClientAddressRejectsBareIPv6Literal() {
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "2001:db8::1");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsUtils.getScmAddressForClients(conf));
+
+ assertThat(e.getMessage())
+ .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+ .contains("[2001:db8::1]");
+ }
+ @Test
+ public void testClientFallbackToScmNamesIPv6() {
// On the ozone.scm.names fallback path an inline port is ignored and the
// default client port is used instead (same semantics as
// testClientFallbackToScmNamesWithPort).
@@ -212,6 +221,19 @@ public void testClientFallbackToScmNamesIPv6() {
"2001:db8:0:0:0:0:0:1", OZONE_SCM_CLIENT_PORT_DEFAULT);
}
+ @Test
+ public void testClientFallbackToScmNamesRejectsBareIPv6Literal() {
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_NAMES, "2001:db8::1");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsUtils.getScmAddressForClients(conf));
+
+ assertThat(e.getMessage())
+ .contains(OZONE_SCM_NAMES)
+ .contains("[2001:db8::1]");
+ }
+
@Test
@SuppressWarnings("StringSplitter")
public void testBlockClientFallbackToClientWithPort() {
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
index 93a84f7c6f7..e9079b8b8bb 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
@@ -34,6 +34,7 @@
import com.google.common.base.Preconditions;
import com.google.common.net.HostAndPort;
+import com.google.common.net.InetAddresses;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.ServiceException;
import jakarta.annotation.Nonnull;
@@ -42,6 +43,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
@@ -138,6 +140,11 @@ public static Collection<InetSocketAddress>
getScmAddressForClients(
String address = conf.getTrimmed(OZONE_SCM_CLIENT_ADDRESS_KEY);
int port = -1;
+ // Only the bracket rule applies: SCM rewrites this property with its
+ // bound host, a wildcard by default, and a process sharing its
+ // configuration reads that back here.
+ validateHostPortAuthority(OZONE_SCM_CLIENT_ADDRESS_KEY, address);
+
if (address == null) {
// fall back to ozone.scm.names for non-ha
Collection<String> scmAddresses =
@@ -155,6 +162,7 @@ public static Collection<InetSocketAddress>
getScmAddressForClients(
}
address = scmAddresses.iterator().next();
+ validateAdvertisedAddress(OZONE_SCM_NAMES, address);
port = conf.getInt(OZONE_SCM_CLIENT_PORT_KEY,
OZONE_SCM_CLIENT_PORT_DEFAULT);
@@ -243,6 +251,144 @@ public static String getHostPortString(String host, int
port) {
return HostAndPort.fromParts(host, port).toString();
}
+ /**
+ * Rejects a host configured as an advertised endpoint that cannot identify
+ * this node to a peer. A wildcard ({@code 0.0.0.0}, {@code ::}) names every
+ * local interface instead of one reachable endpoint, a link-local literal is
+ * only meaningful on a single link, and the zone identifier of a scoped
+ * literal ({@code fe80::1%eth0}) names an interface on the host that wrote
+ * it, so it cannot be resolved by a peer nor encoded in the SAN extension of
+ * an X.509 certificate (see RFC-5280). A DNS name is accepted without being
+ * resolved, and so is loopback, which a single-host deployment legitimately
+ * advertises.
+ *
+ * @param key the property the host was configured under
+ * @param host a hostname or an IP literal, bracketed or not
+ * @throws ConfigurationException if the host cannot be advertised
+ */
+ public static void validateAdvertisedHost(String key, String host) {
+ if (host == null || host.isEmpty()) {
+ return;
+ }
+
+ // A host-only property can still be written with brackets, and the
brackets
+ // are stripped on the way to the address, so unwrap them here or the
+ // literal inside escapes every check below.
+ final String literal = host.startsWith("[") && host.endsWith("]")
+ ? host.substring(1, host.length() - 1)
+ : host;
+
+ // Judged on the text, like HddsServerUtil.isScopedOrMaskingIPv6Address,
+ // because the scope makes the literal unresolvable here:
+ // InetAddresses.forString("fe80::1%eth0") throws unless this host happens
+ // to own an interface by that name.
+ if (literal.indexOf('%') >= 0 || literal.indexOf('/') >= 0) {
+ throw new ConfigurationException(String.format(
+ "%s = %s carries a zone identifier or prefix length, which cannot be
advertised: it names an interface "
+ + "on this host, so a peer cannot resolve it and it cannot be
encoded in an X.509 certificate.",
+ key, host));
+ }
+
+ if (!InetAddresses.isInetAddress(literal)) {
+ return;
+ }
+
+ final InetAddress address = InetAddresses.forString(literal);
+ if (address.isAnyLocalAddress()) {
+ throw new ConfigurationException(String.format(
+ "%s = %s is a wildcard address, which cannot be advertised.
Configure the address this node is reachable "
+ + "at, and listen on every interface through the matching bind
host property.",
+ key, host));
+ }
+ if (address.isLinkLocalAddress()) {
+ throw new ConfigurationException(String.format(
+ "%s = %s is a link-local address, which is only reachable on one
link and cannot be advertised.",
+ key, host));
+ }
+ }
+
+ /**
+ * Rejects a configured advertised address, both for the textual form of its
+ * authority and for the host it names.
+ *
+ * @param key the property the address was configured under
+ * @param value host or host:port
+ * @throws ConfigurationException if the address cannot be advertised
+ * @see #validateAdvertisedHost(String, String)
+ */
+ public static void validateAdvertisedAddress(String key, String value) {
+ // The host first: a wildcard is rejected outright, so it must not be told
+ // to add brackets that leave it rejected anyway.
+ getHostName(value).ifPresent(host -> validateAdvertisedHost(key, host));
+ validateHostPortAuthority(key, value);
+ }
+
+ /**
+ * Rejects an advertised address configured under any of the given
properties.
+ * A property holding a comma-separated list is checked entry by entry, and a
+ * property that is not set is skipped.
+ *
+ * @param conf the configuration to read
+ * @param keys the properties to check
+ * @throws ConfigurationException if any configured address cannot be
advertised
+ * @see #validateAdvertisedAddress(String, String)
+ */
+ public static void validateAdvertisedAddressConfig(ConfigurationSource conf,
String... keys) {
+ for (final String key : keys) {
+ for (final String value : conf.getTrimmedStringCollection(key)) {
+ validateAdvertisedAddress(key, value);
+ }
+ }
+ }
+
+ /**
+ * Rejects an unbracketed IPv6 literal configured under a property that a
port
+ * may follow. Both readings of {@code 2001:db8::1:9862} are valid IPv6
+ * literals - the host {@code 2001:db8::1} on port 9862, or the whole literal
+ * on the property's default port - and nothing in the text tells them apart,
+ * so one of them is used silently. Brackets remove the ambiguity, and are
+ * needed for the single-colon form too, since a bare literal there is read
as
+ * a host and a port.
+ *
+ * @param key the property the value was configured under
+ * @param value host or host:port
+ * @throws ConfigurationException if the host is an unbracketed IPv6 literal
+ */
+ private static void validateHostPortAuthority(String key, String value) {
+ if (value == null || value.isEmpty() || value.startsWith("[")) {
+ return;
+ }
+
+ final String host = HostAndPort.fromString(value).getHost();
+ final int lastColon = host.lastIndexOf(':');
+ if (lastColon < 0) {
+ return;
+ }
+
+ final String shorterHost = host.substring(0, lastColon);
+ final String trailingGroup = host.substring(lastColon + 1);
+ if (isPortNumber(trailingGroup) &&
InetAddresses.isInetAddress(shorterHost)) {
+ throw new ConfigurationException(String.format(
+ "%s = %s is an unbracketed IPv6 literal. Write %s for host %s with
port %s, or %s to use the whole "
+ + "literal as the host.",
+ key, value, getHostPortString(shorterHost,
Integer.parseInt(trailingGroup)), shorterHost, trailingGroup,
+ HostAndPort.fromHost(host)));
+ }
+ throw new ConfigurationException(String.format(
+ "%s = %s is an unbracketed IPv6 literal. Write %s; a port may follow
this property, so the host has to be "
+ + "bracketed.",
+ key, value, HostAndPort.fromHost(host)));
+ }
+
+ private static boolean isPortNumber(String value) {
+ try {
+ final int port = Integer.parseInt(value);
+ return port > 0 && port <= 65535;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
/**
* Parse a Ratis role string produced by
* {@code SCMRatisServerImpl.getRatisRoles()} into its constituent fields.
@@ -337,11 +483,14 @@ public static OptionalInt getNumberFromConfigKeys(
* @return first port number component found from the given keys, or absent.
* @throws IllegalArgumentException if any values are not in the 'host'
* or host:port format.
+ * @throws ConfigurationException if any value holds an unbracketed IPv6
+ * literal, which cannot be told apart from a host and a port.
*/
public static OptionalInt getPortNumberFromConfigKeys(
ConfigurationSource conf, String... keys) {
for (final String key : keys) {
final String value = conf.getTrimmed(key);
+ validateHostPortAuthority(key, value);
final OptionalInt hostPort = getHostPort(value);
if (hostPort.isPresent()) {
return hostPort;
@@ -360,10 +509,13 @@ public static OptionalInt getPortNumberFromConfigKeys(
* @return the hostname (NB: may not be a FQDN)
* @throws UnknownHostException if the hdds.datanode.dns.interface
* option is used and the hostname can not be determined
+ * @throws ConfigurationException if the configured hostname cannot be
+ * advertised to SCM
*/
public static String getHostName(ConfigurationSource conf)
throws UnknownHostException {
String name = conf.get(HDDS_DATANODE_HOST_NAME_KEY);
+ validateAdvertisedHost(HDDS_DATANODE_HOST_NAME_KEY, name);
if (name == null) {
String dnsInterface = conf.get(
CommonConfigurationKeysPublic.HADOOP_SECURITY_DNS_INTERFACE_KEY);
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
index ae871a8a86f..93f9d62ca56 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/NodeDetails.java
@@ -111,7 +111,8 @@ public int getRatisPort() {
}
public String getRpcAddressString() {
- return NetUtils.getHostPortString(getRpcAddress());
+ final InetSocketAddress addr = getRpcAddress();
+ return HddsUtils.getHostPortString(addr.getHostName(), addr.getPort());
}
public String getHttpAddress() {
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
index 98b138de1ce..f3a7b59a0e4 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeInfo.java
@@ -96,6 +96,7 @@ public static List<SCMNodeInfo>
buildNodeInfo(ConfigurationSource conf) {
if (scmAddress == null) {
throw new ConfigurationException(addressKey + "is not defined");
}
+ HddsUtils.validateAdvertisedHost(addressKey, scmAddress);
// Get port from Address Key if defined, else fall back to port key.
int scmClientPort = getPort(conf, scmServiceId, scmNodeId,
@@ -126,6 +127,11 @@ public static List<SCMNodeInfo>
buildNodeInfo(ConfigurationSource conf) {
} else {
scmServiceId = SCM_DUMMY_SERVICE_ID;
+ // The *.address properties double as the non-HA SCM's listen addresses,
+ // which it rewrites with its bound host (a wildcard by default) once its
+ // RPC servers start, so only ozone.scm.names is checked for the host.
+ HddsUtils.validateAdvertisedAddressConfig(conf, OZONE_SCM_NAMES);
+
// Following current approach of fall back to
// OZONE_SCM_CLIENT_ADDRESS_KEY to figure out hostname.
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
index 065dc8b6829..fc2da4832a6 100644
--- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
@@ -19,9 +19,18 @@
import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_CLIENT_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_CLIENT_BIND_HOST_KEY;
+import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_HOST_NAME_KEY;
import static org.apache.hadoop.hdds.HddsUtils.processForLogging;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedAddress;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedAddressConfig;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedHost;
+import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY;
+import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_BIND_HOST_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_NAMES;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_PIPELINE_OWNER_CONTAINER_COUNT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -34,6 +43,7 @@
import java.util.Optional;
import java.util.OptionalInt;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
import org.apache.hadoop.ozone.ha.ConfUtils;
@@ -41,6 +51,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
/**
* Testing HddsUtils.
@@ -265,4 +276,183 @@ void testRedactSensitivePropsForLogging() {
/* Verify that non-sensitive properties retain their value */
assertEquals(processedConf.get("ozone.normal.config"), ORIGINAL_VALUE);
}
+
+ @ParameterizedTest
+ @MethodSource("ambiguousIPv6Authorities")
+ void getPortNumberFromConfigKeysRejectsAmbiguousIPv6Authority(String value,
String host, String port) {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, value);
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsUtils.getPortNumberFromConfigKeys(conf,
OZONE_SCM_CLIENT_ADDRESS_KEY));
+
+ // Both readings have to be spelled out, since the configured text does not
+ // say which one was meant.
+ assertThat(e.getMessage())
+ .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+ .contains(value)
+ .contains("[" + host + "]:" + port)
+ .contains("[" + value + "]");
+ }
+
+ static List<Arguments> ambiguousIPv6Authorities() {
+ return Arrays.asList(
+ Arguments.of("2001:db8::1:9862", "2001:db8::1", "9862"),
+ Arguments.of("fd00::1:2", "fd00::1", "2")
+ );
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"::1", "2001:db8::1", "2001:db8::1:abcd",
"fe80::1%eth0"})
+ void getPortNumberFromConfigKeysRejectsBareIPv6Literal(String value) {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, value);
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsUtils.getPortNumberFromConfigKeys(conf,
OZONE_SCM_CLIENT_ADDRESS_KEY));
+
+ assertThat(e.getMessage())
+ .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+ .contains("[" + value + "]");
+ }
+
+ @ParameterizedTest
+ @MethodSource("unambiguousAuthorities")
+ void getPortNumberFromConfigKeysAcceptsUnambiguousAuthority(String value,
OptionalInt expectedPort) {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, value);
+
+ assertEquals(expectedPort,
+ HddsUtils.getPortNumberFromConfigKeys(conf,
OZONE_SCM_CLIENT_ADDRESS_KEY));
+ }
+
+ static List<Arguments> unambiguousAuthorities() {
+ return Arrays.asList(
+ Arguments.of("[2001:db8::1]:9862", OptionalInt.of(9862)),
+ Arguments.of("[2001:db8::1:9862]:9862", OptionalInt.of(9862)),
+ Arguments.of("[2001:db8::1]", OptionalInt.empty()),
+ Arguments.of("192.0.2.1:9862", OptionalInt.of(9862)),
+ Arguments.of("scm1.example.com:9862", OptionalInt.of(9862)),
+ Arguments.of("scm1.example.com", OptionalInt.empty())
+ );
+ }
+
+ /**
+ * A bind host names no port, so the bracket rule must leave it alone: an
+ * explicit IPv6 listener is configured as a bare {@code ::}.
+ */
+ @Test
+ void getHostNameFromConfigKeysAcceptsBareIPv6BindHost() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_BIND_HOST_KEY, "::");
+
+ assertEquals(Optional.of("::"),
+ HddsUtils.getHostNameFromConfigKeys(conf,
OZONE_SCM_CLIENT_BIND_HOST_KEY));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"scm1.example.com", "192.0.2.1", "2001:db8::1",
"localhost", "127.0.0.1", "::1"})
+ void validateAdvertisedHostAcceptsReachableHost(String host) {
+ assertDoesNotThrow(() ->
validateAdvertisedHost(OZONE_SCM_CLIENT_ADDRESS_KEY, host));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"0.0.0.0", "::", "169.254.1.1", "fe80::1",
"fe80::1%eth0", "2001:db8::1/64",
+ "[::]", "[fe80::1]", "[fe80::1%eth0]"})
+ void validateAdvertisedHostRejectsUnadvertisableHost(String host) {
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> validateAdvertisedHost(OZONE_SCM_CLIENT_ADDRESS_KEY, host));
+
+ assertThat(e.getMessage())
+ .contains(OZONE_SCM_CLIENT_ADDRESS_KEY)
+ .contains(host);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"0.0.0.0:9860", "[::]:9860", "[fe80::1%eth0]:9860"})
+ void validateAdvertisedAddressRejectsUnadvertisableHostWithPort(String
value) {
+ assertThrows(ConfigurationException.class,
+ () -> validateAdvertisedAddress(OZONE_SCM_CLIENT_ADDRESS_KEY, value));
+ }
+
+ @Test
+ void getHostNameRejectsUnadvertisableDatanodeHostname() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(HDDS_DATANODE_HOST_NAME_KEY, "0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsUtils.getHostName(conf));
+
+ assertThat(e.getMessage()).contains(HDDS_DATANODE_HOST_NAME_KEY);
+ }
+
+ @Test
+ void getHostNameAcceptsConfiguredDatanodeHostname() throws Exception {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(HDDS_DATANODE_HOST_NAME_KEY, "dn1.example.com");
+
+ assertEquals("dn1.example.com", HddsUtils.getHostName(conf));
+ }
+
+ /**
+ * SCM rewrites its client address with the bound host, so a process that
+ * shares its configuration reads the wildcard back.
+ */
+ @Test
+ void getScmAddressForClientsAcceptsWildcardListenAddress() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "0.0.0.0:9860");
+
+ InetSocketAddress addr =
HddsUtils.getScmAddressForClients(conf).iterator().next();
+
+ assertEquals("0.0.0.0", addr.getHostString());
+ assertEquals(9860, addr.getPort());
+ }
+
+ @Test
+ void getScmAddressForClientsRejectsUnadvertisableScmName() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_NAMES, "0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsUtils.getScmAddressForClients(conf));
+
+ assertThat(e.getMessage()).contains(OZONE_SCM_NAMES);
+ }
+
+ /**
+ * A host that can never be advertised has to be named as such, rather than
+ * be asked for brackets that leave it rejected anyway.
+ */
+ @Test
+ void validateAdvertisedAddressReportsTheHostBeforeTheBrackets() {
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> validateAdvertisedAddress(OZONE_SCM_CLIENT_ADDRESS_KEY, "::"));
+
+ assertThat(e.getMessage()).contains("wildcard").doesNotContain("bracket");
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"scm1.example.com", "192.0.2.1",
"[2001:db8::1]:9860", "127.0.0.1:9860"})
+ void validateAdvertisedAddressAcceptsReachableAddress(String value) {
+ assertDoesNotThrow(() ->
validateAdvertisedAddress(OZONE_SCM_CLIENT_ADDRESS_KEY, value));
+ }
+
+ @Test
+ void validateAdvertisedAddressConfigSkipsUnsetProperty() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+
+ assertDoesNotThrow(() -> validateAdvertisedAddressConfig(conf,
OZONE_SCM_CLIENT_ADDRESS_KEY));
+ }
+
+ @Test
+ void validateAdvertisedAddressConfigChecksEveryListEntry() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_NAMES, "scm1.example.com,0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> validateAdvertisedAddressConfig(conf,
OZONE_SCM_CLIENT_ADDRESS_KEY, OZONE_SCM_NAMES));
+
+ assertThat(e.getMessage()).contains(OZONE_SCM_NAMES).contains("0.0.0.0");
+ }
}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
index 8706f0f7f03..60111c2f333 100644
---
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMNodeInfo.java
@@ -27,9 +27,11 @@
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_DEFAULT;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_NAMES;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_DEFAULT;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_KEY;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -162,4 +164,80 @@ public void testNonHAWithRestDefaults() {
assertEquals("localhost:" + OZONE_SCM_DATANODE_PORT_DEFAULT,
scmNodeInfos.get(0).getScmDatanodeAddress());
}
+
+ @Test
+ public void testSCMHANodeInfoRejectsWildcardSCMAddress() {
+ for (String nodeId : nodes) {
+ conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ scmServiceId, nodeId), "localhost");
+ }
+ String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ scmServiceId, "scm1");
+ conf.set(addressKey, "0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> SCMNodeInfo.buildNodeInfo(conf));
+
+ assertThat(e.getMessage()).contains(addressKey).contains("0.0.0.0");
+ }
+
+ /**
+ * The SCM address property names a host and takes its ports from separate
+ * properties, so a bare IPv6 literal is unambiguous there.
+ */
+ @Test
+ public void testSCMHANodeInfoAcceptsBareIPv6SCMAddress() {
+ for (String nodeId : nodes) {
+ conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ scmServiceId, nodeId), "2001:db8::1");
+ }
+
+ List<SCMNodeInfo> scmNodeInfos = SCMNodeInfo.buildNodeInfo(conf);
+
+ assertEquals("[2001:db8::1]:" + OZONE_SCM_CLIENT_PORT_DEFAULT,
+ scmNodeInfos.get(0).getScmClientAddress());
+ }
+
+ @Test
+ public void testSCMHANodeInfoRejectsBracketedWildcardSCMAddress() {
+ for (String nodeId : nodes) {
+ conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ scmServiceId, nodeId), "localhost");
+ }
+ String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ scmServiceId, "scm1");
+ conf.set(addressKey, "[::]");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> SCMNodeInfo.buildNodeInfo(conf));
+
+ assertThat(e.getMessage()).contains(addressKey).contains("[::]");
+ }
+
+ /**
+ * A non-HA SCM rewrites its address properties with the bound host, so a
+ * process that shares its configuration reads the wildcard back.
+ */
+ @Test
+ public void testNonHAAcceptsWildcardListenAddress() {
+ OzoneConfiguration config = new OzoneConfiguration();
+ config.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "0.0.0.0:9860");
+ config.set(OZONE_SCM_DATANODE_ADDRESS_KEY, "0.0.0.0:9861");
+
+ List<SCMNodeInfo> scmNodeInfos = SCMNodeInfo.buildNodeInfo(config);
+
+ assertEquals("0.0.0.0:9860", scmNodeInfos.get(0).getScmClientAddress());
+ assertEquals("0.0.0.0:9861", scmNodeInfos.get(0).getScmDatanodeAddress());
+ }
+
+ @Test
+ public void testNonHARejectsWildcardScmNames() {
+ OzoneConfiguration config = new OzoneConfiguration();
+ config.set(OZONE_SCM_NAMES, "0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> SCMNodeInfo.buildNodeInfo(config));
+
+ assertThat(e.getMessage()).contains(OZONE_SCM_NAMES).contains("0.0.0.0");
+ }
}
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
index d0c0a60b03e..c8801ecc5c5 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/datanode/InitDatanodeState.java
@@ -29,6 +29,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.scm.net.HostAndPort;
@@ -76,11 +77,13 @@ public InitDatanodeState(ConfigurationSource conf,
@Override
public DatanodeStateMachine.DatanodeStates call() throws Exception {
final Collection<HostAndPort> addresses;
+ final HostAndPort reconAddress;
try {
addresses = HddsServerUtil.getSCMAddressForDatanodes(conf);
- } catch (IllegalArgumentException e) {
+ reconAddress = getReconAddressForDatanodes(conf);
+ } catch (IllegalArgumentException | ConfigurationException e) {
if (!Strings.isNullOrEmpty(e.getMessage())) {
- LOG.error("Failed to get SCM addresses: {}", e.getMessage());
+ LOG.error("Failed to get SCM or Recon addresses: {}", e.getMessage());
}
return DatanodeStateMachine.DatanodeStates.SHUTDOWN;
}
@@ -103,7 +106,6 @@ public DatanodeStateMachine.DatanodeStates call() throws
Exception {
connectionManager.addSCMServer(addr, context.getThreadNamePrefix());
this.context.addEndpoint(addr);
}
- final HostAndPort reconAddress = getReconAddressForDatanodes(conf);
if (reconAddress != null) {
connectionManager.addReconServer(reconAddress,
context.getThreadNamePrefix());
diff --git
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
index e7ffc64c5a6..44229eed1bf 100644
---
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
+++
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestDatanodeStateMachine.java
@@ -50,6 +50,7 @@
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.ReconfigurationHandler;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.recon.ReconConfigKeys;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
import org.apache.hadoop.ipc_.RPC;
@@ -511,6 +512,17 @@ public void
testDatanodeStateMachineWithInvalidConfiguration()
/** Port out of range **/
confList.add(Maps.immutableEntry(
ScmConfigKeys.OZONE_SCM_NAMES, "scm:123456"));
+ /** Unbracketed IPv6 literal **/
+ confList.add(Maps.immutableEntry(
+ ScmConfigKeys.OZONE_SCM_NAMES, "::1"));
+ /** Wildcard **/
+ confList.add(Maps.immutableEntry(
+ ScmConfigKeys.OZONE_SCM_NAMES, "0.0.0.0"));
+
+ // Invalid ozone.recon.address
+ /** Wildcard **/
+ confList.add(Maps.immutableEntry(
+ ReconConfigKeys.OZONE_RECON_ADDRESS_KEY, "0.0.0.0:9891"));
confList.forEach((entry) -> {
OzoneConfiguration perTestConf = new OzoneConfiguration(conf);
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
index 7ffe7c7a643..1d51c8b7365 100644
---
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
@@ -31,6 +31,8 @@
import static org.apache.hadoop.hdds.HddsUtils.getHostPortString;
import static org.apache.hadoop.hdds.HddsUtils.getPortNumberFromConfigKeys;
import static org.apache.hadoop.hdds.HddsUtils.getScmServiceId;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedAddress;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedHost;
import static
org.apache.hadoop.hdds.recon.ReconConfigKeys.OZONE_RECON_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.recon.ReconConfigKeys.OZONE_RECON_DATANODE_PORT_DEFAULT;
import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_KEY;
@@ -893,6 +895,7 @@ public static Collection<HostAndPort>
getSCMAddressForDatanodes(ConfigurationSou
final Collection<HostAndPort> addresses = new HashSet<>(names.size());
for (String address : names) {
+ validateAdvertisedAddress(OZONE_SCM_NAMES, address);
Optional<String> hostname = getHostName(address);
if (!hostname.isPresent()) {
throw new IllegalArgumentException("Invalid hostname for SCM: "
@@ -931,6 +934,7 @@ public static List<ScmNodeAddress>
getSCMAddressForDatanodes(
LOG.warn("The SCM address configuration {} is not defined, return
nothing", addressKey);
return null;
}
+ validateAdvertisedHost(addressKey, scmAddress);
int scmDatanodePort = SCMNodeInfo.getPort(conf, scmServiceId, scmNodeId,
OZONE_SCM_DATANODE_ADDRESS_KEY, OZONE_SCM_DATANODE_PORT_KEY,
@@ -953,6 +957,7 @@ public static HostAndPort getReconAddressForDatanodes(
if (StringUtils.isEmpty(name)) {
return null;
}
+ validateAdvertisedAddress(OZONE_RECON_ADDRESS_KEY, name);
Optional<String> hostname = getHostName(name);
if (!hostname.isPresent()) {
throw new IllegalArgumentException("Invalid hostname for Recon: "
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
index 60a4ca7325f..50aea32c8b8 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
@@ -215,6 +215,7 @@ public static SCMHANodeDetails
loadSCMHAConfig(OzoneConfiguration conf,
"%s. SCM RPC Address should be set for all nodes in a SCM " +
"service.", rpcAddrKey);
}
+ HddsUtils.validateAdvertisedHost(rpcAddrKey, rpcAddrStr);
isSCMddressSet = true;
String ratisPortKey =
ConfUtils.addKeySuffixes(OZONE_SCM_RATIS_PORT_KEY,
@@ -266,7 +267,8 @@ public static SCMHANodeDetails
loadSCMHAConfig(OzoneConfiguration conf,
LOG.info("Found matching SCM address with SCMServiceId: {}, " +
"SCMNodeId: {}, RPC Address: {} and Ratis port: {}",
localScmServiceId, localScmNodeId,
- NetUtils.getHostPortString(localRpcAddress), localRatisPort);
+ HddsUtils.getHostPortString(localRpcAddress.getHostName(),
localRpcAddress.getPort()),
+ localRatisPort);
// Set SCM node specific config keys.
ConfUtils.setNodeSpecificConfigs(nodeSpecificConfigKeys, conf,
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
index 39fa7e97d03..d819b19432b 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMNodeDetails.java
@@ -19,7 +19,6 @@
import java.net.InetSocketAddress;
import org.apache.hadoop.hdds.NodeDetails;
-import org.apache.hadoop.net.NetUtils;
/**
* Construct SCM node details.
@@ -147,11 +146,6 @@ public SCMNodeDetails build() {
}
}
- @Override
- public String getRpcAddressString() {
- return NetUtils.getHostPortString(getRpcAddress());
- }
-
public InetSocketAddress getClientProtocolServerAddress() {
return clientProtocolServerAddress;
}
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
index b19b59320c7..e452ef51dbb 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtils.java
@@ -17,6 +17,8 @@
package org.apache.hadoop.hdds.scm;
+import static
org.apache.hadoop.hdds.recon.ReconConfigKeys.OZONE_RECON_ADDRESS_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_ADDRESS_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_ID_DIR;
@@ -33,14 +35,17 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
+import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.scm.ha.SCMNodeInfo;
import org.apache.hadoop.hdds.server.ServerUtils;
import org.apache.hadoop.hdds.utils.HddsServerUtil;
import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.ozone.ha.ConfUtils;
import org.apache.hadoop.test.PathUtils;
import org.junit.jupiter.api.Test;
@@ -265,4 +270,49 @@ public void testGetDatanodeIdFilePath() {
FileUtils.deleteQuietly(metaDir);
}
}
+
+ @Test
+ public void testGetSCMAddressForDatanodesRejectsWildcardSCMName() {
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_NAMES, "0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsServerUtil.getSCMAddressForDatanodes(conf));
+
+ assertThat(e.getMessage()).contains(OZONE_SCM_NAMES).contains("0.0.0.0");
+ }
+
+ @Test
+ public void testGetSCMAddressForDatanodesRejectsWildcardHASCMAddress() {
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ final String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ "scmservice", "scm1");
+ conf.set(addressKey, "::");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsServerUtil.getSCMAddressForDatanodes(conf, "scmservice",
+ Collections.singleton("scm1")));
+
+ assertThat(e.getMessage()).contains(addressKey).contains("::");
+ }
+
+ @Test
+ public void testGetReconAddressForDatanodesRejectsWildcardAddress() {
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_RECON_ADDRESS_KEY, "0.0.0.0:9891");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> HddsServerUtil.getReconAddressForDatanodes(conf));
+
+ assertThat(e.getMessage()).contains(OZONE_RECON_ADDRESS_KEY);
+ }
+
+ @Test
+ public void testGetReconAddressForDatanodesAcceptsHostname() {
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(OZONE_RECON_ADDRESS_KEY, "recon.example.com:9891");
+
+ assertEquals("recon.example.com:9891",
+
HddsServerUtil.getReconAddressForDatanodes(conf).getHostAndPortString());
+ }
}
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
index 15715d72b61..17c743beeab 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMConfiguration.java
@@ -36,13 +36,16 @@
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_BIND_HOST_KEY;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_KEY;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_METADATA_DIRS;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.net.InetSocketAddress;
import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
import org.apache.hadoop.hdds.scm.ScmRatisServerConfig;
@@ -57,6 +60,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
/**
* Test for SCM HA-related configuration.
@@ -211,6 +216,30 @@ public void testSCMConfig() throws Exception {
RaftServerConfigKeys.Log.Appender.WAIT_TIME_MIN_KEY);
}
+ @ParameterizedTest
+ @CsvSource({"scm1, 0.0.0.0", "scm2, 169.254.1.1"})
+ void testSCMConfigRejectsUnadvertisableAddress(String nodeId, String
address) {
+ String scmServiceId = "scmserviceId";
+ conf.set(ScmConfigKeys.OZONE_SCM_SERVICE_IDS_KEY, scmServiceId);
+ conf.set(ScmConfigKeys.OZONE_SCM_NODES_KEY + "." + scmServiceId,
+ "scm1,scm2,scm3");
+ conf.set(ScmConfigKeys.OZONE_SCM_NODE_ID_KEY, "scm1");
+ for (String node : new String[] {"scm1", "scm2", "scm3"}) {
+ conf.set(ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY, scmServiceId,
+ node), "localhost");
+ }
+ String addressKey = ConfUtils.addKeySuffixes(OZONE_SCM_ADDRESS_KEY,
+ scmServiceId, nodeId);
+ conf.set(addressKey, address);
+ SCMStorageConfig scmStorageConfig = mock(SCMStorageConfig.class);
+
when(scmStorageConfig.getState()).thenReturn(Storage.StorageState.NOT_INITIALIZED);
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> SCMHANodeDetails.loadSCMHAConfig(conf, scmStorageConfig));
+
+ assertThat(e.getMessage()).contains(addressKey).contains(address);
+ }
+
@Test
public void testSamePortConfig() throws Exception {
String scmServiceId = "scmserviceId";
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 0e457553e79..25453776b7f 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -21,6 +21,7 @@
import static org.apache.hadoop.hdds.HddsUtils.getHostNameFromConfigKeys;
import static org.apache.hadoop.hdds.HddsUtils.getHostPortString;
import static org.apache.hadoop.hdds.HddsUtils.getPortNumberFromConfigKeys;
+import static org.apache.hadoop.hdds.HddsUtils.validateAdvertisedHost;
import static org.apache.hadoop.ozone.OzoneConsts.DOUBLE_SLASH_OM_KEY_PREFIX;
import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_INDICATOR;
@@ -161,12 +162,19 @@ public static String getOmRpcAddress(ConfigurationSource
conf) {
* @param conf configuration
* @param confKey configuration key to lookup address from
* @return Target InetSocketAddress for the OM RPC server.
+ * @throws ConfigurationException if the configured host cannot be advertised
+ * to the other OMs of the service
*/
public static String getOmRpcAddress(ConfigurationSource conf,
String confKey) {
final Optional<String> host = getHostNameFromConfigKeys(conf, confKey);
if (host.isPresent()) {
+ // Without a service and node suffix this is the non-HA OM's own RPC
+ // address, which defaults to a wildcard.
+ if (!OZONE_OM_ADDRESS_KEY.equals(confKey)) {
+ validateAdvertisedHost(confKey, host.get());
+ }
return getHostPortString(host.get(),
getPortNumberFromConfigKeys(conf, confKey)
.orElse(OZONE_OM_PORT_DEFAULT));
diff --git
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
index 8abfba48a13..889d2d3b950 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
@@ -24,6 +24,7 @@
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_INTERNAL_SERVICE_ID;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_NODES_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_PORT_DEFAULT;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SERVICE_IDS_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -43,6 +44,7 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
+import org.apache.hadoop.hdds.conf.ConfigurationException;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.ozone.ha.ConfUtils;
import org.apache.hadoop.ozone.om.OMConfigKeys;
@@ -455,7 +457,7 @@ void testOmAddressesBracketIPv6Literals() {
assertEquals("[2001:db8::1]:9999", OmUtils.getOmRpcAddress(conf));
String peerAddressKey = OZONE_OM_ADDRESS_KEY + ".omservice.om1";
- conf.set(peerAddressKey, "2001:db8::2");
+ conf.set(peerAddressKey, "[2001:db8::2]");
assertEquals("[2001:db8::2]:" + OMConfigKeys.OZONE_OM_PORT_DEFAULT,
OmUtils.getOmRpcAddress(conf, peerAddressKey));
@@ -483,4 +485,38 @@ public void testResolveOmHostAcceptsIpv6Literal() {
}
}
}
+
+ @Test
+ void getOmRpcAddressRejectsWildcardPeerAddress() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ String rpcAddrKey = ConfUtils.addKeySuffixes(OZONE_OM_ADDRESS_KEY,
+ "omservice", "om1");
+ conf.set(rpcAddrKey, "0.0.0.0");
+
+ ConfigurationException e = assertThrows(ConfigurationException.class,
+ () -> OmUtils.getOmRpcAddress(conf, rpcAddrKey));
+
+ assertThat(e.getMessage()).contains(rpcAddrKey).contains("0.0.0.0");
+ }
+
+ @Test
+ void getOmRpcAddressAcceptsPeerHostname() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ String rpcAddrKey = ConfUtils.addKeySuffixes(OZONE_OM_ADDRESS_KEY,
+ "omservice", "om1");
+ conf.set(rpcAddrKey, "om1.example.com");
+
+ assertEquals("om1.example.com:" + OZONE_OM_PORT_DEFAULT,
+ OmUtils.getOmRpcAddress(conf, rpcAddrKey));
+ }
+
+ /**
+ * The non-HA client failover proxy reads the unsuffixed property, which is
+ * the non-HA OM's own RPC address and ships as a wildcard.
+ */
+ @Test
+ void getOmRpcAddressKeepsWildcardDefault() {
+ assertEquals("0.0.0.0:" + OZONE_OM_PORT_DEFAULT,
+ OmUtils.getOmRpcAddress(new OzoneConfiguration(),
OZONE_OM_ADDRESS_KEY));
+ }
}
diff --git
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
index 06c95559b28..ec42e895c07 100644
---
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
+++
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestHadoopRpcOMFollowerReadFailoverProxyProvider.java
@@ -512,7 +512,7 @@ private void setupProxyProvider(int omNodeCount,
OzoneConfiguration config) thro
for (int i = 0; i < omNodeCount; i++) {
String nodeId = NODE_ID_BASE_STR + (i + 1); // 1-th indexed
config.set(ConfUtils.addKeySuffixes(OZONE_OM_ADDRESS_KEY, OM_SERVICE_ID,
- nodeId), "0.0.0.0:" + i);
+ nodeId), "localhost:" + i);
allNodeIds.add(nodeId);
omNodeIds[i] = nodeId;
omNodeAnswers[i] = new OMAnswer();
diff --git
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
index 4a91b44ee34..08085743270 100644
---
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
+++
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/ha/TestOMFailoverProxyProvider.java
@@ -49,7 +49,7 @@
public class TestOMFailoverProxyProvider {
private static final String OM_SERVICE_ID = "om-service-test1";
private static final String NODE_ID_BASE_STR = "omNode-";
- private static final String DUMMY_NODE_ADDR = "0.0.0.0:8080";
+ private static final String DUMMY_NODE_ADDR = "localhost:8080";
private HadoopRpcOMFailoverProxyProvider<OzoneManagerProtocolPB> provider;
private long waitBetweenRetries;
private int numNodes = 3;
diff --git
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
index 4b3eaef0018..46125e761ca 100644
---
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
+++
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
@@ -450,4 +450,23 @@ public void testSetRatisAddress() {
assertEquals("192.168.1.100", nodeDetails.getHostAddress());
assertEquals(9873, nodeDetails.getRatisPort());
}
+
+ /**
+ * An advertised peer identity has to bracket an IPv6 literal, or a reader
+ * takes the last group of the address for the port.
+ */
+ @Test
+ public void testRpcAddressStringBracketsIPv6Literal() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setRpcAddress(InetSocketAddress.createUnresolved("2001:db8::1",
RPC_PORT))
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(false)
+ .build();
+
+ assertEquals("[2001:db8::1]:" + RPC_PORT,
nodeDetails.getRpcAddressString());
+ }
}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
index d6b9d51e921..17ffa2fef40 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerConfiguration.java
@@ -112,7 +112,7 @@ public void testDefaultPortIfNotSpecified() throws
Exception {
String omNode1RpcAddrKey = getOMAddrKeyWithSuffix(serviceID, omNode1Id);
String omNode2RpcAddrKey = getOMAddrKeyWithSuffix(serviceID, omNode2Id);
- conf.set(omNode1RpcAddrKey, "0.0.0.0");
+ conf.set(omNode1RpcAddrKey, "localhost");
conf.set(omNode2RpcAddrKey, "122.0.0.122");
// Set omNode1 as the current node. omNode1 address does not have a port
@@ -121,7 +121,7 @@ public void testDefaultPortIfNotSpecified() throws
Exception {
startCluster();
OzoneManager om = cluster.getOzoneManager();
- assertEquals("0.0.0.0",
+ assertEquals("localhost",
om.getOmRpcServerAddr().getHostName());
assertEquals(OMConfigKeys.OZONE_OM_PORT_DEFAULT,
om.getOmRpcServerAddr().getPort());
@@ -188,7 +188,7 @@ public void testThreeNodeOMservice() throws Exception {
// Set node2 to localhost and the other two nodes to dummy addresses
conf.set(omNode1RpcAddrKey, "123.0.0.123:9862");
- conf.set(omNode2RpcAddrKey, "0.0.0.0:9862");
+ conf.set(omNode2RpcAddrKey, "localhost:9862");
conf.set(omNode3RpcAddrKey, "124.0.0.124:9862");
conf.setInt(omNode3RatisPortKey, 9898);
@@ -217,7 +217,7 @@ public void testThreeNodeOMservice() throws Exception {
OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
break;
case omNode2Id :
- expectedPeerAddress = "0.0.0.0:" +
+ expectedPeerAddress = "localhost:" +
OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
break;
case omNode3Id :
@@ -263,7 +263,7 @@ public void testOMHAWithUnresolvedAddresses() throws
Exception {
// Set node2 to localhost and the other two nodes to dummy addresses
conf.set(omNode1RpcAddrKey, node1Hostname + ":9862");
- conf.set(omNode2RpcAddrKey, "0.0.0.0:9862");
+ conf.set(omNode2RpcAddrKey, "localhost:9862");
conf.set(omNode3RpcAddrKey, node3Hostname + ":9804");
conf.setInt(omNode3RatisPortKey, 9898);
@@ -300,7 +300,7 @@ public void testOMHAWithUnresolvedAddresses() throws
Exception {
OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
break;
case omNode2Id :
- expectedPeerAddress = "0.0.0.0:" +
+ expectedPeerAddress = "localhost:" +
OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
break;
case omNode3Id :
@@ -425,7 +425,7 @@ public void testMultipleOMServiceIds() throws Exception {
conf.set(getOMAddrKeyWithSuffix(om2ServiceId, omNode1Id),
"125.0.0.126:9862");
conf.set(getOMAddrKeyWithSuffix(om2ServiceId, omNode2Id),
- "0.0.0.0:9862");
+ "localhost:9862");
conf.set(getOMAddrKeyWithSuffix(om2ServiceId, omNode3Id),
"126.0.0.127:9862");
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
index da0ac79be26..7c68a51cc57 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ha/OMHANodeDetails.java
@@ -35,6 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import org.apache.hadoop.hdds.HddsUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.ozone.OmUtils;
@@ -203,7 +204,8 @@ public static OMHANodeDetails
loadOMHAConfig(OzoneConfiguration conf) {
LOG.info("Found matching OM address with OMServiceId: {}, " +
"OMNodeId: {}, RPC Address: {} ,Ratis port: {} and isListener:
{}",
localOMServiceId, localOMNodeId,
- NetUtils.getHostPortString(localRpcAddress), localRatisPort,
localIsListener);
+ HddsUtils.getHostPortString(localRpcAddress.getHostName(),
localRpcAddress.getPort()),
+ localRatisPort, localIsListener);
ConfUtils.setNodeSpecificConfigs(genericConfigKeys, conf,
localOMServiceId, localOMNodeId, LOG);
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
index b8b413d2c20..28b0e60609a 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
@@ -33,6 +33,7 @@
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.hadoop.hdds.HddsUtils;
import org.apache.hadoop.hdds.cli.GenericCli;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import
org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB;
@@ -114,6 +115,7 @@ public Void call() throws Exception {
HddsServerUtil.startupShutdownMessage(OzoneVersionInfo.OZONE_VERSION_INFO,
ReconServer.class, originalArgs, LOG, configuration);
ConfigurationProvider.setConfiguration(configuration);
+ HddsUtils.validateAdvertisedAddressConfig(configuration,
ReconConfigKeys.OZONE_RECON_ADDRESS_KEY);
try {
reconAdmins = createReconAdmins(configuration);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]