Browse Source

ZOOKEEPER-3106: Zookeeper client supports IPv6 address and document the "IPV6 feature"

The related UTs in the `ConnectStringParserTest` have all passed,I also tested it in the real IPv6 ENV.
More details in [ZOOKEEPER-3106](https://issues.apache.org/jira/browse/ZOOKEEPER-3106)

Author: maoling <maoling199210191@sina.com>

Reviewers: andor@apache.org

Closes #587 from maoling/ZOOKEEPER-3106 and squashes the following commits:

22e20252 [maoling] fix a flaky test:ClientPortBindTest.testBindByAddress
4bb31f6f [maoling] rebase to solve the conflict in the zookeeperAdmin.xml
45091b91 [maoling] reuse the ConfigUtils.getHostAndPort()
fa984b51 [maoling] remove useless tab & add some annotations
bafc542a [maoling] ZOOKEEPER-3106: Zookeeper client supports IPv6 address and document the IPV6 feature
maoling 6 years ago
parent
commit
effa016c62

+ 12 - 7
zookeeper-client/zookeeper-client-java/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java

@@ -19,6 +19,8 @@
 package org.apache.zookeeper.client;
 package org.apache.zookeeper.client;
 
 
 import org.apache.zookeeper.common.PathUtils;
 import org.apache.zookeeper.common.PathUtils;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
+import org.apache.zookeeper.server.util.ConfigUtils;
 
 
 import java.net.InetSocketAddress;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.ArrayList;
@@ -44,7 +46,8 @@ public final class ConnectStringParser {
     private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
     private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
 
 
     /**
     /**
-     * 
+     * Parse host and port by spliting client connectString
+     * with support for IPv6 literals
      * @throws IllegalArgumentException
      * @throws IllegalArgumentException
      *             for an invalid chroot path.
      *             for an invalid chroot path.
      */
      */
@@ -68,14 +71,16 @@ public final class ConnectStringParser {
         List<String> hostsList = split(connectString,",");
         List<String> hostsList = split(connectString,",");
         for (String host : hostsList) {
         for (String host : hostsList) {
             int port = DEFAULT_PORT;
             int port = DEFAULT_PORT;
-            int pidx = host.lastIndexOf(':');
-            if (pidx >= 0) {
-                // otherwise : is at the end of the string, ignore
-                if (pidx < host.length() - 1) {
-                    port = Integer.parseInt(host.substring(pidx + 1));
+            try {
+                String[] hostAndPort = ConfigUtils.getHostAndPort(host);
+                host = hostAndPort[0];
+                if (hostAndPort.length == 2) {
+                    port = Integer.parseInt(hostAndPort[1]);
                 }
                 }
-                host = host.substring(0, pidx);
+            } catch (ConfigException e) {
+                e.printStackTrace();
             }
             }
+		    
             serverAddresses.add(InetSocketAddress.createUnresolved(host, port));
             serverAddresses.add(InetSocketAddress.createUnresolved(host, port));
         }
         }
     }
     }

+ 4 - 0
zookeeper-common/src/test/java/org/apache/zookeeper/test/ClientPortBindTest.java

@@ -21,6 +21,7 @@ package org.apache.zookeeper.test;
 import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
 import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
 
 
 import java.io.File;
 import java.io.File;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
 import java.net.NetworkInterface;
@@ -59,6 +60,9 @@ public class ClientPortBindTest extends ZKTestCase{
                     InetAddress a = addrs.nextElement();
                     InetAddress a = addrs.nextElement();
                     if(a.isLoopbackAddress()) {
                     if(a.isLoopbackAddress()) {
                       bindAddress = a.getHostAddress();
                       bindAddress = a.getHostAddress();
+                      if (a instanceof Inet6Address) {
+                          bindAddress = "[" + bindAddress + "]";
+                      }
                       break;
                       break;
                     }
                     }
                   }
                   }

+ 26 - 4
zookeeper-common/src/test/java/org/apache/zookeeper/test/ConnectStringParserTest.java

@@ -24,13 +24,18 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.junit.Test;
 
 
 public class ConnectStringParserTest extends ZKTestCase{
 public class ConnectStringParserTest extends ZKTestCase{
-
+	private static final int DEFAULT_PORT = 2181;
+	
     @Test
     @Test
     public void testSingleServerChrootPath(){
     public void testSingleServerChrootPath(){
         String chrootPath = "/hallo/welt";
         String chrootPath = "/hallo/welt";
         String servers = "10.10.10.1";
         String servers = "10.10.10.1";
         assertChrootPath(chrootPath,
         assertChrootPath(chrootPath,
                 new ConnectStringParser(servers+chrootPath));
                 new ConnectStringParser(servers+chrootPath));
+        
+        servers = "[2001:db8:1::242:ac11:2]";
+        assertChrootPath(chrootPath,
+                new ConnectStringParser(servers+chrootPath));
     }
     }
 
 
     @Test
     @Test
@@ -39,27 +44,44 @@ public class ConnectStringParserTest extends ZKTestCase{
         String servers = "10.10.10.1,10.10.10.2";
         String servers = "10.10.10.1,10.10.10.2";
         assertChrootPath(chrootPath,
         assertChrootPath(chrootPath,
                 new ConnectStringParser(servers+chrootPath));
                 new ConnectStringParser(servers+chrootPath));
+        
+        servers = "[2001:db8:1::242:ac11:2]:2181,[2001:db8:85a3:8d3:1319:8a2e:370:7348]:5678";
+        assertChrootPath(chrootPath,
+                new ConnectStringParser(servers+chrootPath));
     }
     }
 
 
     @Test
     @Test
     public void testParseServersWithoutPort(){
     public void testParseServersWithoutPort(){
         String servers = "10.10.10.1,10.10.10.2";
         String servers = "10.10.10.1,10.10.10.2";
         ConnectStringParser parser = new ConnectStringParser(servers);
         ConnectStringParser parser = new ConnectStringParser(servers);
-
         Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString());
         Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString());
+        Assert.assertEquals(DEFAULT_PORT, parser.getServerAddresses().get(0).getPort());
         Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString());
         Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString());
+        Assert.assertEquals(DEFAULT_PORT, parser.getServerAddresses().get(1).getPort());
+        
+        servers = "[2001:db8:1::242:ac11:2],[2001:db8:85a3:8d3:1319:8a2e:370:7348]";
+        parser = new ConnectStringParser(servers);
+        Assert.assertEquals("2001:db8:1::242:ac11:2", parser.getServerAddresses().get(0).getHostString());
+        Assert.assertEquals(DEFAULT_PORT, parser.getServerAddresses().get(0).getPort());
+        Assert.assertEquals("2001:db8:85a3:8d3:1319:8a2e:370:7348", parser.getServerAddresses().get(1).getHostString());
+        Assert.assertEquals(DEFAULT_PORT, parser.getServerAddresses().get(1).getPort());
     }
     }
 
 
     @Test
     @Test
     public void testParseServersWithPort(){
     public void testParseServersWithPort(){
         String servers = "10.10.10.1:112,10.10.10.2:110";
         String servers = "10.10.10.1:112,10.10.10.2:110";
         ConnectStringParser parser = new ConnectStringParser(servers);
         ConnectStringParser parser = new ConnectStringParser(servers);
-
         Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString());
         Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString());
         Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString());
         Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString());
-
         Assert.assertEquals(112, parser.getServerAddresses().get(0).getPort());
         Assert.assertEquals(112, parser.getServerAddresses().get(0).getPort());
         Assert.assertEquals(110, parser.getServerAddresses().get(1).getPort());
         Assert.assertEquals(110, parser.getServerAddresses().get(1).getPort());
+        
+        servers = "[2001:db8:1::242:ac11:2]:1234,[2001:db8:85a3:8d3:1319:8a2e:370:7348]:5678";
+        parser = new ConnectStringParser(servers);
+        Assert.assertEquals("2001:db8:1::242:ac11:2", parser.getServerAddresses().get(0).getHostString());
+        Assert.assertEquals("2001:db8:85a3:8d3:1319:8a2e:370:7348", parser.getServerAddresses().get(1).getHostString());
+        Assert.assertEquals(1234, parser.getServerAddresses().get(0).getPort());
+        Assert.assertEquals(5678, parser.getServerAddresses().get(1).getPort());
     }
     }
 
 
     private void assertChrootPath(String expected, ConnectStringParser parser){
     private void assertChrootPath(String expected, ConnectStringParser parser){

+ 5 - 1
zookeeper-docs/src/documentation/content/xdocs/zookeeperAdmin.xml

@@ -1222,7 +1222,11 @@ server.3=zoo3:2888:3888</programlisting>
               <emphasis role="bold">x</emphasis> in <emphasis
               <emphasis role="bold">x</emphasis> in <emphasis
               role="bold">server.x</emphasis> in the left hand side of this
               role="bold">server.x</emphasis> in the left hand side of this
               setting.</para>
               setting.</para>
-
+              
+              <para><emphasis role="bold">hostname</emphasis> can also be configured
+              with IPv4/IPv6 address.Notice when use IPv6 address which should be
+              surrounded by brackets([])</para>
+              
               <para>The list of servers that make up ZooKeeper servers that is
               <para>The list of servers that make up ZooKeeper servers that is
               used by the clients must match the list of ZooKeeper servers
               used by the clients must match the list of ZooKeeper servers
               that each ZooKeeper server has.</para>
               that each ZooKeeper server has.</para>

+ 12 - 8
zookeeper-server/src/main/java/org/apache/zookeeper/server/util/ConfigUtils.java

@@ -72,17 +72,21 @@ public class ConfigUtils {
         throws ConfigException
         throws ConfigException
     {
     {
         if (s.startsWith("[")) {
         if (s.startsWith("[")) {
-            int i = s.indexOf("]:");
+            int i = s.indexOf("]");
             if (i < 0) {
             if (i < 0) {
                 throw new ConfigException(s + " starts with '[' but has no matching ']:'");
                 throw new ConfigException(s + " starts with '[' but has no matching ']:'");
             }
             }
-
-            String[] sa = s.substring(i + 2).split(":");
-            String[] nsa = new String[sa.length + 1];
-            nsa[0] = s.substring(1, i);
-            System.arraycopy(sa, 0, nsa, 1, sa.length);
-
-            return nsa;
+            if (i + 2 == s.length()) {
+                throw new ConfigException(s + " doesn't have a port after colon");
+            }
+            if (i + 2 < s.length()) {
+               String[] sa = s.substring(i + 2).split(":");
+               String[] nsa = new String[sa.length + 1];
+               nsa[0] = s.substring(1, i);
+               System.arraycopy(sa, 0, nsa, 1, sa.length);
+               return nsa;
+            }
+            return new String[] {s.replaceAll("\\[|\\]", "")};
         } else {
         } else {
             return s.split(":");
             return s.split(":");
         }
         }

+ 30 - 10
zookeeper-server/src/test/java/org/apache/zookeeper/server/util/ConfigUtilsTest.java

@@ -20,33 +20,53 @@ package org.apache.zookeeper.server.util;
 
 
 import org.junit.Test;
 import org.junit.Test;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 
 
 public class ConfigUtilsTest {
 public class ConfigUtilsTest {
 
 
     @Test
     @Test
-    public void testSplitServerConfig() throws ConfigException {
+    public void testGetHostAndPortWithIPv6() throws ConfigException {
         String[] nsa = ConfigUtils.getHostAndPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443");
         String[] nsa = ConfigUtils.getHostAndPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443");
-        System.out.println(nsa[0]);
         assertEquals(nsa[0], "2001:db8:85a3:8d3:1319:8a2e:370:7348");
         assertEquals(nsa[0], "2001:db8:85a3:8d3:1319:8a2e:370:7348");
         assertEquals(nsa[1], "443");
         assertEquals(nsa[1], "443");
+        
+        nsa = ConfigUtils.getHostAndPort("[2001:db8:1::242:ac11:2]:2888:3888");
+        assertEquals(nsa[0], "2001:db8:1::242:ac11:2");
+        assertEquals(nsa[1], "2888");
+        assertEquals(nsa[2], "3888");
     }
     }
 
 
     @Test
     @Test
-    public void testSplitServerConfig2() throws ConfigException {
+    public void testGetHostAndPortWithIPv4() throws ConfigException {
         String[] nsa = ConfigUtils.getHostAndPort("127.0.0.1:443");
         String[] nsa = ConfigUtils.getHostAndPort("127.0.0.1:443");
-        assertEquals(nsa.length, 2, 0);
+        assertEquals(nsa[0], "127.0.0.1");
+        assertEquals(nsa[1], "443");
+        
+        nsa = ConfigUtils.getHostAndPort("127.0.0.1:2888:3888");
+        assertEquals(nsa[0], "127.0.0.1");
+        assertEquals(nsa[1], "2888");
+        assertEquals(nsa[2], "3888");
     }
     }
 
 
     @Test(expected = ConfigException.class)
     @Test(expected = ConfigException.class)
-    public void testSplitServerConfig3() throws ConfigException {
+    public void testGetHostAndPortWithoutBracket() throws ConfigException {
         String[] nsa = ConfigUtils.getHostAndPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348");
         String[] nsa = ConfigUtils.getHostAndPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348");
     }
     }
-
+    
+    @Test(expected = ConfigException.class)
+    public void testGetHostAndPortWithoutPortAfterColon() throws ConfigException {
+        String[] nsa = ConfigUtils.getHostAndPort("[2001:db8:1::242:ac11:2]:");
+    }
+    
     @Test
     @Test
-    public void testSplitServerConfig4() throws ConfigException {
-        String[] nsa = ConfigUtils.getHostAndPort("2001:db8:85a3:8d3:1319:8a2e:370:7348:443");
-        assertFalse(nsa.length == 2);
+    public void testGetHostAndPortWithoutPort() throws ConfigException {
+    	String[] nsa = ConfigUtils.getHostAndPort("127.0.0.1");
+    	assertEquals(nsa[0], "127.0.0.1");
+    	assertEquals(nsa.length, 1);
+    	
+    	nsa = ConfigUtils.getHostAndPort("[2001:db8:1::242:ac11:2]");
+    	assertEquals(nsa[0], "2001:db8:1::242:ac11:2");
+    	assertEquals(nsa.length, 1);
     }
     }
+
 }
 }