/*
* Copyright 2016 The Simple File Server Authors
*
* 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 org.sfs.jobs;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import org.sfs.Server;
import org.sfs.VertxContext;
import org.sfs.nodes.Nodes;
import org.sfs.rx.Defer;
import org.sfs.rx.ToVoid;
import org.sfs.util.HttpStatusCodeException;
import rx.Observable;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static io.vertx.core.logging.LoggerFactory.getLogger;
import static org.sfs.rx.Defer.aVoid;
public class Jobs {
public static class ID {
public static final String RE_ENCRYPT_CONTAINER_KEYS = "re_encrypt_container_keys";
public static final String RE_ENCRYPT_MASTER_KEYS = "re_encrypt_master_keys";
public static final String REPAIR_MASTER_KEYS = "repair_master_keys";
public static final String VERIFY_REPAIR_CONTAINER_OBJECTS = "verify_repair_container_objects";
public static final String VERIFY_REPAIR_ALL_CONTAINERS_OBJECTS = "verify_repair_all_container_objects";
public static final String VERIFY_REPAIR_OBJECT = "verify_repair_object";
public static final String ASSIGN_DOCUMENTS_TO_NODE = "assign_documents_to_node";
}
public static class Parameters {
public static final String JOB_ID = "job_id";
public static final String CONTAINER_ID = "container_id";
public static final String OBJECT_ID = "object_id";
public static final String TIMEOUT = "timeout";
public static final String FORCE_REMOVE_VOLUMES = "force-remove-volumes";
}
private static final Logger LOGGER = getLogger(Jobs.class);
private AtomicBoolean started = new AtomicBoolean(false);
private Map<String, Class<? extends Job>> jobs = new HashMap<>();
private ConcurrentMap<String, Job> runningJobs = new ConcurrentHashMap<>();
public Jobs() {
AssignDocumentsToNodeJob assignDocumentsToNodeJob = new AssignDocumentsToNodeJob();
VerifyRepairAllContainerObjects verifyRepairAllContainerObjects = new VerifyRepairAllContainerObjects();
VerifyRepairContainerObjects verifyRepairContainerObjects = new VerifyRepairContainerObjects();
VerifyRepairObject verifyRepairObject = new VerifyRepairObject();
ReEncryptContainerKeys reEncryptContainerKeys = new ReEncryptContainerKeys();
ReEncryptMasterKeys reEncryptMasterKeys = new ReEncryptMasterKeys();
RepairMasterKeys repairMasterKeys = new RepairMasterKeys();
register(assignDocumentsToNodeJob);
register(verifyRepairAllContainerObjects);
register(verifyRepairContainerObjects);
register(verifyRepairObject);
register(reEncryptContainerKeys);
register(reEncryptMasterKeys);
register(repairMasterKeys);
}
public Observable<Void> open(VertxContext<Server> vertxContext, JsonObject config) {
return aVoid()
.filter(aVoid -> started.compareAndSet(false, true))
.singleOrDefault(null);
}
public Observable<Void> stop(VertxContext<Server> vertxContext, String jobId) {
return Defer.aVoid()
.doOnNext(aVoid -> checkMaster(vertxContext))
.map(aVoid -> getJob(jobId))
.map(jobClass -> runningJobs.get(jobId))
.flatMap(job -> {
if (job != null) {
return job.stop(vertxContext);
} else {
return Defer.aVoid();
}
});
}
public Observable<Void> waitStopped(VertxContext<Server> vertxContext, String jobId) {
return Defer.aVoid()
.doOnNext(aVoid -> checkMaster(vertxContext))
.map(aVoid -> getJob(jobId))
.map(jobClass -> runningJobs.get(jobId))
.flatMap(job -> {
if (job != null) {
return job.waitStopped(vertxContext);
} else {
return Defer.aVoid();
}
});
}
public Observable<Void> waitStopped(VertxContext<Server> vertxContext, String jobId, long timeout, TimeUnit timeUnit) {
return Defer.aVoid()
.doOnNext(aVoid -> checkMaster(vertxContext))
.map(aVoid -> getJob(jobId))
.map(jobClass -> runningJobs.get(jobId))
.flatMap(job -> {
if (job != null) {
return job.waitStopped(vertxContext, timeout, timeUnit);
} else {
return Defer.aVoid();
}
});
}
public Observable<Void> execute(VertxContext<Server> vertxContext, String jobId, MultiMap parameters) {
return Defer.aVoid()
.doOnNext(aVoid -> checkMaster(vertxContext))
.map(aVoid -> getJob(jobId))
.map(this::newInstance)
.flatMap(job ->
Observable.using(
() -> runningJobs.putIfAbsent(jobId, job) == null,
running -> {
if (running) {
return job.execute(vertxContext, parameters);
} else {
return Observable.error(new JobAlreadyRunning());
}
},
running -> {
if (running) {
runningJobs.remove(jobId);
}
}));
}
public Observable<Void> close(VertxContext<Server> vertxContext) {
return aVoid()
.filter(aVoid -> started.compareAndSet(true, false))
.onErrorResumeNext(throwable -> {
LOGGER.warn("Handling error", throwable);
return Defer.aVoid();
})
.flatMap(aVoid ->
Observable.from(runningJobs.values())
.flatMap(job -> job.stop(vertxContext))
.onErrorResumeNext(throwable -> {
LOGGER.warn("Handling error", throwable);
return Defer.aVoid();
})
.count()
.map(new ToVoid<>()))
.singleOrDefault(null);
}
private void checkMaster(VertxContext<Server> vertxContext) {
Nodes nodes = vertxContext.verticle().nodes();
if (!nodes.isMaster()) {
throw new JobMustRunOnMaster();
}
}
private void register(Job job) {
jobs.put(job.id(), job.getClass());
}
private Class<? extends Job> getJob(String jobId) {
Class<? extends Job> job = jobs.get(jobId);
if (job == null) {
throw new JobNotFound();
}
return job;
}
private Job newInstance(Class<? extends Job> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static class JobNotFound extends HttpStatusCodeException {
public JobNotFound() {
super(HttpURLConnection.HTTP_NOT_FOUND);
}
}
public static class JobMustRunOnMaster extends HttpStatusCodeException {
public JobMustRunOnMaster() {
super(HttpURLConnection.HTTP_FORBIDDEN);
}
}
public static class JobAlreadyRunning extends HttpStatusCodeException {
public JobAlreadyRunning() {
super(HttpURLConnection.HTTP_CONFLICT);
}
}
public static class WaitStoppedExpired extends HttpStatusCodeException {
public WaitStoppedExpired() {
super(HttpURLConnection.HTTP_CONFLICT);
}
}
}