/**
* 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 com.pinterest.terrapin.zookeeper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import com.pinterest.terrapin.thrift.generated.Options;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.twitter.common.zookeeper.ZooKeeperClient;
import com.twitter.common.zookeeper.ZooKeeperMap;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.api.support.membermodification.MemberModifier;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Set;
@RunWith(PowerMockRunner.class)
public class ZooKeeperManagerTest {
private static final String CLUSTER_NAME = "test_cluster";
private static final String FILE_SET = "test_fileset";
private static final FileSetInfo FILE_SET_INFO = new FileSetInfo();
private static final String FILE_SET_DIR = "/" + CLUSTER_NAME + "/filesets";
private static final String VIEWS_DIR = "/" + CLUSTER_NAME + "/views";
private static final String LOCKS_DIR = "/" + CLUSTER_NAME + "/locks";
private static final String FILE_SET_PATH = FILE_SET_DIR + "/" + FILE_SET;
private static final String FILE_SET_LOCK_PATH = LOCKS_DIR + "/" + FILE_SET;
private ZooKeeperManager zkManager;
private ZooKeeperClient zkClient;
private ZooKeeper zk;
@Before
public void setUp() throws Exception {
zkClient = mock(ZooKeeperClient.class);
zk = mock(ZooKeeper.class);
zkManager = new ZooKeeperManager(zkClient, CLUSTER_NAME);
when(zkClient.get()).thenReturn(zk);
}
@Test
public void testCreateClusterPaths() throws Exception {
when(zk.create(anyString(), any(byte[].class), anyListOf(ACL.class), any(CreateMode.class)))
.thenReturn("");
ArgumentCaptor<String> pathCaptor = ArgumentCaptor.forClass(String.class);
zkManager.createClusterPaths();
verify(zk, times(3)).create(pathCaptor.capture(), any(byte[].class), anyListOf(ACL.class),
any(CreateMode.class));
Set<String> allPaths = Sets.newHashSet(pathCaptor.getAllValues());
assertTrue(allPaths.contains(FILE_SET_DIR));
assertTrue(allPaths.contains(VIEWS_DIR));
assertTrue(allPaths.contains(LOCKS_DIR));
}
@Test
@PrepareForTest(ZooKeeperMap.class)
public void testRegisterWatchAllFileSets() throws Exception {
PowerMockito.mockStatic(ZooKeeperMap.class);
when(ZooKeeperMap.create(any(ZooKeeperClient.class), anyString(), any(Function.class)))
.thenReturn(null);
ArgumentCaptor<String> pathCaptor = ArgumentCaptor.forClass(String.class);
zkManager.registerWatchAllFileSets();
verifyStatic(times(2));
ZooKeeperMap.create(any(ZooKeeperClient.class), pathCaptor.capture(), any(Function.class));
Set<String> allPaths = Sets.newHashSet(pathCaptor.getAllValues());
assertTrue(allPaths.contains(FILE_SET_DIR));
assertTrue(allPaths.contains(VIEWS_DIR));
}
@Test
public void testLockFileSet() throws Exception {
when(zk.create(anyString(), any(byte[].class), anyListOf(ACL.class), any(CreateMode.class)))
.thenReturn("");
CreateMode mode = CreateMode.EPHEMERAL;
zkManager.lockFileSet(FILE_SET, FILE_SET_INFO, mode);
verify(zk).create(eq(FILE_SET_LOCK_PATH), any(byte[].class),
anyListOf(ACL.class), eq(mode));
}
@Test
public void testUnlockFileSet() throws Exception {
doNothing().when(zk).delete(anyString(), anyInt());
ArgumentCaptor<String> pathCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Integer> versionCaptor = ArgumentCaptor.forClass(Integer.class);
zkManager.unlockFileSet(FILE_SET);
verify(zk).delete(pathCaptor.capture(), versionCaptor.capture());
assertEquals(FILE_SET_LOCK_PATH, pathCaptor.getValue());
assertEquals(new Integer(-1), versionCaptor.getValue());
}
@Test
public void testSetNonExistingFileSetInfo() throws Exception {
when(zk.exists(anyString(), anyBoolean())).thenReturn(null);
when(zk.create(anyString(), any(byte[].class), anyListOf(ACL.class), any(CreateMode.class)))
.thenReturn("");
zkManager.setFileSetInfo(FILE_SET, FILE_SET_INFO);
verify(zk).exists(eq(FILE_SET_PATH), eq(false));
verify(zk).create(eq(FILE_SET_PATH), eq(FILE_SET_INFO.toJson()), anyListOf(ACL.class),
any(CreateMode.class));
}
@Test
public void testSetExistingFileSetInfo() throws Exception {
when(zk.exists(anyString(), anyBoolean())).thenReturn(mock(Stat.class));
when(zk.setData(anyString(), any(byte[].class), anyInt())).thenReturn(mock(Stat.class));
zkManager.setFileSetInfo(FILE_SET, FILE_SET_INFO);
verify(zk).exists(eq(FILE_SET_PATH), eq(false));
verify(zk).setData(eq(FILE_SET_PATH), eq(FILE_SET_INFO.toJson()), eq(-1));
}
@Test
public void testDeleteFileSetInfo() throws Exception {
doNothing().when(zk).delete(anyString(), anyInt());
zkManager.deleteFileSetInfo(FILE_SET);
verify(zk).delete(eq(FILE_SET_PATH), eq(-1));
}
@Test
public void testGetViewInfo() throws Exception {
PowerMockito.mockStatic(ZooKeeperMap.class);
ViewInfo viewInfo = new ViewInfo();
String resource1 = "test_view";
MemberModifier.field(ZooKeeperManager.class, "viewInfoMap")
.set(zkManager, ImmutableMap.of(resource1, viewInfo));
assertNotNull(zkManager.getViewInfo(resource1));
assertNull(zkManager.getViewInfo("non_existing"));
}
@Test
public void testSetExistingViewInfo() throws Exception {
ViewInfo viewInfo = mock(ViewInfo.class);
String resource = "test_resource";
String resourcePath = VIEWS_DIR + "/" + resource;
byte[] resourceData = "hello".getBytes();
when(viewInfo.toCompressedJson()).thenReturn(resourceData);
when(viewInfo.hasOnlinePartitions()).thenReturn(true);
when(viewInfo.getResource()).thenReturn(resource);
when(zk.exists(eq(resourcePath), anyBoolean())).thenReturn(new Stat());
when(zk.setData(eq(resourcePath), eq(resourceData), eq(-1))).thenReturn(new Stat());
zkManager.setViewInfo(viewInfo);
verify(zk).setData(eq(resourcePath), eq(resourceData), eq(-1));
}
@Test
public void testSetNonExistingViewInfo() throws Exception {
ViewInfo viewInfo = mock(ViewInfo.class);
String resource = "test_resource";
String resourcePath = VIEWS_DIR + "/" + resource;
byte[] resourceData = "hello".getBytes();
when(viewInfo.toCompressedJson()).thenReturn(resourceData);
when(viewInfo.hasOnlinePartitions()).thenReturn(true);
when(viewInfo.getResource()).thenReturn(resource);
when(zk.exists(eq(resourcePath), anyBoolean())).thenReturn(null);
when(zk.create(eq(resourcePath), eq(resourceData), anyListOf(ACL.class), any(CreateMode.class)))
.thenReturn(null);
zkManager.setViewInfo(viewInfo);
verify(zk).create(eq(resourcePath), eq(resourceData),
anyListOf(ACL.class), any(CreateMode.class));
}
@Test
public void testSetViewInfoNoOnlinePart() throws Exception {
ViewInfo viewInfo = mock(ViewInfo.class);
byte[] resourceData = "hello".getBytes();
when(viewInfo.toCompressedJson()).thenReturn(resourceData);
when(viewInfo.hasOnlinePartitions()).thenReturn(false);
zkManager.setViewInfo(viewInfo);
verify(zk, times(0)).create(anyString(), any(byte[].class),
anyListOf(ACL.class), any(CreateMode.class));
verify(zk, times(0)).setData(anyString(), any(byte[].class), anyInt());
}
@Test
public void testGetCandidateHdfsDirMap() throws Exception {
FileSetInfo fileSetInfo1 =
new FileSetInfo("abc", "123", 0,
ImmutableList.copyOf(new FileSetInfo.ServingInfo[]{}), new Options());
FileSetInfo fileSetInfo2 =
new FileSetInfo("def", "456", 0,
ImmutableList.copyOf(new FileSetInfo.ServingInfo[]{}), new Options());
FileSetInfo fileSetInfo3 =
new FileSetInfo("ghi", "789", 0,
ImmutableList.copyOf(new FileSetInfo.ServingInfo[]{}), new Options());
MemberModifier.field(ZooKeeperManager.class, "fileSetInfoMap")
.set(zkManager, ImmutableMap.builder()
.put(fileSetInfo1.fileSetName, fileSetInfo1)
.put(fileSetInfo2.fileSetName, fileSetInfo2)
.put(fileSetInfo3.fileSetName, fileSetInfo3)
.build());
when(zk.getData(eq(FILE_SET_DIR + "/" + fileSetInfo1.fileSetName),
anyBoolean(), any(Stat.class))).thenReturn(fileSetInfo1.toJson());
when(zk.getData(eq(LOCKS_DIR + "/" + fileSetInfo1.fileSetName),
anyBoolean(), any(Stat.class))).thenReturn(fileSetInfo1.toJson());
when(zk.getData(eq(FILE_SET_DIR + "/" + fileSetInfo2.fileSetName),
anyBoolean(), any(Stat.class))).thenReturn(fileSetInfo2.toJson());
when(zk.getData(eq(LOCKS_DIR + "/" + fileSetInfo2.fileSetName),
anyBoolean(), any(Stat.class))).thenThrow(new KeeperException.NoNodeException());
when(zk.getData(eq(FILE_SET_DIR + "/" + fileSetInfo3.fileSetName),
anyBoolean(), any(Stat.class))).thenThrow(new KeeperException.NoNodeException());
when(zk.getData(eq(LOCKS_DIR + "/" + fileSetInfo3.fileSetName),
anyBoolean(), any(Stat.class))).thenReturn(fileSetInfo3.toJson());
Map<String, Pair<FileSetInfo, FileSetInfo>> results = zkManager.getCandidateHdfsDirMap();
assertEquals(3, results.size());
assertEquals(fileSetInfo1, results.get(fileSetInfo1.fileSetName).getLeft());
assertEquals(fileSetInfo1, results.get(fileSetInfo1.fileSetName).getRight());
assertEquals(fileSetInfo2, results.get(fileSetInfo2.fileSetName).getLeft());
assertNull(results.get(fileSetInfo2.fileSetName).getRight());
assertNull(results.get(fileSetInfo3.fileSetName).getLeft());
assertEquals(fileSetInfo3, results.get(fileSetInfo3.fileSetName).getRight());
}
@Test
public void testGetControllerLeader() throws Exception {
String host = "somehost";
int port = 9090;
String json = String.format("{\"id\": \"%s_%d\"}", host, port);
when(zk.getData(eq("/" + CLUSTER_NAME + "/CONTROLLER/LEADER"),
anyBoolean(), any(Stat.class))).thenReturn(json.getBytes());
InetSocketAddress address = zkManager.getControllerLeader();
assertEquals(host, address.getHostName());
assertEquals(port, address.getPort());
}
@Test
public void testGetClusterInfo() throws Exception {
String hdfsNameNode = "namenode001";
int replicaFactor = 3;
ClusterInfo clusterInfo = new ClusterInfo(hdfsNameNode, replicaFactor);
when(zk.getData(eq("/" + CLUSTER_NAME), anyBoolean(), any(Stat.class)))
.thenReturn(clusterInfo.toJson());
ClusterInfo returnedInfo = zkManager.getClusterInfo();
assertEquals(hdfsNameNode, returnedInfo.hdfsNameNode);
assertEquals(replicaFactor, returnedInfo.hdfsReplicationFactor);
}
@Test
public void testSetClusterInfo() throws Exception {
String hdfsNameNode = "namenode001";
int replicaFactor = 3;
ClusterInfo clusterInfo = new ClusterInfo(hdfsNameNode, replicaFactor);
when(zk.setData(eq("/" + CLUSTER_NAME), eq(clusterInfo.toJson()), anyInt()))
.thenReturn(new Stat());
zkManager.setClusterInfo(clusterInfo);
verify(zk).setData(eq("/" + CLUSTER_NAME), eq(clusterInfo.toJson()), anyInt());
}
}