You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by an...@apache.org on 2019/12/18 07:26:46 UTC

[zookeeper] branch branch-3.5 updated: ZOOKEEPER-3057: Fix IPv6 literal usage

This is an automated email from the ASF dual-hosted git repository.

andor pushed a commit to branch branch-3.5
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/branch-3.5 by this push:
     new 0ec8ffc  ZOOKEEPER-3057: Fix IPv6 literal usage
0ec8ffc is described below

commit 0ec8ffc13cd543a51ffa63af7c6d07290d1b23d1
Author: Xiangyu Yao <ya...@bytedance.com>
AuthorDate: Wed Dec 18 08:26:39 2019 +0100

    ZOOKEEPER-3057: Fix IPv6 literal usage
    
    This commit backports #548 to branch-3.5.
    
    I cherry picked all the changes except the file (NetUtils.java) where modification were already made by other backports.
    
    Author: Xiangyu Yao <ya...@bytedance.com>
    
    Reviewers: eolivelli@apache.org, andor@apache.org
    
    Closes #1168 from xy24/fix-ipv6-literal
---
 .../zookeeper/server/quorum/LocalPeerBean.java     |  9 ++--
 .../zookeeper/server/quorum/QuorumCnxManager.java  | 18 ++++---
 .../apache/zookeeper/server/quorum/QuorumPeer.java | 36 ++++----------
 .../zookeeper/server/quorum/QuorumPeerConfig.java  | 10 ++--
 .../apache/zookeeper/server/util/ConfigUtils.java  | 25 ++++++++++
 .../org/apache/zookeeper/common/NetUtilsTest.java  | 55 ++++++++++++++++++++++
 .../zookeeper/server/quorum/LocalPeerBeanTest.java |  2 +-
 .../server/quorum/QuorumPeerMainTest.java          | 46 +++++++++++++-----
 .../zookeeper/server/util/ConfigUtilsTest.java     | 52 ++++++++++++++++++++
 9 files changed, 197 insertions(+), 56 deletions(-)

diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/LocalPeerBean.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/LocalPeerBean.java
index 91d779b..25ea3f3 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/LocalPeerBean.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/LocalPeerBean.java
@@ -19,6 +19,7 @@
 package org.apache.zookeeper.server.quorum;
 
 
+import static org.apache.zookeeper.common.NetUtils.formatInetAddr;
 
 /**
  * Implementation of the local peer MBean interface.
@@ -71,7 +72,7 @@ public class LocalPeerBean extends ServerBean implements LocalPeerMXBean {
     }
     
     public String getQuorumAddress() {
-        return peer.getQuorumAddress().toString();
+        return formatInetAddr(peer.getQuorumAddress());
     }
     
     public int getElectionType() {
@@ -79,14 +80,12 @@ public class LocalPeerBean extends ServerBean implements LocalPeerMXBean {
     }
 
     public String getElectionAddress() {
-        return peer.getElectionAddress().getHostString() + ":" +
-            peer.getElectionAddress().getPort();
+        return formatInetAddr(peer.getElectionAddress());
     }
 
     public String getClientAddress() {
         if (null != peer.cnxnFactory) {
-            return String.format("%s:%d", peer.cnxnFactory.getLocalAddress()
-                    .getHostString(), peer.getClientPort());
+            return formatInetAddr(peer.cnxnFactory.getLocalAddress());
         } else {
             return "";
         }
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumCnxManager.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
index 4dcab9b..22a24bc 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumCnxManager.java
@@ -54,6 +54,8 @@ import org.apache.zookeeper.server.ZooKeeperThread;
 import org.apache.zookeeper.server.quorum.auth.QuorumAuthLearner;
 import org.apache.zookeeper.server.quorum.auth.QuorumAuthServer;
 import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
+import org.apache.zookeeper.server.util.ConfigUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -225,10 +227,13 @@ public class QuorumCnxManager {
                         num_read, remaining, sid);
             }
 
-            // FIXME: IPv6 is not supported. Using something like Guava's HostAndPort
-            //        parser would be good.
             String addr = new String(b);
-            String[] host_port = addr.split(":");
+            String[] host_port;
+            try {
+                host_port = ConfigUtils.getHostAndPort(addr);
+            } catch (ConfigException e) {
+                throw new InitialMessageException("Badly formed address: %s", addr);
+            }
 
             if (host_port.length != 2) {
                 throw new InitialMessageException("Badly formed address: %s", addr);
@@ -239,6 +244,8 @@ public class QuorumCnxManager {
                 port = Integer.parseInt(host_port[1]);
             } catch (NumberFormatException e) {
                 throw new InitialMessageException("Bad port number: %s", host_port[1]);
+            } catch (ArrayIndexOutOfBoundsException e) {
+                throw new InitialMessageException("No port number in: %s", addr);
             }
 
             return new InitialMessage(sid, new InetSocketAddress(host_port[0], port));
@@ -405,8 +412,7 @@ public class QuorumCnxManager {
             // represents protocol version (in other words - message type)
             dout.writeLong(PROTOCOL_VERSION);
             dout.writeLong(self.getId());
-            final InetSocketAddress electionAddr = self.getElectionAddress();
-            String addr = electionAddr.getHostString() + ":" + electionAddr.getPort();
+            String addr = formatInetAddr(self.getElectionAddress());
             byte[] addr_bytes = addr.getBytes();
             dout.writeInt(addr_bytes.length);
             dout.write(addr_bytes);
@@ -916,7 +922,7 @@ public class QuorumCnxManager {
                             client = ss.accept();
                             setSockOpts(client);
                             LOG.info("Received connection request "
-                                     + formatInetAddr((InetSocketAddress)client.getRemoteSocketAddress()));
+                                    + formatInetAddr((InetSocketAddress)client.getRemoteSocketAddress()));
                             // Receive and handle the connection request
                             // asynchronously if the quorum sasl authentication is
                             // enabled. This is required because sasl server
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java
index 9ac635e..40ea311 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java
@@ -72,10 +72,13 @@ import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 import org.apache.zookeeper.server.quorum.flexible.QuorumMaj;
 import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
+import org.apache.zookeeper.server.util.ConfigUtils;
 import org.apache.zookeeper.server.util.ZxidUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.zookeeper.common.NetUtils.formatInetAddr;
+
 /**
  * This class manages the quorum protocol. There are three states this server
  * can be in:
@@ -213,27 +216,6 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
             }
         }
 
-        private static String[] splitWithLeadingHostname(String s)
-                throws ConfigException
-        {
-            /* Does it start with an IPv6 literal? */
-            if (s.startsWith("[")) {
-                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;
-            } else {
-                return s.split(":");
-            }
-        }
-
         private static final String wrongFormat = " does not have the form server_config or server_config;client_config"+
         " where server_config is host:port:port or host:port:port:type and client_config is port or host:port";
 
@@ -241,7 +223,7 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
             // LOG.warn("sid = " + sid + " addressStr = " + addressStr);
             this.id = sid;
             String serverClientParts[] = addressStr.split(";");
-            String serverParts[] = splitWithLeadingHostname(serverClientParts[0]);
+            String serverParts[] = ConfigUtils.getHostAndPort(serverClientParts[0]);
             if ((serverClientParts.length > 2) || (serverParts.length < 3)
                     || (serverParts.length > 4)) {
                 throw new ConfigException(addressStr + wrongFormat);
@@ -249,7 +231,7 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
 
             if (serverClientParts.length == 2) {
                 //LOG.warn("ClientParts: " + serverClientParts[1]);
-                String clientParts[] = splitWithLeadingHostname(serverClientParts[1]);
+                String clientParts[] = ConfigUtils.getHostAndPort(serverClientParts[1]);
                 if (clientParts.length > 2) {
                     throw new ConfigException(addressStr + wrongFormat);
                 }
@@ -1417,14 +1399,14 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
             if (leader != null) {
                 for (LearnerHandler fh : leader.getLearners()) {
                     if (fh.getSocket() != null) {
-                        String s = fh.getSocket().getRemoteSocketAddress().toString();
+                        String s = formatInetAddr((InetSocketAddress)fh.getSocket().getRemoteSocketAddress());
                         if (leader.isLearnerSynced(fh))
                             s += "*";
                         l.add(s);
                     }
                 }
             } else if (follower != null) {
-                l.add(follower.sock.getRemoteSocketAddress().toString());
+                l.add(formatInetAddr((InetSocketAddress)follower.sock.getRemoteSocketAddress()));
             }
         }
         return l.toArray(new String[0]);
@@ -2115,8 +2097,8 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider
     private void updateThreadName() {
         String plain = cnxnFactory != null ?
                 cnxnFactory.getLocalAddress() != null ?
-                        cnxnFactory.getLocalAddress().toString() : "disabled" : "disabled";
-        String secure = secureCnxnFactory != null ? secureCnxnFactory.getLocalAddress().toString() : "disabled";
+                        formatInetAddr(cnxnFactory.getLocalAddress()) : "disabled" : "disabled";
+        String secure = secureCnxnFactory != null ? formatInetAddr(secureCnxnFactory.getLocalAddress()) : "disabled";
         setName(String.format("QuorumPeer[myid=%d](plain=%s)(secure=%s)", getId(), plain, secure));
     }
 
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
index dc5aec2..d32806f 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
@@ -55,6 +55,8 @@ import org.apache.zookeeper.server.quorum.flexible.QuorumMaj;
 import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
 import org.apache.zookeeper.server.util.VerifyingFileFactory;
 
+import static org.apache.zookeeper.common.NetUtils.formatInetAddr;
+
 @InterfaceAudience.Public
 public class QuorumPeerConfig {
     private static final Logger LOG = LoggerFactory.getLogger(QuorumPeerConfig.class);
@@ -379,10 +381,10 @@ public class QuorumPeerConfig {
         } else if (clientPortAddress != null) {
             this.clientPortAddress = new InetSocketAddress(
                     InetAddress.getByName(clientPortAddress), clientPort);
-            LOG.info("clientPortAddress is {}", this.clientPortAddress.toString());
+            LOG.info("clientPortAddress is {}", formatInetAddr(this.clientPortAddress));
         } else {
             this.clientPortAddress = new InetSocketAddress(clientPort);
-            LOG.info("clientPortAddress is {}", this.clientPortAddress.toString());
+            LOG.info("clientPortAddress is {}", formatInetAddr(this.clientPortAddress));
         }
 
         if (secureClientPort == 0) {
@@ -393,10 +395,10 @@ public class QuorumPeerConfig {
         } else if (secureClientPortAddress != null) {
             this.secureClientPortAddress = new InetSocketAddress(
                     InetAddress.getByName(secureClientPortAddress), secureClientPort);
-            LOG.info("secureClientPortAddress is {}", this.secureClientPortAddress.toString());
+            LOG.info("secureClientPortAddress is {}", formatInetAddr(this.secureClientPortAddress));
         } else {
             this.secureClientPortAddress = new InetSocketAddress(secureClientPort);
-            LOG.info("secureClientPortAddress is {}", this.secureClientPortAddress.toString());
+            LOG.info("secureClientPortAddress is {}", formatInetAddr(this.secureClientPortAddress));
         }
         if (this.secureClientPortAddress != null) {
             configureSSLAuth();
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/util/ConfigUtils.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/util/ConfigUtils.java
index 1ca37d1..4c50db6 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/util/ConfigUtils.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/util/ConfigUtils.java
@@ -60,4 +60,29 @@ public class ConfigUtils {
         }
         return version + " " + sb.toString();
     }
+
+    /**
+     * Gets host and port by spliting server config with support for IPv6 literals
+     * @return String[] first element being the IP address and the next being the port
+     * @param s server config, server:port
+     */
+    public static String[] getHostAndPort(String s)
+            throws ConfigException
+    {
+        if (s.startsWith("[")) {
+            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;
+        } else {
+            return s.split(":");
+        }
+    }
 }
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/NetUtilsTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/NetUtilsTest.java
new file mode 100644
index 0000000..9887926
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/NetUtilsTest.java
@@ -0,0 +1,55 @@
+package org.apache.zookeeper.common;
+
+import org.apache.zookeeper.common.NetUtils;
+import org.apache.zookeeper.ZKTestCase;
+import org.hamcrest.core.AnyOf;
+import org.hamcrest.core.IsEqual;
+import org.junit.Assert;
+import org.junit.Test;
+import java.net.InetSocketAddress;
+
+public class NetUtilsTest extends ZKTestCase {
+
+    private Integer port = 1234;
+    private String v4addr = "127.0.0.1";
+    private String v6addr = "[0:0:0:0:0:0:0:1]";
+    private String v6addr2 = "[2600:0:0:0:0:0:0:0]";
+    private String v4local = v4addr + ":" + port.toString();
+    private String v6local = v6addr + ":" + port.toString();
+    private String v6ext = v6addr2 + ":" + port.toString();
+
+    @Test
+    public void testFormatInetAddrGoodIpv4() {
+        InetSocketAddress isa = new InetSocketAddress(v4addr, port);
+        Assert.assertEquals("127.0.0.1:1234", NetUtils.formatInetAddr(isa));
+    }
+
+    @Test
+    public void testFormatInetAddrGoodIpv6Local() {
+        // Have to use the expanded address here, hence not using v6addr in instantiation
+        InetSocketAddress isa = new InetSocketAddress("::1", port);
+        Assert.assertEquals(v6local, NetUtils.formatInetAddr(isa));
+    }
+
+    @Test
+    public void testFormatInetAddrGoodIpv6Ext() {
+        // Have to use the expanded address here, hence not using v6addr in instantiation
+        InetSocketAddress isa = new InetSocketAddress("2600::", port);
+        Assert.assertEquals(v6ext, NetUtils.formatInetAddr(isa));
+    }
+
+    @Test
+    public void testFormatInetAddrGoodHostname() {
+        InetSocketAddress isa = new InetSocketAddress("localhost", 1234);
+
+        Assert.assertThat(NetUtils.formatInetAddr(isa),
+                AnyOf.anyOf(IsEqual.equalTo(v4local), IsEqual.equalTo(v6local)
+                ));
+    }
+
+    @Test
+    public void testFormatAddrUnresolved() {
+        InetSocketAddress isa = InetSocketAddress.createUnresolved("doesnt.exist.com", 1234);
+        Assert.assertEquals("doesnt.exist.com:1234", NetUtils.formatInetAddr(isa));
+    }
+}
\ No newline at end of file
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java
index f2e0115..f0388bd 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java
@@ -60,7 +60,7 @@ public class LocalPeerBeanTest {
 
         result = remotePeerBean.getClientAddress();
         String ipv4 = "0.0.0.0:" + clientPort;
-        String ipv6 = "0:0:0:0:0:0:0:0:" + clientPort;
+        String ipv6 = "[0:0:0:0:0:0:0:0]:" + clientPort;
         assertTrue(result.equals(ipv4) || result.equals(ipv6));
         // cleanup
         cnxnFactory.shutdown();
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java
index 09cb985..b3e1e07 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java
@@ -91,18 +91,22 @@ public class QuorumPeerMainTest extends QuorumPeerTestBase {
     /**
      * Verify the ability to start a cluster.
      */
-    @Test
-    public void testQuorum() throws Exception {
+    public void testQuorumInternal(String addr) throws Exception {
         ClientBase.setupTestEnv();
 
         final int CLIENT_PORT_QP1 = PortAssignment.unique();
         final int CLIENT_PORT_QP2 = PortAssignment.unique();
 
-        String quorumCfgSection =
-            "server.1=127.0.0.1:" + PortAssignment.unique()
-            + ":" + PortAssignment.unique() + ";" + CLIENT_PORT_QP1
-            + "\nserver.2=127.0.0.1:" + PortAssignment.unique() 
-            + ":" + PortAssignment.unique() + ";" + CLIENT_PORT_QP2;
+        String quorumCfgSection = String.format("server.1=%1$s:%2$s:%3$s;%4$s",
+                addr,
+                PortAssignment.unique(),
+                PortAssignment.unique(),
+                CLIENT_PORT_QP1) + "\n" +
+                String.format("server.2=%1$s:%2$s:%3$s;%4$s",
+                        addr,
+                        PortAssignment.unique(),
+                        PortAssignment.unique(),
+                        CLIENT_PORT_QP2);
 
         MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection);
         MainThread q2 = new MainThread(2, CLIENT_PORT_QP2, quorumCfgSection);
@@ -110,10 +114,10 @@ public class QuorumPeerMainTest extends QuorumPeerTestBase {
         q2.start();
 
         Assert.assertTrue("waiting for server 1 being up",
-                        ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP1,
+                ClientBase.waitForServerUp(addr + ":" + CLIENT_PORT_QP1,
                         CONNECTION_TIMEOUT));
         Assert.assertTrue("waiting for server 2 being up",
-                        ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP2,
+                ClientBase.waitForServerUp(addr + ":" + CLIENT_PORT_QP2,
                         CONNECTION_TIMEOUT));
         QuorumPeer quorumPeer = q1.main.quorumPeer;
 
@@ -125,7 +129,7 @@ public class QuorumPeerMainTest extends QuorumPeerTestBase {
                 "Default value of maximumSessionTimeOut is not considered",
                 tickTime * 20, quorumPeer.getMaxSessionTimeout());
 
-        ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT_QP1,
+        ZooKeeper zk = new ZooKeeper(addr + ":" + CLIENT_PORT_QP1,
                 ClientBase.CONNECTION_TIMEOUT, this);
         waitForOne(zk, States.CONNECTED);
         zk.create("/foo_q1", "foobar1".getBytes(), Ids.OPEN_ACL_UNSAFE,
@@ -133,7 +137,7 @@ public class QuorumPeerMainTest extends QuorumPeerTestBase {
         Assert.assertEquals(new String(zk.getData("/foo_q1", null, null)), "foobar1");
         zk.close();
 
-        zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT_QP2,
+        zk = new ZooKeeper(addr + ":" + CLIENT_PORT_QP2,
                 ClientBase.CONNECTION_TIMEOUT, this);
         waitForOne(zk, States.CONNECTED);
         zk.create("/foo_q2", "foobar2".getBytes(), Ids.OPEN_ACL_UNSAFE,
@@ -145,14 +149,30 @@ public class QuorumPeerMainTest extends QuorumPeerTestBase {
         q2.shutdown();
 
         Assert.assertTrue("waiting for server 1 down",
-                ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT_QP1,
+                ClientBase.waitForServerDown(addr + ":" + CLIENT_PORT_QP1,
                         ClientBase.CONNECTION_TIMEOUT));
         Assert.assertTrue("waiting for server 2 down",
-                ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT_QP2,
+                ClientBase.waitForServerDown(addr + ":" + CLIENT_PORT_QP2,
                         ClientBase.CONNECTION_TIMEOUT));
     }
 
     /**
+     * Verify the ability to start a cluster.
+     */
+    @Test
+    public void testQuorum() throws Exception {
+        testQuorumInternal("127.0.0.1");
+    }
+
+    /**
+     * Verify the ability to start a cluster. IN V6!!!!
+     */
+    @Test
+    public void testQuorumV6() throws Exception {
+        testQuorumInternal("[::1]");
+    }
+
+    /**
      * Test early leader abandonment.
      */
     @Test
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/server/util/ConfigUtilsTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/util/ConfigUtilsTest.java
new file mode 100644
index 0000000..d852d0f
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/util/ConfigUtilsTest.java
@@ -0,0 +1,52 @@
+/**
+ * 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.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 {
+        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");
+    }
+
+    @Test
+    public void testSplitServerConfig2() throws ConfigException {
+        String[] nsa = ConfigUtils.getHostAndPort("127.0.0.1:443");
+        assertEquals(nsa.length, 2, 0);
+    }
+
+    @Test(expected = ConfigException.class)
+    public void testSplitServerConfig3() throws ConfigException {
+        String[] nsa = ConfigUtils.getHostAndPort("[2001:db8:85a3:8d3:1319:8a2e:370:7348");
+    }
+
+    @Test
+    public void testSplitServerConfig4() throws ConfigException {
+        String[] nsa = ConfigUtils.getHostAndPort("2001:db8:85a3:8d3:1319:8a2e:370:7348:443");
+        assertFalse(nsa.length == 2);
+    }
+}