Explorar el Código

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 hace 6 años
padre
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;
 
 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.util.ArrayList;
@@ -44,7 +46,8 @@ public final class ConnectStringParser {
     private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
 
     /**
-     * 
+     * Parse host and port by spliting client connectString
+     * with support for IPv6 literals
      * @throws IllegalArgumentException
      *             for an invalid chroot path.
      */
@@ -68,14 +71,16 @@ public final class ConnectStringParser {
         List<String> hostsList = split(connectString,",");
         for (String host : hostsList) {
             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));
         }
     }

+ 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 java.io.File;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
@@ -59,6 +60,9 @@ public class ClientPortBindTest extends ZKTestCase{
                     InetAddress a = addrs.nextElement();
                     if(a.isLoopbackAddress()) {
                       bindAddress = a.getHostAddress();
+                      if (a instanceof Inet6Address) {
+                          bindAddress = "[" + bindAddress + "]";
+                      }
                       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;
 
 public class ConnectStringParserTest extends ZKTestCase{
-
+	private static final int DEFAULT_PORT = 2181;
+	
     @Test
     public void testSingleServerChrootPath(){
         String chrootPath = "/hallo/welt";
         String servers = "10.10.10.1";
         assertChrootPath(chrootPath,
                 new ConnectStringParser(servers+chrootPath));
+        
+        servers = "[2001:db8:1::242:ac11:2]";
+        assertChrootPath(chrootPath,
+                new ConnectStringParser(servers+chrootPath));
     }
 
     @Test
@@ -39,27 +44,44 @@ public class ConnectStringParserTest extends ZKTestCase{
         String servers = "10.10.10.1,10.10.10.2";
         assertChrootPath(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
     public void testParseServersWithoutPort(){
         String servers = "10.10.10.1,10.10.10.2";
         ConnectStringParser parser = new ConnectStringParser(servers);
-
         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(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
     public void testParseServersWithPort(){
         String servers = "10.10.10.1:112,10.10.10.2:110";
         ConnectStringParser parser = new ConnectStringParser(servers);
-
         Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString());
         Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString());
-
         Assert.assertEquals(112, parser.getServerAddresses().get(0).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){

+ 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
               role="bold">server.x</emphasis> in the left hand side of this
               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
               used by the clients must match the list of ZooKeeper servers
               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
     {
         if (s.startsWith("[")) {
-            int i = s.indexOf("]:");
+            int i = s.indexOf("]");
             if (i < 0) {
                 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 {
             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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 
 public class ConfigUtilsTest {
 
     @Test
-    public void testSplitServerConfig() throws ConfigException {
+    public void testGetHostAndPortWithIPv6() throws ConfigException {
         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[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
-    public void testSplitServerConfig2() throws ConfigException {
+    public void testGetHostAndPortWithIPv4() throws ConfigException {
         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)
-    public void testSplitServerConfig3() throws ConfigException {
+    public void testGetHostAndPortWithoutBracket() throws ConfigException {
         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
-    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);
     }
+
 }