package com.rubiconproject.oss.kv.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import com.rubiconproject.oss.kv.KeyValueStore;
import com.rubiconproject.oss.kv.KeyValueStoreUnavailable;
import com.rubiconproject.oss.kv.backends.IterableKeyValueStore;
import com.rubiconproject.oss.kv.backends.KeyValueStoreIterator;
import com.rubiconproject.oss.kv.backends.UriConnectionFactory;
import com.rubiconproject.oss.kv.distributed.Node;
import com.rubiconproject.oss.kv.distributed.impl.DistributedKeyValueStoreClientImpl;
import com.rubiconproject.oss.kv.distributed.impl.PropertiesConfigurator;
import com.rubiconproject.oss.kv.transcoder.ByteArrayTranscoder;
import com.rubiconproject.oss.kv.transcoder.Transcoder;
/**
* Valkyrie rebalancing job.
*
* Given an input node (by uri) and a destination valkryie store, will:
* 1) Write data that does NOT belong on the input node to the output store
* 2) Delete successful writes from above on the input node
*
* Usage:
* - create a valkyrie properties file (in, say /tmp/valkyrie.properties) like
* nodestore.implementation=com.othersonline.kv.distributed.impl.JdbcNodeStore
* nodeStore.jdbcDriver=com.mysql.jdbc.Driver
* nodeStore.jdbcUrl=jdbc:mysql://dev-db/oz_central
* nodeStore.jdbcUsername=username
* nodeStore.jdbcPassword=s3cret
* nodeStore.id=1
* read.timeout = 5000
* read.replicas = 3
* read.required = 1
* write.timeout = 10000
* write.replicas = 3
* write.required = 3
* backfill.nullGets = false
* backfill.failedGets = false
*
* - Find the node id of the source node (say 12)
*
* - Run it:
* java -classpath oo-kv-storage.jar:... com.othersonline.kv.tools.ValkyrieRebalance --source "tyrant://dev-db:1978" --node 12 --sleep 20 --properties /tmp/valkyrie.properties
*
* @author stingleff
*
*/
public class ValkyrieRebalance implements Runnable, Callable<Map<String, Long>> {
@Option(name = "--source", usage = "Source uri (default: none)", required = true)
private String source;
@Option(name = "--node", usage = "Source node id (default: none)", required = true)
private int nodeId;
@Option(name = "--properties", usage = "Properties file for destination valkyrie client (default: none)", required = true)
private String properties;
@Option(name = "--sleep", usage = "Sleep time between keys (millis) (default: disabled)")
private long sleep = 0;
@Option(name = "--skip", usage = "Skip this many records first (default: 0)")
private int skip = 0;
@Option(name = "--max", usage = "Stop after x keys (default: disabled)")
private int max = -1;
@Option(name = "--delete", usage = "Delete from source (default: false")
private boolean delete = false;
private Transcoder byteTranscoder = new ByteArrayTranscoder();
private Properties props;
public static void main(String[] args) throws Exception {
ValkyrieRebalance vr = new ValkyrieRebalance();
CmdLineParser parser = new CmdLineParser(vr);
parser.parseArgument(args);
Map<String, Long> stats = vr.call();
for (Map.Entry<String, Long> entry : stats.entrySet()) {
System.out.println(String.format("%1$s\t%2$d", entry.getKey(),
entry.getValue()));
}
System.out.println("Completed successfully. Exiting.");
System.exit(0);
}
public void run() {
try {
call();
} catch (Exception e) {
e.printStackTrace();
}
}
public Map<String, Long> call() throws Exception {
props = getProperties();
IterableKeyValueStore src = getSource();
DistributedKeyValueStoreClientImpl valkyrie = getDestination();
long examined = 0, moved = 0, notMoved = 0, getFailures = 0, setFailures = 0, deleteFailures = 0;
int writeReplicas = Integer.parseInt(props
.getProperty("write.replicas"));
KeyValueStoreIterator keyIterator = src.iterkeys();
try {
Iterator<String> iter = keyIterator.iterator();
while (iter.hasNext()) {
String key = iter.next();
++examined;
if (examined <= skip)
continue;
List<Node> preferenceList = valkyrie.getPreferenceList(key,
writeReplicas);
boolean set = true;
for (Node node : preferenceList) {
if (node.getId() == nodeId) {
set = false;
break;
}
}
if (set) {
byte[] bytes = null;
boolean successfulMove = false;
try {
bytes = (byte[]) src.get(key, byteTranscoder);
} catch (Exception e) {
e.printStackTrace();
++getFailures;
}
try {
if (bytes != null) {
valkyrie.set(key, bytes, byteTranscoder);
successfulMove = true;
++moved;
}
} catch (Exception e) {
e.printStackTrace();
++setFailures;
}
try {
if (successfulMove && delete)
src.delete(key);
} catch (Exception e) {
e.printStackTrace();
++deleteFailures;
}
} else
++notMoved;
if (examined % 1000 == 0) {
System.out.println("Status");
System.out.println("examined: " + examined);
System.out.println("moved: " + moved);
System.out.println("getFailures: " + getFailures);
System.out.println("setFailures: " + setFailures);
System.out.println("deleteFailures: " + deleteFailures);
}
if ((max > 0) && (examined >= max))
break;
if (sleep > 0)
Thread.sleep(sleep);
}
} finally {
keyIterator.close();
}
Map<String, Long> results = new HashMap<String, Long>();
results.put("examined", examined);
results.put("moved", moved);
results.put("not-moved", notMoved);
results.put("get-failures", getFailures);
results.put("set-failures", setFailures);
results.put("delete-failures", deleteFailures);
return results;
}
private IterableKeyValueStore getSource() throws KeyValueStoreUnavailable,
IOException {
UriConnectionFactory factory = new UriConnectionFactory();
KeyValueStore src = factory.getStore(props, source);
if (!(src instanceof IterableKeyValueStore)) {
throw new IllegalArgumentException(
String
.format(
"Source must implement the IterableKeyValueStore interface. %1$s from %2$s does not.",
src, source));
} else
return (IterableKeyValueStore) src;
}
private DistributedKeyValueStoreClientImpl getDestination()
throws FileNotFoundException, IOException {
PropertiesConfigurator configurator = new PropertiesConfigurator();
configurator.load(props);
DistributedKeyValueStoreClientImpl dest = new DistributedKeyValueStoreClientImpl();
dest.setConfigurator(configurator);
dest.start();
return dest;
}
private Properties getProperties() throws IOException {
Properties props = new Properties();
File file = new File(properties);
FileInputStream fis = new FileInputStream(file);
try {
props.load(fis);
} finally {
fis.close();
}
return props;
}
}