/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE file at the root of the source
* tree and available online at
*
* https://github.com/keeps/roda
*/
package org.roda.core.plugins.orchestrate;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.roda.core.data.exceptions.JobAlreadyStartedException;
import org.roda.core.data.exceptions.JobException;
import org.roda.core.data.v2.IsRODAObject;
import org.roda.core.data.v2.index.IsIndexed;
import org.roda.core.data.v2.index.filter.Filter;
import org.roda.core.data.v2.jobs.Job;
import org.roda.core.index.IndexService;
import org.roda.core.model.ModelService;
import org.roda.core.plugins.Plugin;
import org.roda.core.plugins.PluginOrchestrator;
import org.roda.core.plugins.orchestrate.akka.Messages.JobPartialUpdate;
import org.roda.core.plugins.orchestrate.akka.distributed.AkkaDistributedPlugin;
import org.roda.core.plugins.orchestrate.akka.distributed.Frontend;
import org.roda.core.plugins.orchestrate.akka.distributed.Master;
import org.roda.core.storage.StorageService;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import akka.actor.ActorIdentity;
import akka.actor.ActorPath;
import akka.actor.ActorPaths;
import akka.actor.ActorRef;
import akka.actor.ActorSelection;
import akka.actor.ActorSystem;
import akka.actor.Identify;
import akka.actor.PoisonPill;
import akka.actor.Props;
import akka.cluster.singleton.ClusterSingletonManager;
import akka.cluster.singleton.ClusterSingletonManagerSettings;
import akka.dispatch.OnFailure;
import akka.dispatch.OnSuccess;
import akka.pattern.Patterns;
import akka.persistence.journal.leveldb.SharedLeveldbJournal;
import akka.persistence.journal.leveldb.SharedLeveldbStore;
import akka.util.Timeout;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
/*
* Based on
* > http://www.typesafe.com/activator/template/akka-distributed-workers
* > https://github.com/typesafehub/activator-akka-distributed-workers-java
* */
public class AkkaDistributedPluginOrchestrator extends AkkaDistributedPlugin implements PluginOrchestrator {
private final IndexService index;
private final ModelService model;
private final StorageService storage;
private final ActorSystem clusterSystem;
private final ActorRef frontend;
public AkkaDistributedPluginOrchestrator(String hostname, String port) {
super();
index = getIndex();
model = getModel();
storage = getStorage();
String role = "backend";
String systemName = "ClusterSystem";
String systemPath = systemName + "@" + hostname + ":" + port;
Config conf = ConfigFactory.parseString("akka.cluster.roles=[" + role + "]")
.withFallback(ConfigFactory.parseString("akka.cluster.seed-nodes=[\"akka.tcp://" + systemPath + "\"]"))
.withFallback(ConfigFactory.parseString("akka.remote.netty.tcp.hostname=" + hostname))
.withFallback(ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port))
.withFallback(ConfigFactory.load("config/orchestrator/application"));
clusterSystem = ActorSystem.create(systemName, conf);
startupSharedJournal(getClusterSystem(), true, ActorPaths.fromString("akka.tcp://" + systemPath + "/user/store"));
FiniteDuration workTimeout = Duration.create(10, "seconds");
getClusterSystem().actorOf(ClusterSingletonManager.props(Master.props(workTimeout), PoisonPill.getInstance(),
ClusterSingletonManagerSettings.create(getClusterSystem()).withRole(role)), "master");
frontend = clusterSystem.actorOf(Props.create(Frontend.class), "frontend");
}
private static void startupSharedJournal(final ActorSystem system, boolean startStore, final ActorPath path) {
// Start the shared journal on one node (don't crash this SPOF)
// This will not be needed with a distributed journal
if (startStore) {
system.actorOf(Props.create(SharedLeveldbStore.class), "store");
}
// register the shared journal
Timeout timeout = new Timeout(15, TimeUnit.SECONDS);
ActorSelection actorSelection = system.actorSelection(path);
Future<Object> f = Patterns.ask(actorSelection, new Identify(null), timeout);
f.onSuccess(new OnSuccess<Object>() {
@Override
public void onSuccess(Object arg0) throws Throwable {
if (arg0 instanceof ActorIdentity && ((ActorIdentity) arg0).getRef() != null) {
SharedLeveldbJournal.setStore(((ActorIdentity) arg0).getRef(), system);
} else {
system.log().error("Lookup of shared journal at {} timed out", path);
System.exit(-1);
}
}
}, system.dispatcher());
f.onFailure(new OnFailure() {
@Override
public void onFailure(Throwable ex) throws Throwable {
system.log().error(ex, "Lookup of shared journal at {} timed out", path);
}
}, system.dispatcher());
}
public ActorSystem getClusterSystem() {
return clusterSystem;
}
@Override
public void setup() {
// do nothing
}
@Override
public void shutdown() {
// do nothing
}
@Override
public <T extends IsRODAObject, T1 extends IsIndexed> void runPluginFromIndex(Object context, Class<T1> classToActOn,
Filter filter, Plugin<T> plugin) {
// do nothing
}
@Override
public <T extends IsRODAObject> void runPlugin(Object context, Plugin<T> plugin) {
// do nothing
}
@Override
public void executeJob(Job job, boolean async) throws JobAlreadyStartedException {
// do nothing
}
@Override
public void stopJob(Job job) {
// do nothing
}
@Override
public void cleanUnfinishedJobs() {
// do nothing
}
@Override
public void setJobContextInformation(String jobId, Object object) {
// do nothing
}
@Override
public <T extends IsRODAObject> void updateJobInformation(Plugin<T> plugin, JobPluginInfo jobPluginInfo)
throws JobException {
// do nothing
}
@Override
public <T extends IsRODAObject> void updateJob(Plugin<T> plugin, JobPartialUpdate partialUpdate) {
// do nothing
}
@Override
public <T extends IsRODAObject> void runPluginOnAllObjects(Object context, Plugin<T> plugin, Class<T> objectClass) {
// do nothing
}
@Override
public <T extends IsRODAObject> void runPluginOnObjects(Object context, Plugin<T> plugin, Class<T> objectClass,
List<String> uuids) {
// do nothing
}
@Override
public void setJobInError(String jobId) {
// do nothing
}
}