package org.hivedb.management.migration;
import org.hivedb.Hive;
import org.hivedb.HiveLockableException;
import org.hivedb.meta.Node;
import org.hivedb.meta.PartitionDimension;
import org.hivedb.meta.directory.DbDirectory;
import org.hivedb.meta.directory.KeySemaphore;
import org.hivedb.util.Lists;
import org.hivedb.util.functional.Collect;
import org.hivedb.util.functional.Pair;
import org.hivedb.util.functional.Transform;
import org.hivedb.util.functional.Unary;
import java.util.Collection;
import java.util.List;
public class HiveMigrator implements Migrator {
private Hive hive;
private PartitionDimension dimension;
public HiveMigrator(Hive hive) {
this.hive = hive;
this.dimension = hive.getPartitionDimension();
}
@SuppressWarnings("unchecked")
public void deepNodeToNodeCopy(Object migrant, Node origin, Node destination, PartitionKeyMover mover) {
try {
//Copy the Partition Key Instance
mover.copy(migrant, destination);
//Copy all dependent records
for (Pair<Mover, KeyLocator> p : (List<Pair<Mover, KeyLocator>>) mover.getDependentMovers()) {
final KeyLocator value = p.getValue();
for (Object childKey : value.findAll(migrant)) {
Mover childMover = p.getKey();
Object child = childMover.get(childKey, origin);
childMover.copy(child, destination);
}
}
} catch (RuntimeException e) {
throw new MigrationException(
String.format("An error occured while copying records from node %s to node %s. Records may be orphaned on node %s",
destination.getName(),
origin.getName(),
destination.getName()), e);
}
}
@SuppressWarnings("unchecked")
public void cascadeDelete(Object migrant, Node node, PartitionKeyMover mover) {
//Delete dependent records first, just in case there are FKs.
for (Pair<Mover, KeyLocator> p : (List<Pair<Mover, KeyLocator>>) mover.getDependentMovers()) {
for (Object childKey : p.getValue().findAll(migrant)) {
Mover childMover = p.getKey();
Object child = childMover.get(childKey, node);
childMover.delete(child, node);
}
}
//Delete the primary index instance.
mover.delete(migrant, node);
}
/* (non-Javadoc)
* @see org.hivedb.management.migration.Migrator#migrate(java.lang.Object, java.lang.String, java.lang.String, org.hivedb.management.migration.PartitionKeyMover)
*/
@SuppressWarnings("unchecked")
public void move(Object key, Node origin, Node destination, PartitionKeyMover mover) {
Object migrant = mover.get(key, origin);
try {
//Copy the Partition Key Instance
mover.copy(migrant, destination);
//Copy all dependent records
for (Pair<Mover, KeyLocator> p : (List<Pair<Mover, KeyLocator>>) mover.getDependentMovers()) {
for (Object childKey : p.getValue().findAll(migrant)) {
Mover childMover = p.getKey();
Object child = childMover.get(childKey, origin);
childMover.copy(child, destination);
}
}
//Update the partition key location
// hive.updatePrimaryIndexNode(dimension, key, destination);
// } catch (HiveException e) {
// throw new MigrationException(
// String.format("Failed to update directory entry for %s. Records may be orphaned on node %s",
// key,
// destination.getName()), e);
} catch (RuntimeException e) {
throw new MigrationException(
String.format("An error occured while copying records from node %s to node %s. Records may be orphaned on node %s",
destination.getName(),
origin.getName(),
destination.getName()), e);
}
//Delete dependent records first, just in case there are FKs.
for (Pair<Mover, KeyLocator> p : (List<Pair<Mover, KeyLocator>>) mover.getDependentMovers()) {
for (Object childKey : p.getValue().findAll(migrant)) {
Mover childMover = p.getKey();
Object child = childMover.get(childKey, origin);
childMover.delete(child, origin);
}
}
//Delete the primary index instance.
mover.delete(migrant, origin);
}
private Node getNode(int id) {
return hive.getNode(id);
}
private Node getNode(String id) {
return hive.getNode(id);
}
private void lock(Object key) {
try {
hive.directory().updatePrimaryIndexKeyReadOnly(key, true);
} catch (HiveLockableException e) {
throw new MigrationException("Failed to lock partition key " + key + " for writing.", e);
}
}
private void unlock(Object key) {
try {
hive.directory().updatePrimaryIndexKeyReadOnly(key, false);
} catch (HiveLockableException e) {
throw new MigrationException("Failed to unlock partition key " + key + " for writing.", e);
}
}
public void migrate(Object key, Collection<String> destinationNames, PartitionKeyMover mover) {
Collection<Node> destinations = Collect.amass(new Unary<String, Node>() {
public Node f(String item) {
return getNode(item);
}
}, destinationNames);
doMigration(key, destinations, mover);
}
private void doMigration(Object key, Collection<Node> destinations, PartitionKeyMover mover) {
try {
lock(key);
DbDirectory dir = new DbDirectory(dimension);
Collection<Node> origins = Transform.map(new Unary<KeySemaphore, Node>() {
public Node f(KeySemaphore keySemaphore) {
return getNode(keySemaphore.getNodeId());
}
}, dir.getKeySemamphoresOfPrimaryIndexKey(key));
//Elect a random origin node as the authority
Node authority = Lists.random(origins);
Object migrant = mover.get(key, authority);
//Copy the records
for (Node destination : destinations) {
try {
deepNodeToNodeCopy(migrant, authority, destination, mover);
} catch (RuntimeException e) {
throw new MigrationException(String.format("Error while copying records to node %s", destination.getName()), e);
}
}
//Update the directory entries
try {
dir.deletePrimaryIndexKey(key);
for (Node destination : destinations)
dir.insertPrimaryIndexKey(destination, key);
} catch (RuntimeException e) {
try {
//try to repair the damage
for (Node origin : origins)
dir.insertPrimaryIndexKey(origin, key);
} catch (Exception ex) {
}
throw new MigrationException(
String.format("Failed to update directory entry for %s. Records may be orphaned.",
key), e);
}
for (Node node : origins) {
try {
cascadeDelete(migrant, node, mover);
}
catch (RuntimeException e) {
throw new MigrationException(String.format("Error deleting old records on node %s", node.getName()), e);
}
}
} finally {
unlock(key);
}
}
}