/*
* Copyright 2008-2013 LinkedIn, Inc
*
* Licensed 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 voldemort.store.metadata;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import voldemort.ServerTestUtils;
import voldemort.VoldemortException;
import voldemort.client.rebalance.RebalanceTaskInfo;
import voldemort.cluster.Cluster;
import voldemort.server.rebalance.RebalancerState;
import voldemort.store.metadata.MetadataStore.VoldemortState;
import voldemort.tools.admin.AvroAddStoreTest;
import voldemort.utils.ByteArray;
import voldemort.utils.ByteUtils;
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Versioned;
import voldemort.xml.ClusterMapper;
import voldemort.xml.StoreDefinitionsMapper;
import com.google.common.collect.Maps;
public class MetadataStoreTest {
private Logger logger = Logger.getLogger(MetadataStore.class);
public static String storesXmlWithBackwardIncompatibleSchema = "<stores>\n" + AvroAddStoreTest.storeXmlWithBackwardIncompatibleSchema + "</stores>";
public static String storesXmlWithBackwardCompatibleSchema = "<stores>\n" + AvroAddStoreTest.storeXmlWithBackwardCompatibleSchema + "</stores>";
private static int TEST_RUNS = 100;
private MetadataStore metadataStore;
private List<String> TEST_KEYS = Arrays.asList(MetadataStore.CLUSTER_KEY,
MetadataStore.STORES_KEY,
MetadataStore.REBALANCING_STEAL_INFO,
MetadataStore.SERVER_STATE_KEY,
MetadataStore.REBALANCING_SOURCE_CLUSTER_XML);
@Before
public void setUp() throws Exception {
metadataStore = ServerTestUtils.createMetadataStore(ServerTestUtils.getLocalCluster(1),
ServerTestUtils.getStoreDefs(1));
}
public ByteArray getValidKey() {
int i = (int) (Math.random() * TEST_KEYS.size());
String key = TEST_KEYS.get(i);
return new ByteArray(ByteUtils.getBytes(key, "UTF-8"));
}
public byte[] getValidValue(ByteArray key) {
String keyString = ByteUtils.getString(key.get(), "UTF-8");
if(MetadataStore.CLUSTER_KEY.equals(keyString)
|| MetadataStore.REBALANCING_SOURCE_CLUSTER_XML.equals(keyString)) {
return ByteUtils.getBytes(new ClusterMapper().writeCluster(ServerTestUtils.getLocalCluster(1)),
"UTF-8");
} else if(MetadataStore.STORES_KEY.equals(keyString)) {
return ByteUtils.getBytes(new StoreDefinitionsMapper().writeStoreList(ServerTestUtils.getStoreDefs(1)),
"UTF-8");
} else if(MetadataStore.SERVER_STATE_KEY.equals(keyString)) {
int i = (int) (Math.random() * VoldemortState.values().length);
return ByteUtils.getBytes(VoldemortState.values()[i].toString(), "UTF-8");
} else if(MetadataStore.REBALANCING_STEAL_INFO.equals(keyString)) {
int size = (int) (Math.random() * 10) + 1;
List<Integer> partition = new ArrayList<Integer>();
for(int i = 0; i < size; i++) {
partition.add((int) Math.random() * 10);
}
List<Integer> partitionIds = partition;
HashMap<String, List<Integer>> storeToReplicaToPartitionList = Maps.newHashMap();
storeToReplicaToPartitionList.put("test", partitionIds);
return ByteUtils.getBytes(new RebalancerState(Arrays.asList(new RebalanceTaskInfo(0,
(int) Math.random() * 5,
storeToReplicaToPartitionList,
ServerTestUtils.getLocalCluster(1)))).toJsonString(),
"UTF-8");
}
throw new RuntimeException("Unhandled key:" + keyString + " passed");
}
@Test
public void testSimpleGetAndPut() {
for(int i = 0; i <= TEST_RUNS; i++) {
ByteArray key = getValidKey();
VectorClock clock = (VectorClock) metadataStore.get(key, null).get(0).getVersion();
Versioned<byte[]> value = new Versioned<byte[]>(getValidValue(key),
clock.incremented(0, 1));
metadataStore.put(key, value, null);
checkValues(value, metadataStore.get(key, null), key);
}
}
@Test
public void testRepeatedPuts() {
for(int i = 0; i <= TEST_RUNS; i++) {
for(int j = 0; j <= 5; j++) {
ByteArray key = getValidKey();
VectorClock clock = (VectorClock) metadataStore.get(key, null).get(0).getVersion();
Versioned<byte[]> value = new Versioned<byte[]>(getValidValue(key),
clock.incremented(0, 1));
metadataStore.put(key, value, null);
checkValues(value, metadataStore.get(key, null), key);
}
}
}
@Test
public void testObsoletePut() {
for(int i = 0; i <= TEST_RUNS; i++) {
ByteArray key = getValidKey();
VectorClock clock = (VectorClock) metadataStore.get(key, null).get(0).getVersion();
Versioned<byte[]> value = new Versioned<byte[]>(getValidValue(key),
clock.incremented(0, 1));
try {
metadataStore.put(key, value, null);
assertTrue(true);
metadataStore.put(key, value, null);
fail();
} catch(ObsoleteVersionException e) {
// expected ObsoleteVersionException
}
}
}
@Test
public void testSynchronousPut() {
for(int i = 0; i <= TEST_RUNS; i++) {
ByteArray key = getValidKey();
VectorClock clock = (VectorClock) metadataStore.get(key, null).get(0).getVersion();
Versioned<byte[]> value1 = new Versioned<byte[]>(getValidValue(key),
clock.incremented(1, 1));
Versioned<byte[]> value2 = new Versioned<byte[]>(getValidValue(key),
clock.incremented(2, 1));
metadataStore.put(key, value1, null);
metadataStore.put(key, value2, null);
assertEquals("Only one metadata value should return", 1, metadataStore.get(key, null)
.size());
checkValues(value2, metadataStore.get(key, null), key);
}
}
@Test
public void testCleanAllStates() {
// put state entries.
incrementVersionAndPut(metadataStore,
MetadataStore.SERVER_STATE_KEY,
MetadataStore.VoldemortState.REBALANCING_MASTER_SERVER);
assertEquals("Values should match.",
metadataStore.getServerStateUnlocked(),
VoldemortState.REBALANCING_MASTER_SERVER);
// do clean
metadataStore.cleanAllRebalancingState();
// check all values revert back to default.
assertEquals("Values should match.",
metadataStore.getServerStateUnlocked(),
VoldemortState.NORMAL_SERVER);
}
@Test
public void testRebalacingSourceClusterXmlKey() {
metadataStore.cleanAllRebalancingState();
assertTrue("Should be null", null == metadataStore.getRebalancingSourceCluster());
Cluster dummyCluster = ServerTestUtils.getLocalCluster(2);
metadataStore.put(MetadataStore.REBALANCING_SOURCE_CLUSTER_XML, dummyCluster);
assertEquals("Should be equal", dummyCluster, metadataStore.getRebalancingSourceCluster());
metadataStore.put(MetadataStore.REBALANCING_SOURCE_CLUSTER_XML, (Object) null);
assertTrue("Should be null", null == metadataStore.getRebalancingSourceCluster());
List<Versioned<byte[]>> sourceClusterVersions = metadataStore.get(MetadataStore.REBALANCING_SOURCE_CLUSTER_XML,
null);
assertTrue("Just one version expected", 1 == sourceClusterVersions.size());
assertEquals("Empty string should map to null",
"",
new String(sourceClusterVersions.get(0).getValue()));
}
/**
* Test update stores.xml with incompatible avro versions. Should reject and throw exceptions
*/
@Test
public void testUpdateStoresXmlWithIncompatibleAvroSchema() {
try{
logger.info("Now inserting stores with non backward compatible schema. Should see exception");
metadataStore.put(MetadataStore.STORES_KEY, new StoreDefinitionsMapper().readStoreList(new StringReader(storesXmlWithBackwardIncompatibleSchema)));
Assert.fail("Did not throw exception");
} catch(VoldemortException e) {
}
logger.info("Now inserting stores with backward compatible schema. Should not see exception");
metadataStore.put(MetadataStore.STORES_KEY, new StoreDefinitionsMapper().readStoreList(new StringReader(storesXmlWithBackwardCompatibleSchema)));
}
private void checkValues(Versioned<byte[]> value, List<Versioned<byte[]>> list, ByteArray key) {
assertEquals("should return exactly one value ", 1, list.size());
assertEquals("should return the last saved version", value.getVersion(), list.get(0)
.getVersion());
assertEquals("should return the last saved value (key:"
+ ByteUtils.getString(key.get(), "UTF-8") + ")",
new String(value.getValue()),
new String(list.get(0).getValue()));
}
/**
* helper function to auto update version and put()
*
* @param key
* @param value
*/
private void incrementVersionAndPut(MetadataStore metadataStore, String keyString, Object value) {
ByteArray key = new ByteArray(ByteUtils.getBytes(keyString, "UTF-8"));
VectorClock current = (VectorClock) metadataStore.getVersions(key).get(0);
metadataStore.put(keyString,
new Versioned<Object>(value,
current.incremented(0, System.currentTimeMillis())));
}
}