/** * Copyright (c) 2017-2020, Salesforce.com, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * * * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.salesforce.kafka.test; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; class ZookeeperTestServerTest { /** * Test start() starts the service and gracefully handles being called multiple times. */ @Test void testStart() throws Exception { // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { // Start service. zookeeperTestServer.start(); // Validate the zookeeper server appears to be functioning. testZookeeperConnection(zookeeperTestServer.getConnectString()); // Call start again zookeeperTestServer.start(); // Validate the zookeeper server appears to be functioning. testZookeeperConnection(zookeeperTestServer.getConnectString()); } } /** * Test restart() starts the service gracefully if not started. */ @Test void testRestartCold() throws Exception { // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { // Start service by calling restart() zookeeperTestServer.restart(); // Validate the zookeeper server appears to be functioning. testZookeeperConnection(zookeeperTestServer.getConnectString()); } } /** * Test restart() restarts the service if already started. */ @Test void testRestart() throws Exception { // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { // Start service zookeeperTestServer.start(); // Validate the zookeeper server appears to be functioning. testZookeeperConnection(zookeeperTestServer.getConnectString()); // Call restart zookeeperTestServer.restart(); // Validate the zookeeper server appears to be functioning. testZookeeperConnection(zookeeperTestServer.getConnectString()); } } /** * Test restarting preserves data stored. */ @Test void testRestartPreservesData() throws Exception { final String pathStr = "/preservedData" + System.currentTimeMillis(); final String dataStr = "MySpecialData" + System.currentTimeMillis(); // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { // Start service zookeeperTestServer.start(); ZooKeeper zkClient = null; try { // Create client zkClient = createZkClient(zookeeperTestServer.getConnectString()); // Write data writeZkString(zkClient, pathStr, dataStr); } finally { // Close client. if (zkClient != null) { zkClient.close(); } } // Call restart on server. zookeeperTestServer.restart(); // Attempt to read original data out. try { // Create client zkClient = createZkClient(zookeeperTestServer.getConnectString()); // Write data final String result = readZkString(zkClient, pathStr); Assertions.assertEquals(dataStr, result); } finally { // Close client. if (zkClient != null) { zkClient.close(); } } } } /** * Test calling stop() then start() preserves data stored. */ @Test void testStopAndStartPreservesData() throws Exception { final String pathStr = "/preservedStopAndStartData" + System.currentTimeMillis(); final String dataStr = "MyOtherSpecialData" + System.currentTimeMillis(); // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { // Start service zookeeperTestServer.start(); ZooKeeper zkClient = null; try { // Create client zkClient = createZkClient(zookeeperTestServer.getConnectString()); // Write data writeZkString(zkClient, pathStr, dataStr); } finally { // Close client. if (zkClient != null) { zkClient.close(); } } // Call stop on server. zookeeperTestServer.stop(); Thread.sleep(2000L); // Followed by start zookeeperTestServer.start(); // Attempt to read original data out. try { // Create client zkClient = createZkClient(zookeeperTestServer.getConnectString()); // Write data final String result = readZkString(zkClient, pathStr); Assertions.assertEquals(dataStr, result); } finally { // Close client. if (zkClient != null) { zkClient.close(); } } } } /** * Test calling getConnectString() before calling start on the service. * It's expected to throw an IllegalStateException. */ @Test void testGetConnectStringBeforeStartingService() { // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { Assertions.assertThrows(IllegalStateException.class, zookeeperTestServer::getConnectString); } } /** * Test calling getConnectString() after calling start on the service. */ @Test void testGetConnectString() throws Exception { // Create instance. try (final ZookeeperTestServer zookeeperTestServer = new ZookeeperTestServer()) { // Start service. zookeeperTestServer.start(); // Ask for the connect String final String connectString = zookeeperTestServer.getConnectString(); Assertions.assertNotNull(connectString); // Validate the zookeeper server appears to be functioning. testZookeeperConnection(connectString); } } /** * Attempts to validate a Zookeeper server by the following criteria. * - connecting * - create a new node * - reading that node * - closing connection * * @param zkConnectString Zookeeper host. */ private void testZookeeperConnection(final String zkConnectString) throws Exception { final String pathStr = "/zkTest" + System.currentTimeMillis(); final String dataStr = "zkData" + System.currentTimeMillis(); // Attempt to connect using a zookeeper client. ZooKeeper zkClient = null; try { zkClient = createZkClient(zkConnectString); // Create a new node storing dataBytes. writeZkString(zkClient, pathStr, dataStr); // Read back out of Zookeeper final String resultStr = readZkString(zkClient, pathStr); // Validate we got what we expected. Assertions.assertEquals(dataStr, resultStr); } finally { if (zkClient != null) { zkClient.close(); } } } /** * Helper method to create a zookeeper client. */ private ZooKeeper createZkClient(final String zkConnectString) throws IOException, InterruptedException { // Blocks until connected, or 5 sec timeout expires. final CountDownLatch connectionLatch = new CountDownLatch(1); // Attempt to connect using a zookeeper client. final ZooKeeper zkClient = new ZooKeeper(zkConnectString, 1000, event -> { if (event.getState() == Watcher.Event.KeeperState.SyncConnected) { connectionLatch.countDown(); } }); // Wait for connection. connectionLatch.await(5, TimeUnit.SECONDS); return zkClient; } /** * Helper method to write data to zookeeper. */ private void writeZkString( final ZooKeeper zkClient, final String path, final String data ) throws KeeperException, InterruptedException { // Create a new node storing dataBytes. zkClient.create(path, data.getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } /** * Helper method to read data from zookeeper. */ private String readZkString(final ZooKeeper zkClient, final String path) throws KeeperException, InterruptedException { return new String( zkClient.getData(path, null, null), StandardCharsets.UTF_8 ); } }