/*
* Copyright 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.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import voldemort.ServerTestUtils;
import voldemort.TestUtils;
import voldemort.cluster.Cluster;
import voldemort.cluster.Node;
import voldemort.server.VoldemortServer;
import voldemort.store.socket.SocketStoreFactory;
import voldemort.store.socket.clientrequest.ClientRequestExecutorPool;
import voldemort.utils.ConsistencyFix.BadKey;
import voldemort.utils.ConsistencyFix.BadKeyOrphanReader;
import voldemort.utils.ConsistencyFix.BadKeyReader;
import voldemort.utils.ConsistencyFix.BadKeyStatus;
import voldemort.utils.ConsistencyFix.BadKeyWriter;
import voldemort.utils.ConsistencyFix.Stats;
import voldemort.versioning.VectorClock;
public class ConsistencyFixTest {
final static String STORE_NAME = "consistency-fix";
final static String STORES_XML = "test/common/voldemort/config/consistency-stores.xml";
/**
*
* @return bootstrap url
*/
public static String setUpCluster() {
// setup four nodes with one store and one partition
final SocketStoreFactory socketStoreFactory = new ClientRequestExecutorPool(2,
10000,
100000,
32 * 1024);
VoldemortServer[] servers = new VoldemortServer[4];
int partitionMap[][] = { { 0 }, { 1 }, { 2 }, { 3 } };
try {
Cluster cluster = ServerTestUtils.startVoldemortCluster(4,
servers,
partitionMap,
socketStoreFactory,
true,
null,
STORES_XML,
new Properties());
Node node = cluster.getNodeById(0);
return "tcp://" + node.getHost() + ":" + node.getSocketPort();
} catch(IOException e) {
e.printStackTrace();
fail("Unexpected exception");
}
return null;
}
public void badKeyReaderWriteBadKeys(String fileName, boolean addWhiteSpace) {
// Write file of "bad keys" with messy white space
try {
BufferedWriter fileWriter = new BufferedWriter(new FileWriter(fileName));
if(addWhiteSpace)
fileWriter.write("\n\n\t\t\n\n\t\n");
for(int i = 0; i < 1000; ++i) {
byte[] keyb = TestUtils.randomBytes(10);
fileWriter.write(ByteUtils.toHexString(keyb) + "\n");
if(addWhiteSpace) {
if(i % 5 == 0) {
fileWriter.write("\n\n\t\t\n\n\t\n");
}
if(i % 7 == 0) {
fileWriter.write("\t");
}
}
}
if(addWhiteSpace)
fileWriter.write("\n\n\t\t\n\n\t\n");
fileWriter.close();
} catch(IOException e) {
e.printStackTrace();
fail("Unexpected exception");
}
}
public void badKeyReaderWriteOrphanKeys(String fileName, boolean addWhiteSpace) {
// Write file of "orphans" with some white space between entries.
/*-
* Example entry:
6473333333646464,2
00,version(1:1, 2:1) ts:1357257858674
00,version(1:1, 3:1) ts:1357257858684
*/
try {
BufferedWriter fileWriter = new BufferedWriter(new FileWriter(fileName));
if(addWhiteSpace)
fileWriter.write("\n\n\t\t\n\n\t\n");
for(int i = 0; i < 1000; ++i) {
int numValues = (i % 3) + 1;
byte[] keyb = TestUtils.randomBytes(10);
String keyLine = ByteUtils.toHexString(keyb) + "," + numValues;
System.out.println("keyLine: " + keyLine);
fileWriter.write(keyLine + "\n");
for(int j = 0; j < numValues; j++) {
int valLength = (j + 10) * (j + 1);
String value = ByteUtils.toHexString(TestUtils.randomBytes(valLength));
VectorClock vectorClock = TestUtils.getClock(j);
String valueLine = value + "," + vectorClock.toString();
System.out.println("valueLine: " + valueLine);
fileWriter.write(valueLine + "\n");
}
if(addWhiteSpace) {
if(i % 5 == 0) {
fileWriter.write("\n\n\t\t\n\n\t\n");
}
}
}
if(addWhiteSpace)
fileWriter.write("\n\n\t\t\n\n\t\n");
fileWriter.close();
} catch(IOException e) {
e.printStackTrace();
fail("Unexpected exception");
}
}
/**
*
* @param orphan true for testing orphan, false for testing normal...
*/
public void badKeyReaderHelper(boolean orphan) {
String tmpDir = TestUtils.createTempDir().getAbsolutePath();
String fileName = tmpDir + "BadKeyFile";
if(orphan) {
badKeyReaderWriteOrphanKeys(fileName, true);
} else {
badKeyReaderWriteBadKeys(fileName, true);
}
// Get cluster bootstrap url
String url = setUpCluster();
// Construct ConsistencyFix with parseOnly true
ConsistencyFix consistencyFix = new ConsistencyFix(url, STORE_NAME, 100, 100, false, true);
// Do set up for BadKeyReader akin to consistencyFix.execute...
int parallelism = 1;
BlockingQueue<Runnable> blockingQ = new ArrayBlockingQueue<Runnable>(parallelism);
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
ExecutorService consistencyFixWorkers = new ThreadPoolExecutor(parallelism,
parallelism,
0L,
TimeUnit.MILLISECONDS,
blockingQ,
rejectedExecutionHandler);
BlockingQueue<BadKeyStatus> badKeyQOut = new ArrayBlockingQueue<BadKeyStatus>(10000);
ExecutorService badKeyReaderService = Executors.newSingleThreadExecutor();
CountDownLatch allBadKeysReadLatch = new CountDownLatch(1);
// Submit file of bad keys to (appropriate) BadKeyReader
BadKeyReader bkr = null;
if(orphan) {
bkr = new BadKeyOrphanReader(allBadKeysReadLatch,
fileName,
consistencyFix,
consistencyFixWorkers,
badKeyQOut);
} else {
bkr = new BadKeyReader(allBadKeysReadLatch,
fileName,
consistencyFix,
consistencyFixWorkers,
badKeyQOut);
}
badKeyReaderService.submit(bkr);
// Wait for file to be processed.
try {
allBadKeysReadLatch.await();
badKeyReaderService.shutdown();
consistencyFixWorkers.shutdown();
} catch(InterruptedException e) {
e.printStackTrace();
fail("Unexpected exception");
}
consistencyFix.close();
// Make sure everything worked as expected.
assertFalse(bkr.hasException());
assertEquals(0, badKeyQOut.size());
}
@Test
public void testBadKeyReader() {
badKeyReaderHelper(false);
}
@Test
public void testBadKeyOrphanReader() {
badKeyReaderHelper(true);
}
@Test
public void testBadKeyResult() {
BadKey badKey = new BadKey("0101", "0101\n");
ConsistencyFix.BadKeyStatus bkr1 = new BadKeyStatus(badKey, ConsistencyFix.Status.SUCCESS);
assertFalse(bkr1.isPoison());
assertEquals(bkr1.getBadKey().getKeyInHexFormat(), "0101");
assertEquals(bkr1.getBadKey().getReaderInput(), "0101\n");
assertEquals(bkr1.getStatus(), ConsistencyFix.Status.SUCCESS);
ConsistencyFix.BadKeyStatus bkr2 = new BadKeyStatus();
assertTrue(bkr2.isPoison());
assertEquals(bkr2.getBadKey(), null);
assertEquals(bkr2.getStatus(), null);
}
@Test
public void testBadKeyWriter() {
String tmpDir = TestUtils.createTempDir().getAbsolutePath();
String fileName = tmpDir + "BadKeyFile";
// Set up bad key writer
BlockingQueue<BadKeyStatus> bq = new ArrayBlockingQueue<BadKeyStatus>(5);
ExecutorService badKeyWriterService = Executors.newSingleThreadExecutor();
BadKeyWriter badKeyWriter = new BadKeyWriter(fileName, bq);
badKeyWriterService.submit(badKeyWriter);
// Enqueue stuff for bad key writer to write
try {
for(int i = 0; i < 100; ++i) {
BadKey badKey = new BadKey(Integer.toHexString(i), Integer.toHexString(i) + "\n");
bq.put(new BadKeyStatus(badKey, ConsistencyFix.Status.REPAIR_EXCEPTION));
}
// Poison bad key writer
bq.put(new BadKeyStatus());
} catch(InterruptedException e) {
e.printStackTrace();
fail("Unexpected exception");
}
// wait for bad key writer to shutdown
badKeyWriterService.shutdown();
try {
badKeyWriterService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch(InterruptedException e) {
e.printStackTrace();
fail("Unexpected exception");
}
assertFalse(badKeyWriter.hasException());
// Read output file & verify.
try {
BufferedReader fileReader = new BufferedReader(new FileReader(fileName));
int i = 0;
for(String keyLine = fileReader.readLine(); keyLine != null; keyLine = fileReader.readLine()) {
assertEquals(keyLine, Integer.toHexString(i));
i++;
}
} catch(FileNotFoundException fnfe) {
fnfe.printStackTrace();
fail("Unexpected exception");
} catch(IOException ioe) {
ioe.printStackTrace();
fail("Unexpected exception");
}
}
@Test
public void testConsistencyFixSetupTeardown() {
String url = setUpCluster();
ConsistencyFix consistencyFix = new ConsistencyFix(url, STORE_NAME, 100, 100, false, false);
consistencyFix.close();
}
@Test
public void testStats() throws InterruptedException {
ConsistencyFix.Stats stats = new Stats(1000);
long lastTimeMs = stats.lastTimeMs;
TimeUnit.MILLISECONDS.sleep(2);
for(int i = 0; i < 1001; ++i) {
stats.incrementFixCount();
}
assertTrue(stats.fixCount == 1001);
assertTrue(stats.startTimeMs < stats.lastTimeMs);
assertTrue(lastTimeMs < System.currentTimeMillis());
}
@Test
public void testStatus() {
ConsistencyFix.Status status = ConsistencyFix.Status.SUCCESS;
assertEquals(status.toString(), "success");
}
}