// =================================================================================================
// 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 com.twitter.common.base.Closure;
import com.twitter.common.base.Closures;
import com.twitter.common.zookeeper.testing.BaseZooKeeperTest;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Adam Samet
*/
public class ZooKeeperNodeTest extends BaseZooKeeperTest {
private static final List<ACL> ACL = ZooDefs.Ids.OPEN_ACL_UNSAFE;
private ZooKeeperClient zkClient;
private static class Listener<T> implements Closure<T> {
// We use AtomicReference as a wrapper since LinkedBlockingQueue does not allow null values.
private final BlockingQueue<AtomicReference<T>> queue =
new LinkedBlockingQueue<AtomicReference<T>>();
public void execute(T item) {
queue.offer(new AtomicReference<T>(item));
}
public T waitForUpdate() throws InterruptedException {
return queue.take().get();
}
}
private String nodePath;
@Before
public void mySetUp() throws Exception {
zkClient = createZkClient();
ZooKeeperUtils.ensurePath(zkClient, ACL, "/twitter");
nodePath = "/twitter/node";
}
@Test
public void testZooKeeperUnavailableAtConstruction() throws Exception {
shutdownNetwork(); // Make zk unavailable.
// Should be fine.
makeUninitializedNode(nodePath, Closures.<String>noop());
}
@Test(expected = KeeperException.class)
public void testZooKeeperUnavailableAtInit() throws Exception {
ZooKeeperNode zkNode = makeUninitializedNode(nodePath, Closures.<String>noop());
shutdownNetwork(); // Make zk unavailable.
zkNode.init();
}
@Test
public void testInitialization() throws Exception {
String data = "abcdefg";
zkClient.get().create(nodePath, data.getBytes(), ACL, CreateMode.PERSISTENT);
ZooKeeperNode zkNode = makeUninitializedNode(nodePath, Closures.<String>noop());
// get() should return null before initialization
assertEquals(null, zkNode.get());
zkNode.init();
// Now that init has been called, the data should be synchronously reflected.
assertEquals(data, zkNode.get());
}
@Test
public void testInitialEmptyNode() throws Exception {
Listener<String> listener = new Listener<String>();
ZooKeeperNode<String> zkNode = makeUninitializedNode(nodePath, listener);
assertEquals(null, zkNode.get());
zkNode.init();
assertEquals(null, listener.waitForUpdate());
String data = "abcdefg";
zkClient.get().create(nodePath, data.getBytes(), ACL, CreateMode.PERSISTENT);
assertEquals(data, listener.waitForUpdate());
}
@Test
public void testChangingData() throws Exception {
String data1 = "test_data";
zkClient.get().create(nodePath, data1.getBytes(), ACL, CreateMode.PERSISTENT);
Listener<String> listener = new Listener<String>();
TestDeserializer deserializer = new TestDeserializer();
ZooKeeperNode<String> zkNode = makeNode(deserializer, nodePath, listener);
assertEquals(data1, listener.waitForUpdate());
assertNotNull(deserializer.getStat());
assertEquals(0, deserializer.getStat().getVersion());
String data2 = "BLAH";
zkClient.get().setData(nodePath, data2.getBytes(), -1);
assertEquals(data2, listener.waitForUpdate());
assertEquals(1, deserializer.getStat().getVersion());
}
@Test
public void testRemoveNode() throws Exception {
String data = "testdata";
zkClient.get().create(nodePath, data.getBytes(), ACL, CreateMode.PERSISTENT);
Listener<String> listener = new Listener<String>();
TestDeserializer deserializer = new TestDeserializer();
ZooKeeperNode<String> zkNode = makeNode(deserializer, nodePath, listener);
assertEquals(data, listener.waitForUpdate());
assertNotNull(deserializer.getStat());
assertEquals(0, deserializer.getStat().getVersion());
zkClient.get().delete(nodePath, -1);
assertEquals(null, listener.waitForUpdate());
assertEquals(0, deserializer.getStat().getVersion());
zkClient.get().create(nodePath, data.getBytes(), ACL, CreateMode.PERSISTENT);
assertEquals(data, listener.waitForUpdate());
assertEquals(0, deserializer.getStat().getVersion());
}
@Test
public void testSessionExpireLogic() throws Exception {
String data1 = "testdata";
zkClient.get().create(nodePath, data1.getBytes(), ACL, CreateMode.PERSISTENT);
Listener<String> listener = new Listener<String>();
TestDeserializer deserializer = new TestDeserializer();
ZooKeeperNode<String> zkNode = makeNode(deserializer, nodePath, listener);
assertEquals(data1, listener.waitForUpdate());
assertNotNull(deserializer.getStat());
assertEquals(0, deserializer.getStat().getVersion());
expireSession(zkClient);
assertEquals(data1, listener.waitForUpdate());
String data2 = "avewf";
zkClient = createZkClient();
zkClient.get().setData(nodePath, data2.getBytes(), -1);
assertEquals(data2, listener.waitForUpdate());
assertEquals(1, deserializer.getStat().getVersion());
}
@Test
public void testStaticCreate() throws Exception {
String data = "stuff";
zkClient.get().create(nodePath, data.getBytes(), ACL, CreateMode.PERSISTENT);
ZooKeeperNode<String> zkNode = ZooKeeperNode.create(zkClient, nodePath, new TestDeserializer());
assertEquals(data, zkNode.get());
}
private ZooKeeperNode<String> makeNode(TestDeserializer deserializer, String path,
Closure<String> listener) throws Exception {
ZooKeeperNode<String> zkNode = makeUninitializedNode(deserializer, path, listener);
zkNode.init();
return zkNode;
}
private ZooKeeperNode<String> makeUninitializedNode(String path, Closure<String> listener)
throws Exception {
return makeUninitializedNode(new TestDeserializer(), path, listener);
}
private ZooKeeperNode<String> makeUninitializedNode(
ZooKeeperNode.NodeDeserializer<String> deserializer, String path, Closure<String> listener)
throws Exception {
// we test deserializertionWithPair primarily because it is deserializertionally a proper
// superset of deserializertionWithByteArray
return new ZooKeeperNode<String>(zkClient, path, deserializer, listener);
}
// helper to test Stat population and retrieval
private static final class TestDeserializer implements ZooKeeperNode.NodeDeserializer<String> {
private Stat stat = null;
@Override
public String deserialize(byte[] data, Stat stat) {
this.stat = stat;
return new String(data);
}
public Stat getStat() {
return stat;
}
}
}