/**
* 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.
*/
package org.apache.zookeeper.server;
import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.PortAssignment;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZKTestCase;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.common.PathUtils;
import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
import org.apache.zookeeper.test.ClientBase;
import org.junit.Assert;
import org.junit.Test;
/**
* Test stand-alone server.
*
*/
public class ZooKeeperServerMainTest extends ZKTestCase implements Watcher {
protected static final Logger LOG =
LoggerFactory.getLogger(ZooKeeperServerMainTest.class);
private CountDownLatch clientConnected = new CountDownLatch(1);
public static class MainThread extends Thread {
final File confFile;
final TestZKSMain main;
final File tmpDir;
final File dataDir;
final File logDir;
public MainThread(int clientPort, boolean preCreateDirs, String configs)
throws IOException {
this(clientPort, preCreateDirs, ClientBase.createTmpDir(), configs);
}
public MainThread(int clientPort, boolean preCreateDirs, File tmpDir, String configs)
throws IOException {
super("Standalone server with clientPort:" + clientPort);
this.tmpDir = tmpDir;
confFile = new File(tmpDir, "zoo.cfg");
FileWriter fwriter = new FileWriter(confFile);
fwriter.write("tickTime=2000\n");
fwriter.write("initLimit=10\n");
fwriter.write("syncLimit=5\n");
if(configs != null){
fwriter.write(configs);
}
dataDir = new File(this.tmpDir, "data");
logDir = new File(dataDir.toString() + "_txnlog");
if (preCreateDirs) {
if (!dataDir.mkdir()) {
throw new IOException("unable to mkdir " + dataDir);
}
if (!logDir.mkdir()) {
throw new IOException("unable to mkdir " + logDir);
}
ClientBase.createInitializeFile(logDir);
}
String normalizedDataDir = PathUtils.normalizeFileSystemPath(dataDir.toString());
String normalizedLogDir = PathUtils.normalizeFileSystemPath(logDir.toString());
fwriter.write("dataDir=" + normalizedDataDir + "\n");
fwriter.write("dataLogDir=" + normalizedLogDir + "\n");
fwriter.write("clientPort=" + clientPort + "\n");
fwriter.flush();
fwriter.close();
main = new TestZKSMain();
}
public void run() {
String args[] = new String[1];
args[0] = confFile.toString();
try {
main.initializeAndRun(args);
} catch (Exception e) {
// test will still fail even though we just log/ignore
LOG.error("unexpected exception in run", e);
}
}
public void shutdown() throws IOException {
main.shutdown();
}
void deleteDirs() throws IOException{
delete(tmpDir);
}
void delete(File f) throws IOException {
if (f.isDirectory()) {
for (File c : f.listFiles())
delete(c);
}
if (!f.delete())
// double check for the file existence
if (f.exists()) {
throw new IOException("Failed to delete file: " + f);
}
}
ServerCnxnFactory getCnxnFactory() {
return main.getCnxnFactory();
}
}
public static class TestZKSMain extends ZooKeeperServerMain {
public void shutdown() {
super.shutdown();
}
}
/**
* Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2247.
* Test to verify that even after non recoverable error (error while
* writing transaction log), ZooKeeper is still available.
*/
@Test(timeout = 30000)
public void testNonRecoverableError() throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
MainThread main = new MainThread(CLIENT_PORT, true, null);
main.start();
Assert.assertTrue("waiting for server being up",
ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT));
ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT,
ClientBase.CONNECTION_TIMEOUT, this);
zk.create("/foo1", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
Assert.assertEquals(new String(zk.getData("/foo1", null, null)), "foobar");
// inject problem in server
ZooKeeperServer zooKeeperServer = main.getCnxnFactory()
.getZooKeeperServer();
FileTxnSnapLog snapLog = zooKeeperServer.getTxnLogFactory();
FileTxnSnapLog fileTxnSnapLogWithError = new FileTxnSnapLog(
snapLog.getDataDir(), snapLog.getSnapDir()) {
@Override
public void commit() throws IOException {
throw new IOException("Input/output error");
}
};
ZKDatabase newDB = new ZKDatabase(fileTxnSnapLogWithError);
zooKeeperServer.setZKDatabase(newDB);
try {
// do create operation, so that injected IOException is thrown
zk.create("/foo2", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
fail("IOException is expected as error is injected in transaction log commit funtionality");
} catch (Exception e) {
// do nothing
}
zk.close();
Assert.assertTrue("waiting for server down",
ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT,
ClientBase.CONNECTION_TIMEOUT));
fileTxnSnapLogWithError.close();
main.shutdown();
main.deleteDirs();
}
/**
* Tests that the ZooKeeper server will fail to start if the
* snapshot directory is read only.
*
* This test will fail if it is executed as root user.
*/
@Test(timeout = 30000)
public void testReadOnlySnapshotDir() throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
// Start up the ZK server to automatically create the necessary directories
// and capture the directory where data is stored
MainThread main = new MainThread(CLIENT_PORT, true, null);
File tmpDir = main.tmpDir;
main.start();
Assert.assertTrue("waiting for server being up", ClientBase
.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT / 2));
main.shutdown();
// Make the snapshot directory read only
File snapDir = new File(main.dataDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION);
snapDir.setWritable(false);
// Restart ZK and observe a failure
main = new MainThread(CLIENT_PORT, false, tmpDir, null);
main.start();
Assert.assertFalse("waiting for server being up", ClientBase
.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT / 2));
main.shutdown();
snapDir.setWritable(true);
main.deleteDirs();
}
/**
* Tests that the ZooKeeper server will fail to start if the
* transaction log directory is read only.
*
* This test will fail if it is executed as root user.
*/
@Test(timeout = 30000)
public void testReadOnlyTxnLogDir() throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
// Start up the ZK server to automatically create the necessary directories
// and capture the directory where data is stored
MainThread main = new MainThread(CLIENT_PORT, true, null);
File tmpDir = main.tmpDir;
main.start();
Assert.assertTrue("waiting for server being up", ClientBase
.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT / 2));
main.shutdown();
// Make the transaction log directory read only
File logDir = new File(main.logDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION);
logDir.setWritable(false);
// Restart ZK and observe a failure
main = new MainThread(CLIENT_PORT, false, tmpDir, null);
main.start();
Assert.assertFalse("waiting for server being up", ClientBase
.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT / 2));
main.shutdown();
logDir.setWritable(true);
main.deleteDirs();
}
/**
* Verify the ability to start a standalone server instance.
*/
@Test
public void testStandalone() throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
MainThread main = new MainThread(CLIENT_PORT, true, null);
main.start();
Assert.assertTrue("waiting for server being up",
ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT));
clientConnected = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT,
ClientBase.CONNECTION_TIMEOUT, this);
Assert.assertTrue("Failed to establish zkclient connection!",
clientConnected.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS));
zk.create("/foo", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
Assert.assertEquals(new String(zk.getData("/foo", null, null)), "foobar");
zk.close();
main.shutdown();
main.join();
main.deleteDirs();
Assert.assertTrue("waiting for server down",
ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT,
ClientBase.CONNECTION_TIMEOUT));
}
/**
* Test verifies that the server shouldn't allow minsessiontimeout >
* maxsessiontimeout
*/
@Test
public void testWithMinSessionTimeoutGreaterThanMaxSessionTimeout()
throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
final int tickTime = 2000;
final int minSessionTimeout = 20 * tickTime + 1000; // min is higher
final int maxSessionTimeout = tickTime * 2 - 100; // max is lower
final String configs = "maxSessionTimeout=" + maxSessionTimeout + "\n"
+ "minSessionTimeout=" + minSessionTimeout + "\n";
MainThread main = new MainThread(CLIENT_PORT, true, configs);
String args[] = new String[1];
args[0] = main.confFile.toString();
try {
main.main.initializeAndRun(args);
Assert.fail("Must throw exception as "
+ "minsessiontimeout > maxsessiontimeout");
} catch (ConfigException iae) {
// expected
}
}
/**
* Test verifies that the server is able to redefine if user configured only
* minSessionTimeout limit
*/
@Test
public void testWithOnlyMinSessionTimeout() throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
final int tickTime = 2000;
final int minSessionTimeout = tickTime * 2 - 100;
int maxSessionTimeout = 20 * tickTime;
final String configs = "minSessionTimeout=" + minSessionTimeout + "\n";
MainThread main = new MainThread(CLIENT_PORT, true, configs);
main.start();
String HOSTPORT = "127.0.0.1:" + CLIENT_PORT;
Assert.assertTrue("waiting for server being up",
ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT));
// create session with min value
verifySessionTimeOut(minSessionTimeout, minSessionTimeout, HOSTPORT);
verifySessionTimeOut(minSessionTimeout - 2000, minSessionTimeout,
HOSTPORT);
// create session with max value
verifySessionTimeOut(maxSessionTimeout, maxSessionTimeout, HOSTPORT);
verifySessionTimeOut(maxSessionTimeout + 2000, maxSessionTimeout,
HOSTPORT);
main.shutdown();
Assert.assertTrue("waiting for server down", ClientBase
.waitForServerDown(HOSTPORT, ClientBase.CONNECTION_TIMEOUT));
}
/**
* Test verifies that the server is able to redefine the min/max session
* timeouts
*/
@Test
public void testMinMaxSessionTimeOut() throws Exception {
ClientBase.setupTestEnv();
final int CLIENT_PORT = PortAssignment.unique();
final int tickTime = 2000;
final int minSessionTimeout = tickTime * 2 - 100;
final int maxSessionTimeout = 20 * tickTime + 1000;
final String configs = "maxSessionTimeout=" + maxSessionTimeout + "\n"
+ "minSessionTimeout=" + minSessionTimeout + "\n";
MainThread main = new MainThread(CLIENT_PORT, true, configs);
main.start();
String HOSTPORT = "127.0.0.1:" + CLIENT_PORT;
Assert.assertTrue("waiting for server being up",
ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT));
// create session with min value
verifySessionTimeOut(minSessionTimeout, minSessionTimeout, HOSTPORT);
verifySessionTimeOut(minSessionTimeout - 2000, minSessionTimeout,
HOSTPORT);
// create session with max value
verifySessionTimeOut(maxSessionTimeout, maxSessionTimeout, HOSTPORT);
verifySessionTimeOut(maxSessionTimeout + 2000, maxSessionTimeout,
HOSTPORT);
main.shutdown();
Assert.assertTrue("waiting for server down", ClientBase
.waitForServerDown(HOSTPORT, ClientBase.CONNECTION_TIMEOUT));
}
private void verifySessionTimeOut(int sessionTimeout,
int expectedSessionTimeout, String HOSTPORT) throws IOException,
KeeperException, InterruptedException {
clientConnected = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper(HOSTPORT, sessionTimeout, this);
Assert.assertTrue("Failed to establish zkclient connection!",
clientConnected.await(sessionTimeout, TimeUnit.MILLISECONDS));
Assert.assertEquals("Not able to configure the sessionTimeout values",
expectedSessionTimeout, zk.getSessionTimeout());
zk.close();
}
@Test
public void testJMXRegistrationWithNIO() throws Exception {
ClientBase.setupTestEnv();
File tmpDir_1 = ClientBase.createTmpDir();
ServerCnxnFactory server_1 = startServer(tmpDir_1);
File tmpDir_2 = ClientBase.createTmpDir();
ServerCnxnFactory server_2 = startServer(tmpDir_2);
server_1.shutdown();
server_2.shutdown();
deleteFile(tmpDir_1);
deleteFile(tmpDir_2);
}
@Test
public void testJMXRegistrationWithNetty() throws Exception {
String originalServerCnxnFactory = System
.getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY,
NettyServerCnxnFactory.class.getName());
try {
ClientBase.setupTestEnv();
File tmpDir_1 = ClientBase.createTmpDir();
ServerCnxnFactory server_1 = startServer(tmpDir_1);
File tmpDir_2 = ClientBase.createTmpDir();
ServerCnxnFactory server_2 = startServer(tmpDir_2);
server_1.shutdown();
server_2.shutdown();
deleteFile(tmpDir_1);
deleteFile(tmpDir_2);
} finally {
// setting back
if (originalServerCnxnFactory == null
|| originalServerCnxnFactory.isEmpty()) {
System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
} else {
System.setProperty(
ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY,
originalServerCnxnFactory);
}
}
}
private void deleteFile(File f) throws IOException {
if (f.isDirectory()) {
for (File c : f.listFiles())
deleteFile(c);
}
if (!f.delete())
// double check for the file existence
if (f.exists()) {
throw new IOException("Failed to delete file: " + f);
}
}
private ServerCnxnFactory startServer(File tmpDir) throws IOException,
InterruptedException {
final int CLIENT_PORT = PortAssignment.unique();
ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000);
ServerCnxnFactory f = ServerCnxnFactory.createFactory(CLIENT_PORT, -1);
f.startup(zks);
Assert.assertNotNull("JMX initialization failed!", zks.jmxServerBean);
Assert.assertTrue("waiting for server being up",
ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT,
CONNECTION_TIMEOUT));
return f;
}
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
clientConnected.countDown();
}
}
}