// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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. // ================================================================================================= package com.twitter.common.zookeeper.testing; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import com.google.common.base.Preconditions; import org.apache.zookeeper.server.NIOServerCnxn; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.ZooKeeperServer.BasicDataTreeBuilder; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; import com.twitter.common.application.ShutdownRegistry; import com.twitter.common.base.Command; import com.twitter.common.base.ExceptionalCommand; import com.twitter.common.io.FileUtils; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Time; import com.twitter.common.zookeeper.ZooKeeperClient; import com.twitter.common.zookeeper.ZooKeeperClient.Credentials; /** * A helper class for starting in-process ZooKeeper server and clients. * * <p>This is ONLY meant to be used for testing. */ public class ZooKeeperTestServer { /** * The default session timeout for clients created by servers constructed with * {@link #ZooKeeperTestServer(int, ShutdownRegistry)}. */ public static final Amount<Integer, Time> DEFAULT_SESSION_TIMEOUT = Amount.of(100, Time.MILLISECONDS); protected final ZooKeeperServer zooKeeperServer; private final ShutdownRegistry shutdownRegistry; private NIOServerCnxn.Factory connectionFactory; private int port; private final Amount<Integer, Time> defaultSessionTimeout; /** * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port * @param shutdownRegistry a registry that will be used to register client and server shutdown * commands. It is up to the caller to execute the registered actions at an appropriate time. * @throws IOException if there was aproblem creating the server's database */ public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry) throws IOException { this(port, shutdownRegistry, DEFAULT_SESSION_TIMEOUT); } /** * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port * @param shutdownRegistry a registry that will be used to register client and server shutdown * commands. It is up to the caller to execute the registered actions at an appropriate time. * @param defaultSessionTimeout the default session timeout for clients created with * {@link #createClient()}. * @throws IOException if there was aproblem creating the server's database */ public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry, Amount<Integer, Time> defaultSessionTimeout) throws IOException { Preconditions.checkArgument(0 <= port && port <= 0xFFFF); this.port = port; this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry); this.defaultSessionTimeout = Preconditions.checkNotNull(defaultSessionTimeout); zooKeeperServer = new ZooKeeperServer(new FileTxnSnapLog(createTempDir(), createTempDir()), new BasicDataTreeBuilder()); } /** * Starts zookeeper up on the configured port. If the configured port is the ephemeral port * (@{code 0}), then the actual chosen port is returned. */ public final int startNetwork() throws IOException, InterruptedException { connectionFactory = new NIOServerCnxn.Factory(new InetSocketAddress(port)); connectionFactory.startup(zooKeeperServer); shutdownRegistry.addAction(new Command() { @Override public void execute() { shutdownNetwork(); } }); port = zooKeeperServer.getClientPort(); return port; } /** * Starts zookeeper back up on the last used port. */ public final void restartNetwork() throws IOException, InterruptedException { checkEphemeralPortAssigned(); Preconditions.checkState(!connectionFactory.isAlive()); startNetwork(); } /** * Shuts down the in-process zookeeper network server. */ public final void shutdownNetwork() { if (connectionFactory != null && connectionFactory.isAlive()) { connectionFactory.shutdown(); } } /** * Expires the active session for the given client. The client should be one returned from * {@link #createClient}. * * @param zkClient the client to expire * @throws ZooKeeperClient.ZooKeeperConnectionException if a problem is encountered connecting to * the local zk server while trying to expire the session * @throws InterruptedException if interrupted while requesting expiration */ public final void expireClientSession(ZooKeeperClient zkClient) throws ZooKeeperClient.ZooKeeperConnectionException, InterruptedException { zooKeeperServer.closeSession(zkClient.get().getSessionId()); } /** * Returns the current port to connect to the in-process zookeeper instance. */ public final int getPort() { checkEphemeralPortAssigned(); return port; } /** * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server * with the default session timeout. */ public final ZooKeeperClient createClient() { return createClient(defaultSessionTimeout); } /** * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with * the default session timeout. */ public final ZooKeeperClient createClient(Credentials credentials) { return createClient(defaultSessionTimeout, credentials); } /** * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server * with a custom {@code sessionTimeout}. */ public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout) { return createClient(sessionTimeout, Credentials.NONE); } /** * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with * a custom {@code sessionTimeout}. */ public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout, Credentials credentials) { final ZooKeeperClient client = new ZooKeeperClient(sessionTimeout, credentials, InetSocketAddress.createUnresolved("127.0.0.1", port)); shutdownRegistry.addAction(new ExceptionalCommand<InterruptedException>() { @Override public void execute() { client.close(); } }); return client; } private void checkEphemeralPortAssigned() { Preconditions.checkState(port > 0, "startNetwork must be called first"); } private File createTempDir() { final File tempDir = FileUtils.createTempDir(); shutdownRegistry.addAction(new ExceptionalCommand<IOException>() { @Override public void execute() throws IOException { org.apache.commons.io.FileUtils.deleteDirectory(tempDir); } }); return tempDir; } }