Java tutorial
package org.apache.helix; /* * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.I0Itec.zkclient.IDefaultNameSpace; import org.I0Itec.zkclient.ZkServer; import org.I0Itec.zkclient.exception.ZkNoNodeException; import org.apache.commons.io.FileUtils; import org.apache.helix.PropertyKey.Builder; import org.apache.helix.api.State; import org.apache.helix.api.id.MessageId; import org.apache.helix.api.id.PartitionId; import org.apache.helix.api.id.ResourceId; import org.apache.helix.api.id.StateModelDefId; import org.apache.helix.manager.zk.ZKHelixAdmin; import org.apache.helix.manager.zk.ZKHelixDataAccessor; import org.apache.helix.manager.zk.ZNRecordSerializer; import org.apache.helix.manager.zk.ZkBaseDataAccessor; import org.apache.helix.manager.zk.ZkCallbackHandler; import org.apache.helix.manager.zk.ZkClient; import org.apache.helix.model.CurrentState; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState.RebalanceMode; import org.apache.helix.model.Message; import org.apache.helix.model.Message.MessageType; import org.apache.helix.model.StateModelDefinition; import org.apache.helix.model.StateModelDefinition.StateModelDefinitionProperty; import org.apache.helix.store.zk.ZNode; import org.apache.helix.tools.ClusterSetup; import org.apache.helix.util.ZKClientPool; import org.apache.log4j.Logger; import org.apache.zookeeper.data.Stat; import org.testng.Assert; public class TestHelper { private static final Logger LOG = Logger.getLogger(TestHelper.class); static public ZkServer startZkServer(final String zkAddress) throws Exception { List<String> empty = Collections.emptyList(); return TestHelper.startZkServer(zkAddress, empty, true); } static public ZkServer startZkServer(final String zkAddress, final String rootNamespace) throws Exception { List<String> rootNamespaces = new ArrayList<String>(); rootNamespaces.add(rootNamespace); return TestHelper.startZkServer(zkAddress, rootNamespaces, true); } static public ZkServer startZkServer(final String zkAddress, final List<String> rootNamespaces) throws Exception { return startZkServer(zkAddress, rootNamespaces, true); } static public ZkServer startZkServer(final String zkAddress, final List<String> rootNamespaces, boolean overwrite) throws Exception { System.out.println("Start zookeeper at " + zkAddress + " in thread " + Thread.currentThread().getName()); String zkDir = zkAddress.replace(':', '_'); final String logDir = "/tmp/" + zkDir + "/logs"; final String dataDir = "/tmp/" + zkDir + "/dataDir"; if (overwrite) { FileUtils.deleteDirectory(new File(dataDir)); FileUtils.deleteDirectory(new File(logDir)); } ZKClientPool.reset(); IDefaultNameSpace defaultNameSpace = new IDefaultNameSpace() { @Override public void createDefaultNameSpace(org.I0Itec.zkclient.ZkClient zkClient) { if (rootNamespaces == null) { return; } for (String rootNamespace : rootNamespaces) { try { zkClient.deleteRecursive(rootNamespace); } catch (Exception e) { LOG.error("fail to deleteRecursive path:" + rootNamespace, e); } } } }; int port = Integer.parseInt(zkAddress.substring(zkAddress.lastIndexOf(':') + 1)); ZkServer zkServer = new ZkServer(dataDir, logDir, defaultNameSpace, port); zkServer.start(); return zkServer; } static public void stopZkServer(ZkServer zkServer) { if (zkServer != null) { zkServer.shutdown(); System.out.println("Shut down zookeeper at port " + zkServer.getPort() + " in thread " + Thread.currentThread().getName()); } } public static void setupEmptyCluster(ZkClient zkClient, String clusterName) { ZKHelixAdmin admin = new ZKHelixAdmin(zkClient); admin.addCluster(clusterName, true); } /** * convert T[] to set<T> * @param s * @return */ public static <T> Set<T> setOf(T... s) { Set<T> set = new HashSet<T>(Arrays.asList(s)); return set; } /** * generic method for verification with a timeout * @param verifierName * @param args */ public static void verifyWithTimeout(String verifierName, long timeout, Object... args) { final long sleepInterval = 1000; // in ms final int loop = (int) (timeout / sleepInterval) + 1; try { boolean result = false; int i = 0; for (; i < loop; i++) { Thread.sleep(sleepInterval); // verifier should be static method result = (Boolean) TestHelper.getMethod(verifierName).invoke(null, args); if (result == true) { break; } } // debug // LOG.info(verifierName + ": wait " + ((i + 1) * 1000) + "ms to verify (" // + result + ")"); System.err.println(verifierName + ": wait " + ((i + 1) * 1000) + "ms to verify " + " (" + result + ")"); LOG.debug("args:" + Arrays.toString(args)); // System.err.println("args:" + Arrays.toString(args)); if (result == false) { LOG.error(verifierName + " fails"); LOG.error("args:" + Arrays.toString(args)); } Assert.assertTrue(result); } catch (Exception e) { LOG.error("Exception in verify: " + verifierName, e); } } private static Method getMethod(String name) { Method[] methods = TestHelper.class.getMethods(); for (Method method : methods) { if (name.equals(method.getName())) { return method; } } return null; } public static boolean verifyEmptyCurStateAndExtView(String clusterName, String resourceName, Set<String> instanceNames, String zkAddr) { ZkClient zkClient = new ZkClient(zkAddr); zkClient.setZkSerializer(new ZNRecordSerializer()); try { ZKHelixDataAccessor accessor = new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(zkClient)); Builder keyBuilder = accessor.keyBuilder(); for (String instanceName : instanceNames) { List<String> sessionIds = accessor.getChildNames(keyBuilder.sessions(instanceName)); for (String sessionId : sessionIds) { CurrentState curState = accessor .getProperty(keyBuilder.currentState(instanceName, sessionId, resourceName)); if (curState != null && curState.getRecord().getMapFields().size() != 0) { return false; } } ExternalView extView = accessor.getProperty(keyBuilder.externalView(resourceName)); if (extView != null && extView.getRecord().getMapFields().size() != 0) { return false; } } return true; } finally { zkClient.close(); } } public static boolean verifyNotConnected(HelixManager manager) { return !manager.isConnected(); } public static void setupCluster(String clusterName, String zkAddr, int startPort, String participantNamePrefix, String resourceNamePrefix, int resourceNb, int partitionNb, int nodesNb, int replica, String stateModelDef, boolean doRebalance) throws Exception { TestHelper.setupCluster(clusterName, zkAddr, startPort, participantNamePrefix, resourceNamePrefix, resourceNb, partitionNb, nodesNb, replica, stateModelDef, RebalanceMode.SEMI_AUTO, doRebalance); } public static void setupCluster(String clusterName, String ZkAddr, int startPort, String participantNamePrefix, String resourceNamePrefix, int resourceNb, int partitionNb, int nodesNb, int replica, String stateModelDef, RebalanceMode mode, boolean doRebalance) throws Exception { ZkClient zkClient = new ZkClient(ZkAddr); if (zkClient.exists("/" + clusterName)) { LOG.warn("Cluster already exists:" + clusterName + ". Deleting it"); zkClient.deleteRecursive("/" + clusterName); } ClusterSetup setupTool = new ClusterSetup(ZkAddr); setupTool.addCluster(clusterName, true); for (int i = 0; i < nodesNb; i++) { int port = startPort + i; setupTool.addInstanceToCluster(clusterName, participantNamePrefix + "_" + port); } for (int i = 0; i < resourceNb; i++) { String resourceName = resourceNamePrefix + i; setupTool.addResourceToCluster(clusterName, resourceName, partitionNb, stateModelDef, mode.toString()); if (doRebalance) { setupTool.rebalanceStorageCluster(clusterName, resourceName, replica); } } zkClient.close(); } /** * @param stateMap * : "ResourceName/partitionKey" -> setOf(instances) * @param state * : MASTER|SLAVE|ERROR... */ public static void verifyState(String clusterName, String zkAddr, Map<String, Set<String>> stateMap, String state) { ZkClient zkClient = new ZkClient(zkAddr); zkClient.setZkSerializer(new ZNRecordSerializer()); try { ZKHelixDataAccessor accessor = new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(zkClient)); Builder keyBuilder = accessor.keyBuilder(); for (String resGroupPartitionKey : stateMap.keySet()) { Map<String, String> retMap = getResourceAndPartitionKey(resGroupPartitionKey); String resGroup = retMap.get("RESOURCE"); String partitionKey = retMap.get("PARTITION"); ExternalView extView = accessor.getProperty(keyBuilder.externalView(resGroup)); for (String instance : stateMap.get(resGroupPartitionKey)) { String actualState = extView.getStateMap(partitionKey).get(instance); Assert.assertNotNull(actualState, "externalView doesn't contain state for " + resGroup + "/" + partitionKey + " on " + instance + " (expect " + state + ")"); Assert.assertEquals(actualState, state, "externalView for " + resGroup + "/" + partitionKey + " on " + instance + " is " + actualState + " (expect " + state + ")"); } } } finally { zkClient.close(); } } /** * @param resourcePartition * : key is in form of "resource/partitionKey" or "resource_x" * @return */ private static Map<String, String> getResourceAndPartitionKey(String resourcePartition) { String resourceName; String partitionName; int idx = resourcePartition.indexOf('/'); if (idx > -1) { resourceName = resourcePartition.substring(0, idx); partitionName = resourcePartition.substring(idx + 1); } else { idx = resourcePartition.lastIndexOf('_'); resourceName = resourcePartition.substring(0, idx); partitionName = resourcePartition; } Map<String, String> retMap = new HashMap<String, String>(); retMap.put("RESOURCE", resourceName); retMap.put("PARTITION", partitionName); return retMap; } public static <T> Map<String, T> startThreadsConcurrently(final int nrThreads, final Callable<T> method, final long timeout) { final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishCounter = new CountDownLatch(nrThreads); final Map<String, T> resultsMap = new ConcurrentHashMap<String, T>(); final List<Thread> threadList = new ArrayList<Thread>(); for (int i = 0; i < nrThreads; i++) { Thread thread = new Thread() { @Override public void run() { try { boolean isTimeout = !startLatch.await(timeout, TimeUnit.SECONDS); if (isTimeout) { LOG.error("Timeout while waiting for start latch"); } } catch (InterruptedException ex) { LOG.error("Interrupted while waiting for start latch"); } try { T result = method.call(); if (result != null) { resultsMap.put("thread_" + this.getId(), result); } LOG.debug("result=" + result); } catch (Exception e) { LOG.error("Exeption in executing " + method.getClass().getName(), e); } finishCounter.countDown(); } }; threadList.add(thread); thread.start(); } startLatch.countDown(); // wait for all thread to complete try { boolean isTimeout = !finishCounter.await(timeout, TimeUnit.SECONDS); if (isTimeout) { LOG.error("Timeout while waiting for finish latch. Interrupt all threads"); for (Thread thread : threadList) { thread.interrupt(); } } } catch (InterruptedException e) { LOG.error("Interrupted while waiting for finish latch", e); } return resultsMap; } public static Message createMessage(MessageId msgId, String fromState, String toState, String tgtName, String resourceName, String partitionName) { Message msg = new Message(MessageType.STATE_TRANSITION, msgId); msg.setFromState(State.from(fromState)); msg.setToState(State.from(toState)); msg.setTgtName(tgtName); msg.setResourceId(ResourceId.from(resourceName)); msg.setPartitionId(PartitionId.from(partitionName)); msg.setStateModelDef(StateModelDefId.from("MasterSlave")); return msg; } public static String getTestMethodName() { StackTraceElement[] calls = Thread.currentThread().getStackTrace(); return calls[2].getMethodName(); } public static String getTestClassName() { StackTraceElement[] calls = Thread.currentThread().getStackTrace(); String fullClassName = calls[2].getClassName(); return fullClassName.substring(fullClassName.lastIndexOf('.') + 1); } public static <T> Map<String, T> startThreadsConcurrently(final List<Callable<T>> methods, final long timeout) { final int nrThreads = methods.size(); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishCounter = new CountDownLatch(nrThreads); final Map<String, T> resultsMap = new ConcurrentHashMap<String, T>(); final List<Thread> threadList = new ArrayList<Thread>(); for (int i = 0; i < nrThreads; i++) { final Callable<T> method = methods.get(i); Thread thread = new Thread() { @Override public void run() { try { boolean isTimeout = !startLatch.await(timeout, TimeUnit.SECONDS); if (isTimeout) { LOG.error("Timeout while waiting for start latch"); } } catch (InterruptedException ex) { LOG.error("Interrupted while waiting for start latch"); } try { T result = method.call(); if (result != null) { resultsMap.put("thread_" + this.getId(), result); } LOG.debug("result=" + result); } catch (Exception e) { LOG.error("Exeption in executing " + method.getClass().getName(), e); } finishCounter.countDown(); } }; threadList.add(thread); thread.start(); } startLatch.countDown(); // wait for all thread to complete try { boolean isTimeout = !finishCounter.await(timeout, TimeUnit.SECONDS); if (isTimeout) { LOG.error("Timeout while waiting for finish latch. Interrupt all threads"); for (Thread thread : threadList) { thread.interrupt(); } } } catch (InterruptedException e) { LOG.error("Interrupted while waiting for finish latch", e); } return resultsMap; } public static void printCache(Map<String, ZNode> cache) { System.out.println("START:Print cache"); TreeMap<String, ZNode> map = new TreeMap<String, ZNode>(); map.putAll(cache); for (String key : map.keySet()) { ZNode node = map.get(key); TreeSet<String> childSet = new TreeSet<String>(); childSet.addAll(node.getChildSet()); System.out.print(key + "=" + node.getData() + ", " + childSet + ", " + (node.getStat() == null ? "null\n" : node.getStat())); } System.out.println("END:Print cache"); } public static void readZkRecursive(String path, Map<String, ZNode> map, ZkClient zkclient) { try { Stat stat = new Stat(); ZNRecord record = zkclient.readData(path, stat); List<String> childNames = zkclient.getChildren(path); ZNode node = new ZNode(path, record, stat); node.addChildren(childNames); map.put(path, node); for (String childName : childNames) { String childPath = path + "/" + childName; readZkRecursive(childPath, map, zkclient); } } catch (ZkNoNodeException e) { // OK } } public static void readZkRecursive(String path, Map<String, ZNode> map, BaseDataAccessor<ZNRecord> zkAccessor) { try { Stat stat = new Stat(); ZNRecord record = zkAccessor.get(path, stat, 0); List<String> childNames = zkAccessor.getChildNames(path, 0); // System.out.println("childNames: " + childNames); ZNode node = new ZNode(path, record, stat); node.addChildren(childNames); map.put(path, node); if (childNames != null && !childNames.isEmpty()) { for (String childName : childNames) { String childPath = path + "/" + childName; readZkRecursive(childPath, map, zkAccessor); } } } catch (ZkNoNodeException e) { // OK } } public static boolean verifyZkCache(List<String> paths, BaseDataAccessor<ZNRecord> zkAccessor, ZkClient zkclient, boolean needVerifyStat) { // read everything Map<String, ZNode> zkMap = new HashMap<String, ZNode>(); Map<String, ZNode> cache = new HashMap<String, ZNode>(); for (String path : paths) { readZkRecursive(path, zkMap, zkclient); readZkRecursive(path, cache, zkAccessor); } // printCache(map); return verifyZkCache(paths, null, cache, zkMap, needVerifyStat); } public static boolean verifyZkCache(List<String> paths, Map<String, ZNode> cache, ZkClient zkclient, boolean needVerifyStat) { return verifyZkCache(paths, null, cache, zkclient, needVerifyStat); } public static boolean verifyZkCache(List<String> paths, List<String> pathsExcludeForStat, Map<String, ZNode> cache, ZkClient zkclient, boolean needVerifyStat) { // read everything on zk under paths Map<String, ZNode> zkMap = new HashMap<String, ZNode>(); for (String path : paths) { readZkRecursive(path, zkMap, zkclient); } // printCache(map); return verifyZkCache(paths, pathsExcludeForStat, cache, zkMap, needVerifyStat); } public static boolean verifyZkCache(List<String> paths, List<String> pathsExcludeForStat, Map<String, ZNode> cache, Map<String, ZNode> zkMap, boolean needVerifyStat) { // equal size if (zkMap.size() != cache.size()) { System.err.println("size mismatch: cacheSize: " + cache.size() + ", zkMapSize: " + zkMap.size()); System.out.println("cache: (" + cache.size() + ")"); TestHelper.printCache(cache); System.out.println("zkMap: (" + zkMap.size() + ")"); TestHelper.printCache(zkMap); return false; } // everything in cache is also in map for (String path : cache.keySet()) { ZNode cacheNode = cache.get(path); ZNode zkNode = zkMap.get(path); if (zkNode == null) { // in cache but not on zk System.err.println("path: " + path + " in cache but not on zk: inCacheNode: " + cacheNode); return false; } if ((zkNode.getData() == null && cacheNode.getData() != null) || (zkNode.getData() != null && cacheNode.getData() == null) || (zkNode.getData() != null && cacheNode.getData() != null && !zkNode.getData().equals(cacheNode.getData()))) { // data not equal System.err.println("data mismatch on path: " + path + ", inCache: " + cacheNode.getData() + ", onZk: " + zkNode.getData()); return false; } if ((zkNode.getChildSet() == null && cacheNode.getChildSet() != null) || (zkNode.getChildSet() != null && cacheNode.getChildSet() == null) || (zkNode.getChildSet() != null && cacheNode.getChildSet() != null && !zkNode.getChildSet().equals(cacheNode.getChildSet()))) { // childSet not equal System.err.println("childSet mismatch on path: " + path + ", inCache: " + cacheNode.getChildSet() + ", onZk: " + zkNode.getChildSet()); return false; } if (needVerifyStat && pathsExcludeForStat != null && !pathsExcludeForStat.contains(path)) { if (cacheNode.getStat() == null || !zkNode.getStat().equals(cacheNode.getStat())) { // stat not equal System.err.println("Stat mismatch on path: " + path + ", inCache: " + cacheNode.getStat() + ", onZk: " + zkNode.getStat()); return false; } } } return true; } public static StateModelDefinition generateStateModelDefForBootstrap() { ZNRecord record = new ZNRecord("Bootstrap"); record.setSimpleField(StateModelDefinitionProperty.INITIAL_STATE.toString(), "IDLE"); List<String> statePriorityList = new ArrayList<String>(); statePriorityList.add("ONLINE"); statePriorityList.add("BOOTSTRAP"); statePriorityList.add("OFFLINE"); statePriorityList.add("IDLE"); statePriorityList.add("DROPPED"); statePriorityList.add("ERROR"); record.setListField(StateModelDefinitionProperty.STATE_PRIORITY_LIST.toString(), statePriorityList); for (String state : statePriorityList) { String key = state + ".meta"; Map<String, String> metadata = new HashMap<String, String>(); if (state.equals("ONLINE")) { metadata.put("count", "R"); record.setMapField(key, metadata); } else if (state.equals("BOOTSTRAP")) { metadata.put("count", "-1"); record.setMapField(key, metadata); } else if (state.equals("OFFLINE")) { metadata.put("count", "-1"); record.setMapField(key, metadata); } else if (state.equals("IDLE")) { metadata.put("count", "-1"); record.setMapField(key, metadata); } else if (state.equals("DROPPED")) { metadata.put("count", "-1"); record.setMapField(key, metadata); } else if (state.equals("ERROR")) { metadata.put("count", "-1"); record.setMapField(key, metadata); } } for (String state : statePriorityList) { String key = state + ".next"; if (state.equals("ONLINE")) { Map<String, String> metadata = new HashMap<String, String>(); metadata.put("BOOTSTRAP", "OFFLINE"); metadata.put("OFFLINE", "OFFLINE"); metadata.put("DROPPED", "OFFLINE"); metadata.put("IDLE", "OFFLINE"); record.setMapField(key, metadata); } else if (state.equals("BOOTSTRAP")) { Map<String, String> metadata = new HashMap<String, String>(); metadata.put("ONLINE", "ONLINE"); metadata.put("OFFLINE", "OFFLINE"); metadata.put("DROPPED", "OFFLINE"); metadata.put("IDLE", "OFFLINE"); record.setMapField(key, metadata); } else if (state.equals("OFFLINE")) { Map<String, String> metadata = new HashMap<String, String>(); metadata.put("ONLINE", "BOOTSTRAP"); metadata.put("BOOTSTRAP", "BOOTSTRAP"); metadata.put("DROPPED", "IDLE"); metadata.put("IDLE", "IDLE"); record.setMapField(key, metadata); } else if (state.equals("IDLE")) { Map<String, String> metadata = new HashMap<String, String>(); metadata.put("ONLINE", "OFFLINE"); metadata.put("BOOTSTRAP", "OFFLINE"); metadata.put("OFFLINE", "OFFLINE"); metadata.put("DROPPED", "DROPPED"); record.setMapField(key, metadata); } else if (state.equals("ERROR")) { Map<String, String> metadata = new HashMap<String, String>(); metadata.put("IDLE", "IDLE"); record.setMapField(key, metadata); } } List<String> stateTransitionPriorityList = new ArrayList<String>(); stateTransitionPriorityList.add("ONLINE-OFFLINE"); stateTransitionPriorityList.add("BOOTSTRAP-ONLINE"); stateTransitionPriorityList.add("OFFLINE-BOOTSTRAP"); stateTransitionPriorityList.add("BOOTSTRAP-OFFLINE"); stateTransitionPriorityList.add("OFFLINE-IDLE"); stateTransitionPriorityList.add("IDLE-OFFLINE"); stateTransitionPriorityList.add("IDLE-DROPPED"); stateTransitionPriorityList.add("ERROR-IDLE"); record.setListField(StateModelDefinitionProperty.STATE_TRANSITION_PRIORITYLIST.toString(), stateTransitionPriorityList); return new StateModelDefinition(record); } public static String znrecordToString(ZNRecord record) { StringBuffer sb = new StringBuffer(); sb.append(record.getId() + "\n"); Map<String, String> simpleFields = record.getSimpleFields(); if (simpleFields != null) { sb.append("simpleFields\n"); for (String key : simpleFields.keySet()) { sb.append(" " + key + "\t: " + simpleFields.get(key) + "\n"); } } Map<String, List<String>> listFields = record.getListFields(); sb.append("listFields\n"); for (String key : listFields.keySet()) { List<String> list = listFields.get(key); sb.append(" " + key + "\t: "); for (String listValue : list) { sb.append(listValue + ", "); } sb.append("\n"); } Map<String, Map<String, String>> mapFields = record.getMapFields(); sb.append("mapFields\n"); for (String key : mapFields.keySet()) { Map<String, String> map = mapFields.get(key); sb.append(" " + key + "\t: \n"); for (String mapKey : map.keySet()) { sb.append(" " + mapKey + "\t: " + map.get(mapKey) + "\n"); } } return sb.toString(); } public static interface Verifier { boolean verify() throws Exception; } public static boolean verify(Verifier verifier, long timeout) throws Exception { long start = System.currentTimeMillis(); do { boolean result = verifier.verify(); if (result || (System.currentTimeMillis() - start) > timeout) { return result; } Thread.sleep(100); } while (true); } public static void printHandlers(HelixManager manager, List<ZkCallbackHandler> handlers) { StringBuilder sb = new StringBuilder(); sb.append(manager.getInstanceName() + " has " + handlers.size() + " cb-handlers. [\n"); for (int i = 0; i < handlers.size(); i++) { ZkCallbackHandler handler = handlers.get(i); String path = handler.getPath(); sb.append(path.substring(manager.getClusterName().length() + 1) + ": " + handler.getListener()); if (i < (handlers.size() - 1)) { sb.append("\n"); } } sb.append("]"); System.out.println(sb.toString()); } public static int getRandomPort() throws IOException { ServerSocket sock = new ServerSocket(); sock.bind(null); int port = sock.getLocalPort(); sock.close(); return port; } }