/**
* 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.apache.aurora.scheduler.storage.log;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import org.apache.aurora.gen.storage.Op;
import org.apache.aurora.gen.storage.PruneJobUpdateHistory;
import org.apache.aurora.gen.storage.RemoveJob;
import org.apache.aurora.gen.storage.RemoveLock;
import org.apache.aurora.gen.storage.RemoveQuota;
import org.apache.aurora.gen.storage.RemoveTasks;
import org.apache.aurora.gen.storage.RewriteTask;
import org.apache.aurora.gen.storage.SaveCronJob;
import org.apache.aurora.gen.storage.SaveFrameworkId;
import org.apache.aurora.gen.storage.SaveHostAttributes;
import org.apache.aurora.gen.storage.SaveJobInstanceUpdateEvent;
import org.apache.aurora.gen.storage.SaveJobUpdate;
import org.apache.aurora.gen.storage.SaveJobUpdateEvent;
import org.apache.aurora.gen.storage.SaveLock;
import org.apache.aurora.gen.storage.SaveQuota;
import org.apache.aurora.gen.storage.SaveTasks;
import org.apache.aurora.scheduler.events.EventSink;
import org.apache.aurora.scheduler.events.PubsubEvent;
import org.apache.aurora.scheduler.storage.AttributeStore;
import org.apache.aurora.scheduler.storage.CronJobStore;
import org.apache.aurora.scheduler.storage.JobUpdateStore;
import org.apache.aurora.scheduler.storage.LockStore;
import org.apache.aurora.scheduler.storage.QuotaStore;
import org.apache.aurora.scheduler.storage.SchedulerStore;
import org.apache.aurora.scheduler.storage.Storage.MutableStoreProvider;
import org.apache.aurora.scheduler.storage.TaskStore;
import org.apache.aurora.scheduler.storage.entities.IHostAttributes;
import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
import org.apache.aurora.scheduler.storage.entities.IJobInstanceUpdateEvent;
import org.apache.aurora.scheduler.storage.entities.IJobKey;
import org.apache.aurora.scheduler.storage.entities.IJobUpdate;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateEvent;
import org.apache.aurora.scheduler.storage.entities.IJobUpdateKey;
import org.apache.aurora.scheduler.storage.entities.ILock;
import org.apache.aurora.scheduler.storage.entities.ILockKey;
import org.apache.aurora.scheduler.storage.entities.IResourceAggregate;
import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
import org.slf4j.Logger;
import uno.perk.forward.Forward;
import static java.util.Objects.requireNonNull;
import static org.apache.aurora.scheduler.storage.log.LogStorage.TransactionManager;
/**
* Mutable stores implementation that translates all operations to {@link Op}s (which are passed
* to a provided {@link TransactionManager}) before forwarding the operations to delegate mutable
* stores.
*/
@Forward({
SchedulerStore.class,
CronJobStore.class,
TaskStore.class,
LockStore.class,
QuotaStore.class,
AttributeStore.class,
JobUpdateStore.class})
class WriteAheadStorage extends WriteAheadStorageForwarder implements
MutableStoreProvider,
SchedulerStore.Mutable,
CronJobStore.Mutable,
TaskStore.Mutable,
LockStore.Mutable,
QuotaStore.Mutable,
AttributeStore.Mutable,
JobUpdateStore.Mutable {
private final TransactionManager transactionManager;
private final SchedulerStore.Mutable schedulerStore;
private final CronJobStore.Mutable jobStore;
private final TaskStore.Mutable taskStore;
private final LockStore.Mutable lockStore;
private final QuotaStore.Mutable quotaStore;
private final AttributeStore.Mutable attributeStore;
private final JobUpdateStore.Mutable jobUpdateStore;
private final Logger log;
private final EventSink eventSink;
/**
* Creates a new write-ahead storage that delegates to the providing default stores.
*
* @param transactionManager External controller for transaction operations.
* @param schedulerStore Delegate.
* @param jobStore Delegate.
* @param taskStore Delegate.
* @param lockStore Delegate.
* @param quotaStore Delegate.
* @param attributeStore Delegate.
* @param jobUpdateStore Delegate.
*/
WriteAheadStorage(
TransactionManager transactionManager,
SchedulerStore.Mutable schedulerStore,
CronJobStore.Mutable jobStore,
TaskStore.Mutable taskStore,
LockStore.Mutable lockStore,
QuotaStore.Mutable quotaStore,
AttributeStore.Mutable attributeStore,
JobUpdateStore.Mutable jobUpdateStore,
Logger log,
EventSink eventSink) {
super(
schedulerStore,
jobStore,
taskStore,
lockStore,
quotaStore,
attributeStore,
jobUpdateStore);
this.transactionManager = requireNonNull(transactionManager);
this.schedulerStore = requireNonNull(schedulerStore);
this.jobStore = requireNonNull(jobStore);
this.taskStore = requireNonNull(taskStore);
this.lockStore = requireNonNull(lockStore);
this.quotaStore = requireNonNull(quotaStore);
this.attributeStore = requireNonNull(attributeStore);
this.jobUpdateStore = requireNonNull(jobUpdateStore);
this.log = requireNonNull(log);
this.eventSink = requireNonNull(eventSink);
}
private void write(Op op) {
Preconditions.checkState(
transactionManager.hasActiveTransaction(),
"Mutating operations must be within a transaction.");
transactionManager.log(op);
}
@Override
public void saveFrameworkId(final String frameworkId) {
requireNonNull(frameworkId);
write(Op.saveFrameworkId(new SaveFrameworkId(frameworkId)));
schedulerStore.saveFrameworkId(frameworkId);
}
@Override
public boolean unsafeModifyInPlace(final String taskId, final ITaskConfig taskConfiguration) {
requireNonNull(taskId);
requireNonNull(taskConfiguration);
boolean mutated = taskStore.unsafeModifyInPlace(taskId, taskConfiguration);
if (mutated) {
write(Op.rewriteTask(new RewriteTask(taskId, taskConfiguration.newBuilder())));
}
return mutated;
}
@Override
public void deleteTasks(final Set<String> taskIds) {
requireNonNull(taskIds);
write(Op.removeTasks(new RemoveTasks(taskIds)));
taskStore.deleteTasks(taskIds);
}
@Override
public void saveTasks(final Set<IScheduledTask> newTasks) {
requireNonNull(newTasks);
write(Op.saveTasks(new SaveTasks(IScheduledTask.toBuildersSet(newTasks))));
taskStore.saveTasks(newTasks);
}
@Override
public Optional<IScheduledTask> mutateTask(
String taskId,
Function<IScheduledTask, IScheduledTask> mutator) {
Optional<IScheduledTask> mutated = taskStore.mutateTask(taskId, mutator);
log.debug("Storing updated task to log: {}={}", taskId, mutated.get().getStatus());
write(Op.saveTasks(new SaveTasks(ImmutableSet.of(mutated.get().newBuilder()))));
return mutated;
}
@Override
public void saveQuota(final String role, final IResourceAggregate quota) {
requireNonNull(role);
requireNonNull(quota);
write(Op.saveQuota(new SaveQuota(role, quota.newBuilder())));
quotaStore.saveQuota(role, quota);
}
@Override
public boolean saveHostAttributes(final IHostAttributes attrs) {
requireNonNull(attrs);
boolean changed = attributeStore.saveHostAttributes(attrs);
if (changed) {
write(Op.saveHostAttributes(new SaveHostAttributes(attrs.newBuilder())));
eventSink.post(new PubsubEvent.HostAttributesChanged(attrs));
}
return changed;
}
@Override
public void removeJob(final IJobKey jobKey) {
requireNonNull(jobKey);
write(Op.removeJob(new RemoveJob().setJobKey(jobKey.newBuilder())));
jobStore.removeJob(jobKey);
}
@Override
public void saveAcceptedJob(final IJobConfiguration jobConfig) {
requireNonNull(jobConfig);
write(Op.saveCronJob(new SaveCronJob(jobConfig.newBuilder())));
jobStore.saveAcceptedJob(jobConfig);
}
@Override
public void removeQuota(final String role) {
requireNonNull(role);
write(Op.removeQuota(new RemoveQuota(role)));
quotaStore.removeQuota(role);
}
@Override
public void saveLock(final ILock lock) {
requireNonNull(lock);
write(Op.saveLock(new SaveLock(lock.newBuilder())));
lockStore.saveLock(lock);
}
@Override
public void removeLock(final ILockKey lockKey) {
requireNonNull(lockKey);
write(Op.removeLock(new RemoveLock(lockKey.newBuilder())));
lockStore.removeLock(lockKey);
}
@Override
public void saveJobUpdate(IJobUpdate update, Optional<String> lockToken) {
requireNonNull(update);
write(Op.saveJobUpdate(new SaveJobUpdate(update.newBuilder(), lockToken.orNull())));
jobUpdateStore.saveJobUpdate(update, lockToken);
}
@Override
public void saveJobUpdateEvent(IJobUpdateKey key, IJobUpdateEvent event) {
requireNonNull(key);
requireNonNull(event);
write(Op.saveJobUpdateEvent(new SaveJobUpdateEvent(event.newBuilder(), key.newBuilder())));
jobUpdateStore.saveJobUpdateEvent(key, event);
}
@Override
public void saveJobInstanceUpdateEvent(IJobUpdateKey key, IJobInstanceUpdateEvent event) {
requireNonNull(key);
requireNonNull(event);
write(Op.saveJobInstanceUpdateEvent(
new SaveJobInstanceUpdateEvent(event.newBuilder(), key.newBuilder())));
jobUpdateStore.saveJobInstanceUpdateEvent(key, event);
}
@Override
public Set<IJobUpdateKey> pruneHistory(int perJobRetainCount, long historyPruneThresholdMs) {
Set<IJobUpdateKey> prunedUpdates = jobUpdateStore.pruneHistory(
perJobRetainCount,
historyPruneThresholdMs);
if (!prunedUpdates.isEmpty()) {
// Pruned updates will eventually go away from persisted storage when a new snapshot is cut.
// So, persisting pruning attempts is not strictly necessary as the periodic pruner will
// provide eventual consistency between volatile and persistent storage upon scheduler
// restart. By generating an out of band pruning during log replay the consistency is
// achieved sooner without potentially exposing pruned but not yet persisted data.
write(Op.pruneJobUpdateHistory(
new PruneJobUpdateHistory(perJobRetainCount, historyPruneThresholdMs)));
}
return prunedUpdates;
}
@Override
public void deleteAllTasks() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
@Override
public void deleteHostAttributes() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
@Override
public void deleteJobs() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
@Override
public void deleteQuotas() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
@Override
public void deleteLocks() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
@Override
public void deleteAllUpdatesAndEvents() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
@Override
public SchedulerStore.Mutable getSchedulerStore() {
return this;
}
@Override
public CronJobStore.Mutable getCronJobStore() {
return this;
}
@Override
public TaskStore.Mutable getUnsafeTaskStore() {
return this;
}
@Override
public LockStore.Mutable getLockStore() {
return this;
}
@Override
public QuotaStore.Mutable getQuotaStore() {
return this;
}
@Override
public AttributeStore.Mutable getAttributeStore() {
return this;
}
@Override
public TaskStore getTaskStore() {
return this;
}
@Override
public JobUpdateStore.Mutable getJobUpdateStore() {
return this;
}
@Override
public <T> T getUnsafeStoreAccess() {
throw new UnsupportedOperationException(
"Unsupported since casual storage users should never be doing this.");
}
}