You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by yj...@apache.org on 2019/02/23 17:41:27 UTC
[hadoop] branch trunk updated: HDFS-14118. Support using DNS to
resolve nameservices to IP addresses. Contributed by Fengnan Li.
This is an automated email from the ASF dual-hosted git repository.
yjzhangal pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git
The following commit(s) were added to refs/heads/trunk by this push:
new f7a27cd HDFS-14118. Support using DNS to resolve nameservices to IP addresses. Contributed by Fengnan Li.
f7a27cd is described below
commit f7a27cdee4e6829ebea4ea965e549a27acbf4310
Author: Yongjun Zhang <yz...@cloudera.com>
AuthorDate: Fri Feb 22 21:54:12 2019 -0800
HDFS-14118. Support using DNS to resolve nameservices to IP addresses. Contributed by Fengnan Li.
---
.../apache/hadoop/fs/CommonConfigurationKeys.java | 8 ++
.../apache/hadoop/net/DNSDomainNameResolver.java | 34 ++++++
.../org/apache/hadoop/net/DomainNameResolver.java | 39 ++++++
.../hadoop/net/DomainNameResolverFactory.java | 74 +++++++++++
.../java/org/apache/hadoop/net/package-info.java | 23 ++++
.../main/java/org/apache/hadoop/net/package.html | 23 ----
.../src/main/resources/core-default.xml | 10 ++
.../apache/hadoop/net/MockDomainNameResolver.java | 68 +++++++++++
.../hadoop/net/TestMockDomainNameResolver.java | 71 +++++++++++
.../hadoop/hdfs/client/HdfsClientConfigKeys.java | 3 +
.../ha/AbstractNNFailoverProxyProvider.java | 53 ++++++++
.../ha/TestConfiguredFailoverProxyProvider.java | 135 ++++++++++++++++++++-
.../src/main/resources/hdfs-default.xml | 31 ++++-
13 files changed, 547 insertions(+), 25 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java
index 72e5309..384e5d1e 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java
@@ -21,6 +21,8 @@ package org.apache.hadoop.fs;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.http.lib.StaticUserWebFilter;
+import org.apache.hadoop.net.DomainNameResolver;
+import org.apache.hadoop.net.DNSDomainNameResolver;
/**
* This class contains constants for configuration keys used
@@ -393,4 +395,10 @@ public class CommonConfigurationKeys extends CommonConfigurationKeysPublic {
public static final String ZK_RETRY_INTERVAL_MS =
ZK_PREFIX + "retry-interval-ms";
public static final int ZK_RETRY_INTERVAL_MS_DEFAULT = 1000;
+ /** Default domain name resolver for hadoop to use. */
+ public static final String HADOOP_DOMAINNAME_RESOLVER_IMPL =
+ "hadoop.domainname.resolver.impl";
+ public static final Class<? extends DomainNameResolver>
+ HADOOP_DOMAINNAME_RESOLVER_IMPL_DEFAULT =
+ DNSDomainNameResolver.class;
}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DNSDomainNameResolver.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DNSDomainNameResolver.java
new file mode 100644
index 0000000..bb1aa90
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DNSDomainNameResolver.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.net;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * DNSDomainNameResolver takes one domain name and returns all of the IP
+ * addresses from the underlying DNS service.
+ */
+public class DNSDomainNameResolver implements DomainNameResolver {
+ @Override
+ public InetAddress[] getAllByDomainName(String domainName)
+ throws UnknownHostException {
+ return InetAddress.getAllByName(domainName);
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DomainNameResolver.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DomainNameResolver.java
new file mode 100644
index 0000000..6d2d800
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DomainNameResolver.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.net;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * This interface provides methods for the failover proxy to get IP addresses
+ * of the associated servers (NameNodes, RBF routers etc). Implementations will
+ * use their own service discovery mechanism, DNS, Zookeeper etc
+ */
+public interface DomainNameResolver {
+ /**
+ * Takes one domain name and returns its IP addresses based on the actual
+ * service discovery methods.
+ *
+ * @param domainName
+ * @return all IP addresses
+ * @throws UnknownHostException
+ */
+ InetAddress[] getAllByDomainName(String domainName)
+ throws UnknownHostException;
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DomainNameResolverFactory.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DomainNameResolverFactory.java
new file mode 100644
index 0000000..a0b0380
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/DomainNameResolverFactory.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.net;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.util.ReflectionUtils;
+
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * This class creates the DomainNameResolver instance based on the config.
+ * It can either create the default resolver for the whole resolving for
+ * hadoop or create individual resolver per nameservice or yarn.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public final class DomainNameResolverFactory {
+
+ private DomainNameResolverFactory() {
+ // Utility classes should not have a public or default constructor
+ }
+
+ /**
+ * Create a domain name resolver to convert the domain name in the config to
+ * the actual IP addresses of the Namenode/Router/RM.
+ *
+ * @param conf Configuration to get the resolver from.
+ * @param uri the url that the resolver will be used against
+ * @param configKey The config key name suffixed with
+ * the nameservice/yarnservice.
+ * @return Domain name resolver.
+ */
+ public static DomainNameResolver newInstance(
+ Configuration conf, URI uri, String configKey) throws IOException {
+ String host = uri.getHost();
+ String confKeyWithHost = configKey + "." + host;
+ return newInstance(conf, confKeyWithHost);
+ }
+
+ /**
+ * This function gets the instance based on the config.
+ *
+ * @param conf Configuration
+ * @param configKey config key name.
+ * @return Domain name resolver.
+ * @throws IOException when the class cannot be found or initiated.
+ */
+ public static DomainNameResolver newInstance(
+ Configuration conf, String configKey) {
+ Class<? extends DomainNameResolver> resolverClass = conf.getClass(
+ configKey,
+ DNSDomainNameResolver.class,
+ DomainNameResolver.class);
+ return ReflectionUtils.newInstance(resolverClass, conf);
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/package-info.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/package-info.java
new file mode 100644
index 0000000..2536429
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Network-related classes.
+ */
+@InterfaceAudience.Public
+package org.apache.hadoop.net;
+import org.apache.hadoop.classification.InterfaceAudience;
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/package.html b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/package.html
deleted file mode 100644
index b4e5b5d..0000000
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/net/package.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<html>
-
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<body>
-Network-related classes.
-</body>
-</html>
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index d05e1bb..868f6e2 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -3360,4 +3360,14 @@
address. (i.e 0.0.0.0)
</description>
</property>
+
+ <property>
+ <name>hadoop.domainname.resolver.impl</name>
+ <value>org.apache.hadoop.net.DNSDomainNameResolver</value>
+ <description>The implementation of DomainNameResolver used for service (NameNodes,
+ RBF Routers etc) discovery. The default implementation
+ org.apache.hadoop.net.DNSDomainNameResolver returns all IP addresses associated
+ with the input domain name of the services by querying the underlying DNS.
+ </description>
+ </property>
</configuration>
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/MockDomainNameResolver.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/MockDomainNameResolver.java
new file mode 100644
index 0000000..cb55ae0
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/MockDomainNameResolver.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.net;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * This mock resolver class returns the predefined resolving results.
+ * By default it uses a default "test.foo.bar" domain with two IP addresses.
+ */
+public class MockDomainNameResolver implements DomainNameResolver {
+
+ public static final String DOMAIN = "test.foo.bar";
+ // This host will be used to mock non-resolvable host
+ public static final String UNKNOW_DOMAIN = "unknown.foo.bar";
+ public static final byte[] BYTE_ADDR_1 = new byte[]{10, 1, 1, 1};
+ public static final byte[] BYTE_ADDR_2 = new byte[]{10, 1, 1, 2};
+ public static final String ADDR_1 = "10.1.1.1";
+ public static final String ADDR_2 = "10.1.1.2";
+
+ /** Internal mapping of domain names and IP addresses. */
+ private Map<String, InetAddress[]> addrs = new TreeMap<>();
+
+
+ public MockDomainNameResolver() {
+ try {
+ InetAddress nn1Address = InetAddress.getByAddress(BYTE_ADDR_1);
+ InetAddress nn2Address = InetAddress.getByAddress(BYTE_ADDR_2);
+ addrs.put(DOMAIN, new InetAddress[]{nn1Address, nn2Address});
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public InetAddress[] getAllByDomainName(String domainName)
+ throws UnknownHostException {
+ if (!addrs.containsKey(domainName)) {
+ throw new UnknownHostException(domainName + " is not resolvable");
+ }
+ return addrs.get(domainName);
+ }
+
+ @VisibleForTesting
+ public void setAddressMap(Map<String, InetAddress[]> addresses) {
+ this.addrs = addresses;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/TestMockDomainNameResolver.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/TestMockDomainNameResolver.java
new file mode 100644
index 0000000..5d8f014
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/net/TestMockDomainNameResolver.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.net;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * This class mainly test the MockDomainNameResolver comes working as expected.
+ */
+public class TestMockDomainNameResolver {
+
+ private Configuration conf;
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Before
+ public void setup() {
+ conf = new Configuration();
+ conf.set(CommonConfigurationKeys.HADOOP_DOMAINNAME_RESOLVER_IMPL,
+ MockDomainNameResolver.class.getName());
+ }
+
+ @Test
+ public void testMockDomainNameResolverCanBeCreated() throws IOException {
+ DomainNameResolver resolver = DomainNameResolverFactory.newInstance(
+ conf, CommonConfigurationKeys.HADOOP_DOMAINNAME_RESOLVER_IMPL);
+ InetAddress[] addrs = resolver.getAllByDomainName(
+ MockDomainNameResolver.DOMAIN);
+
+ assertEquals(2, addrs.length);
+ assertEquals(MockDomainNameResolver.ADDR_1, addrs[0].getHostAddress());
+ assertEquals(MockDomainNameResolver.ADDR_2, addrs[1].getHostAddress());
+ }
+
+ @Test
+ public void testMockDomainNameResolverCanNotBeCreated()
+ throws UnknownHostException {
+ DomainNameResolver resolver = DomainNameResolverFactory.newInstance(
+ conf, CommonConfigurationKeys.HADOOP_DOMAINNAME_RESOLVER_IMPL);
+ exception.expect(UnknownHostException.class);
+ resolver.getAllByDomainName(
+ MockDomainNameResolver.UNKNOW_DOMAIN);
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/client/HdfsClientConfigKeys.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/client/HdfsClientConfigKeys.java
index 9d20933..67904ee 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/client/HdfsClientConfigKeys.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/client/HdfsClientConfigKeys.java
@@ -288,6 +288,9 @@ public interface HdfsClientConfigKeys {
int CONNECTION_RETRIES_ON_SOCKET_TIMEOUTS_DEFAULT = 0;
String RANDOM_ORDER = PREFIX + "random.order";
boolean RANDOM_ORDER_DEFAULT = false;
+ String RESOLVE_ADDRESS_NEEDED_KEY = PREFIX + "resolve-needed";
+ boolean RESOLVE_ADDRESS_NEEDED_DEFAULT = false;
+ String RESOLVE_SERVICE_KEY = PREFIX + "resolver.impl";
}
/** dfs.client.write configuration properties */
diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/namenode/ha/AbstractNNFailoverProxyProvider.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/namenode/ha/AbstractNNFailoverProxyProvider.java
index 572cb1c..93452a3 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/namenode/ha/AbstractNNFailoverProxyProvider.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/server/namenode/ha/AbstractNNFailoverProxyProvider.java
@@ -19,6 +19,7 @@
package org.apache.hadoop.hdfs.server.namenode.ha;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
@@ -35,6 +36,8 @@ import org.apache.hadoop.hdfs.DFSUtilClient;
import org.apache.hadoop.hdfs.HAUtilClient;
import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys;
import org.apache.hadoop.io.retry.FailoverProxyProvider;
+import org.apache.hadoop.net.DomainNameResolver;
+import org.apache.hadoop.net.DomainNameResolverFactory;
import org.apache.hadoop.security.UserGroupInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -176,6 +179,11 @@ public abstract class AbstractNNFailoverProxyProvider<T> implements
}
Collection<InetSocketAddress> addressesOfNns = addressesInNN.values();
+ try {
+ addressesOfNns = getResolvedAddressesIfNecessary(addressesOfNns, uri);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
for (InetSocketAddress address : addressesOfNns) {
proxies.add(new NNProxyInfo<T>(address));
}
@@ -193,6 +201,51 @@ public abstract class AbstractNNFailoverProxyProvider<T> implements
}
/**
+ * If resolved is needed: for every domain name in the parameter list,
+ * resolve them into the actual IP addresses.
+ *
+ * @param addressesOfNns The domain name list from config.
+ * @param nameNodeUri The URI of namenode/nameservice.
+ * @return The collection of resolved IP addresses.
+ * @throws IOException If there are issues resolving the addresses.
+ */
+ Collection<InetSocketAddress> getResolvedAddressesIfNecessary(
+ Collection<InetSocketAddress> addressesOfNns, URI nameNodeUri)
+ throws IOException {
+ // 'host' here is usually the ID of the nameservice when address
+ // resolving is needed.
+ String host = nameNodeUri.getHost();
+ String configKeyWithHost =
+ HdfsClientConfigKeys.Failover.RESOLVE_ADDRESS_NEEDED_KEY + "." + host;
+ boolean resolveNeeded = conf.getBoolean(configKeyWithHost,
+ HdfsClientConfigKeys.Failover.RESOLVE_ADDRESS_NEEDED_DEFAULT);
+ if (!resolveNeeded) {
+ // Early return is no resolve is necessary
+ return addressesOfNns;
+ }
+
+ Collection<InetSocketAddress> addressOfResolvedNns = new ArrayList<>();
+ DomainNameResolver dnr = DomainNameResolverFactory.newInstance(
+ conf, nameNodeUri, HdfsClientConfigKeys.Failover.RESOLVE_SERVICE_KEY);
+ // If the address needs to be resolved, get all of the IP addresses
+ // from this address and pass them into the proxy
+ LOG.info("Namenode domain name will be resolved with {}",
+ dnr.getClass().getName());
+ for (InetSocketAddress address : addressesOfNns) {
+ InetAddress[] resolvedAddresses = dnr.getAllByDomainName(
+ address.getHostName());
+ int port = address.getPort();
+ for (InetAddress raddress : resolvedAddresses) {
+ InetSocketAddress resolvedAddress = new InetSocketAddress(
+ raddress, port);
+ addressOfResolvedNns.add(resolvedAddress);
+ }
+ }
+
+ return addressOfResolvedNns;
+ }
+
+ /**
* Check whether random order is configured for failover proxy provider
* for the namenode/nameservice.
*
diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestConfiguredFailoverProxyProvider.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestConfiguredFailoverProxyProvider.java
index d7a5db6..5892246 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-client/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestConfiguredFailoverProxyProvider.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestConfiguredFailoverProxyProvider.java
@@ -20,17 +20,21 @@ package org.apache.hadoop.hdfs.server.namenode.ha;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys;
import org.apache.hadoop.hdfs.protocol.ClientProtocol;
+import org.apache.hadoop.net.MockDomainNameResolver;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.event.Level;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
@@ -40,6 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -54,6 +59,7 @@ public class TestConfiguredFailoverProxyProvider {
private int rpcPort = 8020;
private URI ns1Uri;
private URI ns2Uri;
+ private URI ns3Uri;
private String ns1;
private String ns1nn1Hostname = "machine1.foo.bar";
private InetSocketAddress ns1nn1 =
@@ -71,8 +77,12 @@ public class TestConfiguredFailoverProxyProvider {
private String ns2nn3Hostname = "router3.foo.bar";
private InetSocketAddress ns2nn3 =
new InetSocketAddress(ns2nn3Hostname, rpcPort);
+ private String ns3;
private static final int NUM_ITERATIONS = 50;
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
@BeforeClass
public static void setupClass() throws Exception {
GenericTestUtils.setLogLevel(RequestHedgingProxyProvider.LOG, Level.TRACE);
@@ -120,11 +130,42 @@ public class TestConfiguredFailoverProxyProvider {
HdfsClientConfigKeys.Failover.RANDOM_ORDER + "." + ns2,
true);
- conf.set(HdfsClientConfigKeys.DFS_NAMESERVICES, ns1 + "," + ns2);
+ ns3 = "mycluster-3-" + Time.monotonicNow();
+ ns3Uri = new URI("hdfs://" + ns3);
+
+ conf.set(HdfsClientConfigKeys.DFS_NAMESERVICES,
+ String.join(",", ns1, ns2, ns3));
conf.set("fs.defaultFS", "hdfs://" + ns1);
}
/**
+ * Add more DNS related settings to the passed in configuration.
+ * @param config Configuration file to add settings to.
+ */
+ private void addDNSSettings(Configuration config, boolean hostResolvable) {
+ config.set(
+ HdfsClientConfigKeys.DFS_HA_NAMENODES_KEY_PREFIX + "." + ns3, "nn");
+ String domain = hostResolvable
+ ? MockDomainNameResolver.DOMAIN
+ : MockDomainNameResolver.UNKNOW_DOMAIN;
+ config.set(
+ HdfsClientConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY + "." + ns3 + ".nn",
+ domain + ":" + rpcPort);
+ config.set(
+ HdfsClientConfigKeys.Failover.PROXY_PROVIDER_KEY_PREFIX + "." + ns3,
+ ConfiguredFailoverProxyProvider.class.getName());
+ config.setBoolean(
+ HdfsClientConfigKeys.Failover.RESOLVE_ADDRESS_NEEDED_KEY + "." + ns3,
+ true);
+ config.set(
+ HdfsClientConfigKeys.Failover.RESOLVE_SERVICE_KEY + "." + ns3,
+ MockDomainNameResolver.class.getName());
+ config.setBoolean(
+ HdfsClientConfigKeys.Failover.RANDOM_ORDER + "." + ns3,
+ true);
+ }
+
+ /**
* Tests getProxy with random.order configuration set to false.
* This expects the proxy order to be consistent every time a new
* ConfiguredFailoverProxyProvider is created.
@@ -209,6 +250,98 @@ public class TestConfiguredFailoverProxyProvider {
nn1Count.get() + nn2Count.get() + nn3Count.get());
}
+ @Test
+ public void testResolveDomainNameUsingDNS() throws Exception {
+ Configuration dnsConf = new Configuration(conf);
+ addDNSSettings(dnsConf, true);
+
+ // Mock ClientProtocol
+ Map<InetSocketAddress, ClientProtocol> proxyMap = new HashMap<>();
+ final AtomicInteger nn1Count = addClientMock(
+ MockDomainNameResolver.BYTE_ADDR_1, proxyMap);
+ final AtomicInteger nn2Count = addClientMock(
+ MockDomainNameResolver.BYTE_ADDR_2, proxyMap);
+
+ // Get a client multiple times
+ final Map<String, AtomicInteger> proxyResults = new HashMap<>();
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ @SuppressWarnings("resource")
+ ConfiguredFailoverProxyProvider<ClientProtocol> provider =
+ new ConfiguredFailoverProxyProvider<>(
+ dnsConf, ns3Uri, ClientProtocol.class, createFactory(proxyMap));
+ ClientProtocol proxy = provider.getProxy().proxy;
+ String proxyAddress = provider.getProxy().proxyInfo;
+
+ if (proxyResults.containsKey(proxyAddress)) {
+ proxyResults.get(proxyAddress).incrementAndGet();
+ } else {
+ proxyResults.put(proxyAddress, new AtomicInteger(1));
+ }
+ proxy.getStats();
+ }
+
+ // Check we got the proper addresses
+ assertEquals(2, proxyResults.size());
+ assertTrue(
+ "nn1 wasn't returned: " + proxyResults,
+ proxyResults.containsKey(
+ "/" + MockDomainNameResolver.ADDR_1 + ":8020"));
+ assertTrue(
+ "nn2 wasn't returned: " + proxyResults,
+ proxyResults.containsKey(
+ "/" + MockDomainNameResolver.ADDR_2 + ":8020"));
+
+ // Check that the Namenodes were invoked
+ assertEquals(NUM_ITERATIONS, nn1Count.get() + nn2Count.get());
+ assertTrue("nn1 was selected too much:" + nn1Count.get(),
+ nn1Count.get() < NUM_ITERATIONS);
+ assertTrue("nn1 should have been selected: " + nn1Count.get(),
+ nn1Count.get() > 0);
+ assertTrue("nn2 was selected too much:" + nn2Count.get(),
+ nn2Count.get() < NUM_ITERATIONS);
+ assertTrue(
+ "nn2 should have been selected: " + nn2Count.get(),
+ nn2Count.get() > 0);
+ }
+
+ @Test
+ public void testResolveDomainNameUsingDNSUnknownHost() throws Exception {
+ Configuration dnsConf = new Configuration(conf);
+ addDNSSettings(dnsConf, false);
+
+ Map<InetSocketAddress, ClientProtocol> proxyMap = new HashMap<>();
+ exception.expect(RuntimeException.class);
+ ConfiguredFailoverProxyProvider<ClientProtocol> provider =
+ new ConfiguredFailoverProxyProvider<>(
+ dnsConf, ns3Uri, ClientProtocol.class, createFactory(proxyMap));
+
+ assertNull("failover proxy cannot be created due to unknownhost",
+ provider);
+ }
+
+ /**
+ * Add a ClientProtocol mock for the proxy.
+ * @param addr IP address for the destination.
+ * @param proxyMap Map containing the client for each target address.
+ * @return The counter for the number of calls to this target.
+ * @throws Exception If the client cannot be created.
+ */
+ private AtomicInteger addClientMock(
+ byte[] addr, Map<InetSocketAddress, ClientProtocol> proxyMap)
+ throws Exception {
+
+ final AtomicInteger counter = new AtomicInteger(0);
+ InetAddress inetAddr = InetAddress.getByAddress(addr);
+ InetSocketAddress inetSockerAddr =
+ new InetSocketAddress(inetAddr, rpcPort);
+
+ final ClientProtocol cpMock = mock(ClientProtocol.class);
+ when(cpMock.getStats()).thenAnswer(createAnswer(counter, 1));
+ proxyMap.put(inetSockerAddr, cpMock);
+
+ return counter;
+ }
+
/**
* createAnswer creates an Answer for using with the ClientProtocol mocks.
* @param counter counter to increment
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
index 57dc279..94d6512 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
@@ -3728,7 +3728,12 @@
<value>false</value>
<description>
Determines if the failover proxies are picked in random order instead of the
- configured order. The prefix can be used with an optional nameservice ID
+ configured order. Random order may be enabled for better load balancing
+ or to avoid always hitting failed ones first if the failed ones appear in the
+ beginning of the configured or resolved list.
+ For example, In the case of multiple RBF routers or ObserverNameNodes,
+ it is recommended to be turned on for load balancing.
+ The config name can be extended with an optional nameservice ID
(of form dfs.client.failover.random.order[.nameservice]) in case multiple
nameservices exist and random order should be enabled for specific
nameservices.
@@ -3736,6 +3741,30 @@
</property>
<property>
+ <name>dfs.client.failover.resolve-needed</name>
+ <value>false</value>
+ <description>
+ Determines if the given nameservice address is a domain name which needs to
+ be resolved (using the resolver configured by dfs.client.failover.resolver-impl).
+ This adds a transparency layer in the client so physical server address
+ can change without changing the client. The config name can be extended with
+ an optional nameservice ID (of form dfs.client.failover.resolve-needed[.nameservice])
+ to configure specific nameservices when multiple nameservices exist.
+ </description>
+</property>
+
+<property>
+ <name>dfs.client.failover.resolver.impl</name>
+ <value>org.apache.hadoop.net.DNSDomainNameResolver</value>
+ <description>
+ Determines what class to use to resolve nameservice name to specific machine
+ address(es). The config name can be extended with an optional nameservice ID
+ (of form dfs.client.failover.resolver.impl[.nameservice]) to configure
+ specific nameservices when multiple nameservices exist.
+ </description>
+</property>
+
+<property>
<name>dfs.client.key.provider.cache.expiry</name>
<value>864000000</value>
<description>
---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org