// =================================================================================================
// 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;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException.ConnectionLossException;
import org.apache.zookeeper.KeeperException.NoAuthException;
import org.apache.zookeeper.ZooKeeper;
import org.junit.Test;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.zookeeper.ZooKeeperClient.Credentials;
import com.twitter.common.zookeeper.ZooKeeperClient.ZooKeeperConnectionException;
import com.twitter.common.zookeeper.testing.BaseZooKeeperTest;
import static com.google.common.testing.junit4.JUnitAsserts.assertNotEqual;
import static org.junit.Assert.*;
/**
* @author John Sirois
*/
public class ZooKeeperClientTest extends BaseZooKeeperTest {
public ZooKeeperClientTest() {
super(Amount.of(1, Time.DAYS));
}
@Test
public void testGet() throws Exception {
final ZooKeeperClient zkClient = createZkClient();
shutdownNetwork();
try {
zkClient.get(Amount.of(50L, Time.MILLISECONDS));
fail("Expected client connection to timeout while network down");
} catch (TimeoutException e) {
assertTrue(zkClient.isClosed());
}
assertNull(zkClient.getZooKeeperClientForTests());
final CountDownLatch blockingGetComplete = new CountDownLatch(1);
final AtomicReference<ZooKeeper> client = new AtomicReference<ZooKeeper>();
new Thread(new Runnable() {
@Override public void run() {
try {
client.set(zkClient.get());
} catch (ZooKeeperConnectionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
blockingGetComplete.countDown();
}
}
}).start();
restartNetwork();
// Hung blocking connects should succeed when server connection comes up
blockingGetComplete.await();
assertNotNull(client.get());
// New connections should succeed now that network is back up
long sessionId = zkClient.get().getSessionId();
// While connected the same client should be reused (no new connections while healthy)
assertSame(client.get(), zkClient.get());
shutdownNetwork();
// Our client doesn't know the network is down yet so we should be able to get()
ZooKeeper zooKeeper = zkClient.get();
try {
zooKeeper.exists("/", false);
fail("Expected client operation to fail while network down");
} catch (ConnectionLossException e) {
// expected
}
restartNetwork();
assertEquals("Expected connection to be re-established with existing session",
sessionId, zkClient.get().getSessionId());
}
/**
* Test that if a blocking get() call gets interrupted, after a connection has been created
* but before it's connected, the zk connection gets closed.
*/
@Test
public void testGetInterrupted() throws Exception {
final ZooKeeperClient zkClient = createZkClient();
shutdownNetwork();
final CountDownLatch blockingGetComplete = new CountDownLatch(1);
final AtomicBoolean interrupted = new AtomicBoolean();
final AtomicReference<ZooKeeper> client = new AtomicReference<ZooKeeper>();
Thread getThread = new Thread(new Runnable() {
@Override public void run() {
try {
client.set(zkClient.get());
} catch (ZooKeeperConnectionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
interrupted.set(true);
throw new RuntimeException(e);
} finally {
blockingGetComplete.countDown();
}
}
});
getThread.start();
while (zkClient.getZooKeeperClientForTests() == null) {
Thread.sleep(100);
}
getThread.interrupt();
blockingGetComplete.await();
assertNull("The zk connection should have been closed", zkClient.getZooKeeperClientForTests());
assertTrue("The waiter thread should have been interrupted", interrupted.get());
assertTrue(zkClient.isClosed());
}
@Test
public void testClose() throws Exception {
ZooKeeperClient zkClient = createZkClient();
zkClient.close();
// Close should be idempotent
zkClient.close();
long firstSessionId = zkClient.get().getSessionId();
// Close on an open client should force session re-establishment
zkClient.close();
assertNotEqual(firstSessionId, zkClient.get().getSessionId());
}
@Test
public void testCredentials() throws Exception {
String path = "/test";
ZooKeeperClient authenticatedClient = createZkClient("creator", "creator");
assertEquals(path,
authenticatedClient.get().create(path, "42".getBytes(),
ZooKeeperUtils.EVERYONE_READ_CREATOR_ALL, CreateMode.PERSISTENT));
ZooKeeperClient unauthenticatedClient = createZkClient(Credentials.NONE);
assertEquals("42", getData(unauthenticatedClient, path));
try {
setData(unauthenticatedClient, path, "37");
fail("Expected unauthenticated write attempt to fail");
} catch (NoAuthException e) {
assertEquals("42", getData(unauthenticatedClient, path));
}
ZooKeeperClient nonOwnerClient = createZkClient("nonowner", "nonowner");
assertEquals("42", getData(nonOwnerClient, path));
try {
setData(nonOwnerClient, path, "37");
fail("Expected non owner write attempt to fail");
} catch (NoAuthException e) {
assertEquals("42", getData(nonOwnerClient, path));
}
ZooKeeperClient authenticatedClient2 = createZkClient("creator", "creator");
setData(authenticatedClient2, path, "37");
assertEquals("37", getData(authenticatedClient2, path));
}
private void setData(ZooKeeperClient zkClient, String path, String data) throws Exception {
zkClient.get().setData(path, data.getBytes(), ZooKeeperUtils.ANY_VERSION);
}
private String getData(ZooKeeperClient zkClient, String path) throws Exception {
return new String(zkClient.get().getData(path, false, null));
}
}