[KYUUBI-SHADED #4] Copy StaticHostProvider from Zookeeper 3.4.14
### _Why are the changes needed?_
Simply copy `StaticHostProvider` from Zookeeper 3.4.14, to pave the way for fixing ZOOKEEPER-3779
### _How was this patch tested?_
- [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
- [ ] Add screenshots for manual tests if appropriate
- [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request
Closes #4 from pan3793/zk-34.
6c588c9 [Cheng Pan] Copy StaticHostProvider from Zookeeper 3.4.14
Authored-by: Cheng Pan <chengpan@apache.org>
Signed-off-by: Cheng Pan <chengpan@apache.org>
diff --git a/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/pom.xml b/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/pom.xml
index 3faf27d..c6b8e1d 100644
--- a/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/pom.xml
+++ b/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/pom.xml
@@ -33,6 +33,8 @@
<properties>
<zookeeper.version>3.4.14</zookeeper.version>
<curator.version>4.2.0</curator.version>
+ <slf4j.version>1.7.25</slf4j.version>
+ <yetus.version>0.5.0</yetus.version>
</properties>
<dependencyManagement>
@@ -47,6 +49,18 @@
<dependencies>
<dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.yetus</groupId>
+ <artifactId>audience-annotations</artifactId>
+ <version>${yetus.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<exclusions>
diff --git a/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java b/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java
new file mode 100644
index 0000000..8a24783
--- /dev/null
+++ b/kyuubi-shaded-zookeeper-parent/kyuubi-shaded-zookeeper-34/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java
@@ -0,0 +1,170 @@
+/**
+ * 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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.zookeeper.client;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Most simple HostProvider, resolves on every next() call.
+ *
+ * <p>Please be aware that although this class doesn't do any DNS caching, there're multiple levels
+ * of caching already present across the stack like in JVM, OS level, hardware, etc. The best we
+ * could do here is to get the most recent address from the underlying system which is considered
+ * up-to-date.
+ */
+@InterfaceAudience.Public
+public final class StaticHostProvider implements HostProvider {
+ public interface Resolver {
+ InetAddress[] getAllByName(String name) throws UnknownHostException;
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(StaticHostProvider.class);
+
+ private final List<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>(5);
+
+ private int lastIndex = -1;
+
+ private int currentIndex = -1;
+
+ private Resolver resolver;
+
+ /**
+ * Constructs a SimpleHostSet.
+ *
+ * @param serverAddresses possibly unresolved ZooKeeper server addresses
+ * @throws IllegalArgumentException if serverAddresses is empty or resolves to an empty list
+ */
+ public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) {
+ this.resolver =
+ new Resolver() {
+ @Override
+ public InetAddress[] getAllByName(String name) throws UnknownHostException {
+ return InetAddress.getAllByName(name);
+ }
+ };
+ init(serverAddresses);
+ }
+
+ /**
+ * Introduced for testing purposes. getAllByName() is a static method of InetAddress, therefore
+ * cannot be easily mocked. By abstraction of Resolver interface we can easily inject a mocked
+ * implementation in tests.
+ *
+ * @param serverAddresses possibly unresolved ZooKeeper server addresses
+ * @param resolver custom resolver implementation
+ * @throws IllegalArgumentException if serverAddresses is empty or resolves to an empty list
+ */
+ public StaticHostProvider(Collection<InetSocketAddress> serverAddresses, Resolver resolver) {
+ this.resolver = resolver;
+ init(serverAddresses);
+ }
+
+ /**
+ * Common init method for all constructors. Resolve all unresolved server addresses, put them in a
+ * list and shuffle.
+ */
+ private void init(Collection<InetSocketAddress> serverAddresses) {
+ if (serverAddresses.isEmpty()) {
+ throw new IllegalArgumentException("A HostProvider may not be empty!");
+ }
+
+ this.serverAddresses.addAll(serverAddresses);
+ Collections.shuffle(this.serverAddresses);
+ }
+
+ /**
+ * Evaluate to a hostname if one is available and otherwise it returns the string representation
+ * of the IP address.
+ *
+ * <p>In Java 7, we have a method getHostString, but earlier versions do not support it. This
+ * method is to provide a replacement for InetSocketAddress.getHostString().
+ *
+ * @param addr
+ * @return Hostname string of address parameter
+ */
+ private String getHostString(InetSocketAddress addr) {
+ String hostString = "";
+
+ if (addr == null) {
+ return hostString;
+ }
+ if (!addr.isUnresolved()) {
+ InetAddress ia = addr.getAddress();
+
+ // If the string starts with '/', then it has no hostname
+ // and we want to avoid the reverse lookup, so we return
+ // the string representation of the address.
+ if (ia.toString().startsWith("/")) {
+ hostString = ia.getHostAddress();
+ } else {
+ hostString = addr.getHostName();
+ }
+ } else {
+ // According to the Java 6 documentation, if the hostname is
+ // unresolved, then the string before the colon is the hostname.
+ String addrString = addr.toString();
+ hostString = addrString.substring(0, addrString.lastIndexOf(':'));
+ }
+
+ return hostString;
+ }
+
+ public int size() {
+ return serverAddresses.size();
+ }
+
+ public InetSocketAddress next(long spinDelay) {
+ currentIndex = ++currentIndex % serverAddresses.size();
+ if (currentIndex == lastIndex && spinDelay > 0) {
+ try {
+ Thread.sleep(spinDelay);
+ } catch (InterruptedException e) {
+ LOG.warn("Unexpected exception", e);
+ }
+ } else if (lastIndex == -1) {
+ // We don't want to sleep on the first ever connect attempt.
+ lastIndex = 0;
+ }
+
+ InetSocketAddress curAddr = serverAddresses.get(currentIndex);
+ try {
+ String curHostString = getHostString(curAddr);
+ List<InetAddress> resolvedAddresses =
+ new ArrayList<InetAddress>(Arrays.asList(this.resolver.getAllByName(curHostString)));
+ if (resolvedAddresses.isEmpty()) {
+ return curAddr;
+ }
+ Collections.shuffle(resolvedAddresses);
+ return new InetSocketAddress(resolvedAddresses.get(0), curAddr.getPort());
+ } catch (UnknownHostException e) {
+ return curAddr;
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ lastIndex = currentIndex;
+ }
+}