package voldemort.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import voldemort.ServerTestUtils;
import voldemort.VoldemortException;
import voldemort.client.protocol.admin.AdminClient;
import voldemort.client.protocol.admin.AdminClientConfig;
import voldemort.cluster.Cluster;
import voldemort.cluster.Node;
import voldemort.server.VoldemortConfig;
import voldemort.server.VoldemortServer;
import voldemort.store.StoreDefinition;
import voldemort.store.memory.InMemoryStorageConfiguration;
import voldemort.store.quota.QuotaType;
import voldemort.versioning.Versioned;
import voldemort.xml.StoreDefinitionsMapper;
import com.google.common.collect.Lists;
@RunWith(Parameterized.class)
public class VerifyOrAddStoreTest {
private final static String storesXmlfile = "test/common/voldemort/config/stores.xml";
private final static String readOnlyXmlFilePath = "test/common/voldemort/config/readonly-store.xml";
private final static String PROCESS_NAME = "unit-test";
private VoldemortServer[] servers;
private Cluster cluster;
private AdminClient adminClient;
private final Properties serverProps;
private final long defaultQuota;
private StoreDefinition newStoreDef;
private String newStoreName;
private final ClientConfig clientConfig;
private final ExecutorService service;
private final int FAILED_NODE_ID;
private static final int NUM_SERVERS = 4;
@Parameters
public static Collection<Object[]> configs() {
List<Object[]> allConfigs = Lists.newArrayList();
for (boolean fetchAllStores : new boolean[] { true, false }) {
for (int threads : new int[] { 0, 2, 4, 6 }) {
allConfigs.add(new Object[] { fetchAllStores, threads });
}
}
return allConfigs;
}
public VerifyOrAddStoreTest(boolean fetchAllStores, int numberOfThreads) {
clientConfig = new ClientConfig().setFetchAllStoresXmlInBootstrap(fetchAllStores);
if (numberOfThreads == 0) {
service = null;
} else {
service = Executors.newFixedThreadPool(numberOfThreads);
}
// Set default quota in between 100 and 1100 at random
defaultQuota = 100 + new Random().nextInt(1000);
FAILED_NODE_ID = new Random().nextInt(NUM_SERVERS);
serverProps = new Properties();
serverProps.setProperty(VoldemortConfig.DEFAULT_STORAGE_SPACE_QUOTA_IN_KB, Long.toString(defaultQuota));
}
@Before
public void setUp() throws IOException {
servers = new VoldemortServer[NUM_SERVERS];
int partitionMap[][] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 }, { 12, 13, 14, 15 } };
cluster = ServerTestUtils.startVoldemortCluster(servers, partitionMap, serverProps, storesXmlfile);
newStoreDef = new StoreDefinitionsMapper().readStoreList(new File(readOnlyXmlFilePath)).get(0);
newStoreName = newStoreDef.getName();
Node node = cluster.getNodeById(0);
String bootstrapUrl = "tcp://" + node.getHost() + ":" + node.getSocketPort();
clientConfig.setBootstrapUrls(bootstrapUrl);
adminClient = new AdminClient(new AdminClientConfig(), clientConfig);
}
@After
public void tearDown() throws Exception {
adminClient.close();
for(VoldemortServer server: servers) {
ServerTestUtils.stopVoldemortServer(server);
}
if (service != null) {
service.shutdown();
service.awaitTermination(100, TimeUnit.MILLISECONDS);
}
}
private StoreDefinition retrieveStoreOnNode(String storeName, int nodeId) {
return adminClient.metadataMgmtOps.getStoreDefinition(nodeId, storeName);
}
private Long getQuotaForNode(String storeName, QuotaType quotaType, int nodeId) {
Versioned<String> quotaStr = adminClient.quotaMgmtOps.getQuotaForNode(storeName, quotaType, nodeId);
if (quotaStr == null) {
return null;
}
return Long.parseLong(quotaStr.getValue());
}
private void verifyStoreAddedOnAllNodes() {
// Get from a random node, verify
StoreDefinition retrieved = adminClient.metadataMgmtOps.getStoreDefinition(newStoreName);
assertNotNull("Created store can't be retrieved", retrieved);
assertEquals("Store Created and retrieved are different ", newStoreDef, retrieved);
// Get from one by one verify
for (Integer nodeId : cluster.getNodeIds()) {
retrieved = retrieveStoreOnNode(newStoreName, nodeId);
assertNotNull("Created store can't be retrieved", retrieved);
assertEquals("Store Created and retrieved are different ", newStoreDef, retrieved);
Long quota = getQuotaForNode(newStoreName, QuotaType.STORAGE_SPACE, nodeId);
assertEquals("Default quota mismatch", defaultQuota, quota.longValue());
}
}
private void verifyStoreDoesNotExist() {
StoreDefinition retrieved = adminClient.metadataMgmtOps.getStoreDefinition(newStoreName);
assertNull("Store should not be created", retrieved);
for (Integer nodeId : cluster.getNodeIds()) {
retrieved = retrieveStoreOnNode(newStoreName, nodeId);
assertNull("Store should not be created", retrieved);
Long quota = getQuotaForNode(newStoreName, QuotaType.STORAGE_SPACE, nodeId);
assertNull("Default quota should not exist", quota);
}
}
@Test
public void verifyNewStoreAddition() {
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, service);
verifyStoreAddedOnAllNodes();
// Verify set quota on the stores as well.
for (QuotaType quotaType : new QuotaType[] { QuotaType.STORAGE_SPACE, QuotaType.GET_THROUGHPUT }) {
long newQuota = 100000 + new Random().nextInt(10000);
adminClient.quotaMgmtOps.setQuota(newStoreName, quotaType, newQuota, service);
for (Integer nodeId : cluster.getNodeIds()) {
Long retrievedQuota = getQuotaForNode(newStoreName, quotaType, nodeId);
assertEquals("Set and retrieved different quota", newQuota, retrievedQuota.longValue());
}
}
}
@Test
public void verifyNoStoreCreated() {
try {
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, false, service);
Assert.fail("Non existent store create with flag disabled should have thrown error");
} catch (VoldemortException ex) {
// Pass
}
verifyStoreDoesNotExist();
}
@Test
public void verifyPartialCreatedStores() {
// Add the store on one node with same definition.
adminClient.storeMgmtOps.addStore(newStoreDef, 0);
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, service);
verifyStoreAddedOnAllNodes();
}
private StoreDefinition getIncompatibleStoreDef() {
// Create store with same definition but modify the type.
return new StoreDefinition(newStoreDef.getName(),
InMemoryStorageConfiguration.TYPE_NAME,
newStoreDef.getDescription(),
newStoreDef.getKeySerializer(),
newStoreDef.getValueSerializer(),
newStoreDef.getTransformsSerializer(),
newStoreDef.getRoutingPolicy(),
newStoreDef.getRoutingStrategyType(),
newStoreDef.getReplicationFactor(),
newStoreDef.getPreferredReads(),
newStoreDef.getRequiredReads(),
newStoreDef.getPreferredWrites(),
newStoreDef.getRequiredWrites(),
newStoreDef.getViewTargetStoreName(),
newStoreDef.getValueTransformation(),
newStoreDef.getZoneReplicationFactor(),
newStoreDef.getZoneCountReads(),
newStoreDef.getZoneCountWrites(),
newStoreDef.getRetentionDays(),
newStoreDef.getRetentionScanThrottleRate(),
newStoreDef.getRetentionFrequencyDays(),
newStoreDef.getSerializerFactory(),
newStoreDef.getHintedHandoffStrategyType(),
newStoreDef.getHintPrefListSize(),
newStoreDef.getOwners(),
newStoreDef.getMemoryFootprintMB());
}
@Test
public void verifyMisMatchFails() throws Exception {
StoreDefinition incompatibleDef = getIncompatibleStoreDef();
// Add the incompatible store definition to Node 0
adminClient.storeMgmtOps.addStore(incompatibleDef, FAILED_NODE_ID);
// Adding the store with different definition should fail.
try {
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, false, service);
Assert.fail("Non existent store create with flag disabled should have thrown error");
} catch (VoldemortException ex) {
// Pass
}
for (Integer nodeId : cluster.getNodeIds()) {
StoreDefinition retrieved = retrieveStoreOnNode(newStoreName, nodeId);
if (nodeId == FAILED_NODE_ID) {
assertEquals("mismatched store def should be left intact", incompatibleDef, retrieved);
} else {
assertNull("Store should not exist in this node", retrieved);
}
}
}
@Test
public void verifyCreateWithNodeFailure() throws Exception {
ServerTestUtils.stopVoldemortServer(servers[FAILED_NODE_ID]);
// Set CreateStore to false, node down should have thrown an error
try {
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, false, service);
Assert.fail("Node failure should have raised an error");
} catch(VoldemortException ex) {
// Pass
}
// Verify that.
for (Integer nodeId : cluster.getNodeIds()) {
if (nodeId == FAILED_NODE_ID) {
// this is a dead server, continue;
} else {
StoreDefinition retrieved = retrieveStoreOnNode(newStoreName, nodeId);
assertNull("store should not exist", retrieved);
}
}
// Default is to set createStore to true, it should create stores on all
// nodes that are up and thrown an exception at the end.
try {
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, service);
Assert.fail("Node failure should have raised an error");
} catch (VoldemortException ex) {
// Pass
}
// Verify that.
for(Integer nodeId: cluster.getNodeIds()) {
if (nodeId == FAILED_NODE_ID) {
// this is a dead server, continue;
} else {
StoreDefinition retrieved = retrieveStoreOnNode(newStoreName, nodeId);
assertEquals("store successfully created on online nodes", newStoreDef, retrieved);
}
}
// Verify set quota on the stores as well.
for (QuotaType quotaType : new QuotaType[] { QuotaType.STORAGE_SPACE, QuotaType.GET_THROUGHPUT }) {
long newQuota = 100000 + new Random().nextInt(10000);
try {
adminClient.quotaMgmtOps.setQuota(newStoreName, quotaType, newQuota, service);
Assert.fail("Node failure should have raised an error");
} catch (VoldemortException ex) {
// expected.
}
for (Integer nodeId : cluster.getNodeIds()) {
if (nodeId == FAILED_NODE_ID) {
// this is dead server, continue.
} else {
Long retrievedQuota = getQuotaForNode(newStoreName, quotaType, nodeId);
assertEquals("Set and retrieved different quota", newQuota, retrievedQuota.longValue());
}
}
}
}
@Test
public void verifyAdminClientAcrossNodeRestart() throws Exception {
long newQuota = 500000 + new Random().nextInt(10000);
// Do a query create the connections.
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, service);
// Create client pool as well.
for (QuotaType quotaType : new QuotaType[] { QuotaType.STORAGE_SPACE, QuotaType.GET_THROUGHPUT }) {
adminClient.quotaMgmtOps.setQuota(newStoreName, quotaType, newQuota, service);
}
ServerTestUtils.stopVoldemortServer(servers[FAILED_NODE_ID]);
try {
retrieveStoreOnNode(newStoreName, FAILED_NODE_ID);
Assert.fail("Downed node retrieval should have thrown an error");
} catch (VoldemortException ex) {
// Expected
}
try {
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, service);
Assert.fail("Downed node should have thrown an error");
} catch (VoldemortException ex) {
// Expected
}
// setQuota should throw as well.
for (QuotaType quotaType : new QuotaType[] { QuotaType.STORAGE_SPACE, QuotaType.GET_THROUGHPUT }) {
try {
adminClient.quotaMgmtOps.setQuota(newStoreName, quotaType, newQuota, service);
Assert.fail("Node failure should have raised an error");
} catch (VoldemortException ex) {
// expected.
}
}
servers[FAILED_NODE_ID] =
ServerTestUtils.restartServer(servers[FAILED_NODE_ID], FAILED_NODE_ID, cluster, serverProps);
StoreDefinition retrieved = retrieveStoreOnNode(newStoreName, FAILED_NODE_ID);
assertEquals("After restart, should retrieve the store", newStoreDef, retrieved);
adminClient.storeMgmtOps.verifyOrAddStore(newStoreDef, PROCESS_NAME, service);
long updatedQuota = newQuota + 100;
for (QuotaType quotaType : new QuotaType[] { QuotaType.STORAGE_SPACE, QuotaType.GET_THROUGHPUT }) {
adminClient.quotaMgmtOps.setQuota(newStoreName, quotaType, updatedQuota, service);
for (Integer nodeId : cluster.getNodeIds()) {
Long retrievedQuota = getQuotaForNode(newStoreName, quotaType, nodeId);
assertEquals("Set and retrieved different quota", updatedQuota, retrievedQuota.longValue());
}
}
}
}