package com.bazaarvoice.ostrich.registry.zookeeper;
import com.bazaarvoice.curator.recipes.PersistentEphemeralNode;
import com.bazaarvoice.ostrich.ServiceEndPoint;
import com.bazaarvoice.ostrich.ServiceEndPointBuilder;
import com.bazaarvoice.ostrich.ServiceEndPointJsonCodec;
import com.codahale.metrics.MetricRegistry;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import org.apache.curator.framework.CuratorFramework;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.concurrent.TimeUnit;
import static com.bazaarvoice.ostrich.registry.zookeeper.ZooKeeperServiceRegistry.MAX_DATA_SIZE;
import static com.bazaarvoice.ostrich.registry.zookeeper.ZooKeeperServiceRegistry.makeEndPointPath;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
public class ZooKeeperServiceRegistryTest {
private static final ServiceEndPoint FOO = newEndPoint("Foo", "server:80", "");
private static final String FOO_PATH = makeEndPointPath(FOO);
private ZooKeeperServiceRegistry.NodeFactory _nodeFactory;
private ZooKeeperServiceRegistry _registry;
@Before
public void setup() {
_nodeFactory = mock(ZooKeeperServiceRegistry.NodeFactory.class);
when(_nodeFactory.create(anyString(), any(byte[].class)))
.thenAnswer(new Answer<PersistentEphemeralNode>() {
@Override
public PersistentEphemeralNode answer(InvocationOnMock invocation) throws Throwable {
return mock(PersistentEphemeralNode.class);
}
});
MetricRegistry metrics = mock(MetricRegistry.class, RETURNS_DEEP_STUBS);
_registry = new ZooKeeperServiceRegistry(_nodeFactory, metrics);
}
@After
public void teardown() throws Exception {
_registry.close();
}
@Test(expected = NullPointerException.class)
public void testNullCurator() throws Exception {
new ZooKeeperServiceRegistry((CuratorFramework) null, mock(MetricRegistry.class));
}
@Test(expected = NullPointerException.class)
public void testNullFactory() {
new ZooKeeperServiceRegistry((ZooKeeperServiceRegistry.NodeFactory) null, mock(MetricRegistry.class));
}
@Test(expected = NullPointerException.class)
public void testNullMetricRegistry() {
new ZooKeeperServiceRegistry(_nodeFactory, null);
}
@Test
public void testConstructor() {
new ZooKeeperServiceRegistry(mock(CuratorFramework.class), mock(MetricRegistry.class));
}
@Test(expected = NullPointerException.class)
public void testRegisterNullService() throws Exception {
_registry.register(null);
}
@Test(expected = NullPointerException.class)
public void testUnregisterNullService() throws Exception {
_registry.unregister(null);
}
@Test(expected = IllegalStateException.class)
public void testRegisterAfterClose() throws Exception {
_registry.close();
_registry.register(FOO);
}
@Test(expected = IllegalStateException.class)
public void testUnregisterAfterClose() throws Exception {
_registry.close();
_registry.unregister(FOO);
}
@Test(expected = IllegalStateException.class)
public void testLargePayloadSize() {
int padding = ServiceEndPointJsonCodec.toJson(FOO).getBytes(Charsets.UTF_8).length;
String payload = Strings.repeat("x", MAX_DATA_SIZE - padding);
_registry.register(newEndPoint(FOO.getServiceName(), FOO.getId(), payload), false);
}
@Test
public void testMediumPayloadSize() {
int padding = ServiceEndPointJsonCodec.toJson(FOO).getBytes(Charsets.UTF_8).length;
String payload = Strings.repeat("x", MAX_DATA_SIZE - padding - 1);
_registry.register(newEndPoint(FOO.getServiceName(), FOO.getId(), payload), false);
}
@Test
public void testEmptyPayload() {
_registry.register(newEndPoint(FOO.getServiceName(), FOO.getId(), ""), false);
}
@Test
public void testRegister() throws Exception {
PersistentEphemeralNode node = mock(PersistentEphemeralNode.class);
when(_nodeFactory.create(anyString(), any(byte[].class))).thenReturn(node);
_registry.register(FOO);
ArgumentCaptor<byte[]> dataCaptor = ArgumentCaptor.forClass(byte[].class);
verify(_nodeFactory).create(eq(FOO_PATH), dataCaptor.capture());
assertEquals(FOO, ServiceEndPointJsonCodec.fromJson(new String(dataCaptor.getValue())));
verify(node, never()).close(anyLong(), any(TimeUnit.class));
}
@Test
public void testDuplicateRegister() throws Exception {
PersistentEphemeralNode firstNode = mock(PersistentEphemeralNode.class);
PersistentEphemeralNode secondNode = mock(PersistentEphemeralNode.class);
when(_nodeFactory.create(anyString(), any(byte[].class))).thenReturn(firstNode, secondNode);
_registry.register(FOO);
_registry.register(FOO);
verify(_nodeFactory, times(2)).create(eq(FOO_PATH), any(byte[].class));
verify(firstNode).close(anyLong(), any(TimeUnit.class));
}
@Test
public void testUnregister() throws Exception {
PersistentEphemeralNode node = mock(PersistentEphemeralNode.class);
when(_nodeFactory.create(anyString(), any(byte[].class))).thenReturn(node);
_registry.register(FOO);
_registry.unregister(FOO);
verify(node).close(anyLong(), any(TimeUnit.class));
}
@Test
public void testUnregisterWithoutFirstRegistering() throws Exception {
_registry.unregister(FOO);
verify(_nodeFactory, never()).create(eq(FOO_PATH), any(byte[].class));
}
@Test
public void testDuplicateUnregister() throws Exception {
_registry.register(FOO);
_registry.unregister(FOO);
_registry.unregister(FOO);
}
@Test
public void testServiceNodeIsDeletedWhenRegistryIsClosed() throws Exception {
PersistentEphemeralNode node = mock(PersistentEphemeralNode.class);
when(_nodeFactory.create(anyString(), any(byte[].class))).thenReturn(node);
_registry.register(FOO);
_registry.close();
verify(node).close(anyLong(), any(TimeUnit.class));
}
private static ServiceEndPoint newEndPoint(String serviceName, String id, String payload) {
return new ServiceEndPointBuilder()
.withServiceName(serviceName)
.withId(id)
.withPayload(payload)
.build();
}
}