/**
* 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.backup;
import java.io.File;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import org.apache.aurora.common.application.Lifecycle;
import org.apache.aurora.common.args.Arg;
import org.apache.aurora.common.args.CmdLine;
import org.apache.aurora.common.args.constraints.NotNull;
import org.apache.aurora.common.base.Command;
import org.apache.aurora.common.quantity.Amount;
import org.apache.aurora.common.quantity.Time;
import org.apache.aurora.gen.storage.Snapshot;
import org.apache.aurora.scheduler.base.AsyncUtil;
import org.apache.aurora.scheduler.storage.SnapshotStore;
import org.apache.aurora.scheduler.storage.backup.Recovery.RecoveryImpl;
import org.apache.aurora.scheduler.storage.backup.StorageBackup.StorageBackupImpl;
import org.apache.aurora.scheduler.storage.backup.StorageBackup.StorageBackupImpl.BackupConfig;
import org.apache.aurora.scheduler.storage.backup.TemporaryStorage.TemporaryStorageFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.requireNonNull;
/**
* A module that will periodically save full storage backups to local disk and makes those backups
* available for on-line recovery.
*/
public class BackupModule extends PrivateModule {
private static final Logger LOG = LoggerFactory.getLogger(BackupModule.class);
@CmdLine(name = "backup_interval", help = "Minimum interval on which to write a storage backup.")
private static final Arg<Amount<Long, Time>> BACKUP_INTERVAL =
Arg.create(Amount.of(1L, Time.HOURS));
@CmdLine(name = "max_saved_backups",
help = "Maximum number of backups to retain before deleting the oldest backups.")
private static final Arg<Integer> MAX_SAVED_BACKUPS = Arg.create(48);
@NotNull
@CmdLine(name = "backup_dir",
help = "Directory to store backups under. Will be created if it does not exist.")
private static final Arg<File> BACKUP_DIR = Arg.create();
private final Class<? extends SnapshotStore<Snapshot>> snapshotStore;
private final File unvalidatedBackupDir;
/**
* Creates a new backup module.
*
* @param snapshotStore Snapshot store implementation class.
*/
public BackupModule(Class<? extends SnapshotStore<Snapshot>> snapshotStore) {
this(BACKUP_DIR.get(), snapshotStore);
}
/**
* Creates a new backup module using a given backupDir instead of a flagged one.
*
* @param backupDir Directory to write backups to.
* @param snapshotStore Snapshot store implementation class.
*/
@VisibleForTesting
public BackupModule(File backupDir, Class<? extends SnapshotStore<Snapshot>> snapshotStore) {
this.unvalidatedBackupDir = requireNonNull(backupDir);
this.snapshotStore = requireNonNull(snapshotStore);
}
@Override
protected void configure() {
Executor executor = AsyncUtil.singleThreadLoggingScheduledExecutor("StorageBackup-%d", LOG);
bind(Executor.class).toInstance(executor);
TypeLiteral<SnapshotStore<Snapshot>> type = new TypeLiteral<SnapshotStore<Snapshot>>() { };
bind(type).annotatedWith(StorageBackupImpl.SnapshotDelegate.class).to(snapshotStore);
bind(type).to(StorageBackupImpl.class);
bind(StorageBackup.class).to(StorageBackupImpl.class);
bind(StorageBackupImpl.class).in(Singleton.class);
expose(type);
expose(StorageBackup.class);
bind(new TypeLiteral<Function<Snapshot, TemporaryStorage>>() { })
.to(TemporaryStorageFactory.class);
bind(Command.class).to(LifecycleHook.class);
bind(Recovery.class).to(RecoveryImpl.class);
bind(RecoveryImpl.class).in(Singleton.class);
expose(Recovery.class);
}
static class LifecycleHook implements Command {
private final Lifecycle lifecycle;
@Inject LifecycleHook(Lifecycle lifecycle) {
this.lifecycle = requireNonNull(lifecycle);
}
@Override
public void execute() {
lifecycle.shutdown();
}
}
@Provides
File provideBackupDir() {
if (!unvalidatedBackupDir.exists()) {
if (unvalidatedBackupDir.mkdirs()) {
LOG.info("Created backup dir " + unvalidatedBackupDir.getPath() + ".");
} else {
throw new IllegalArgumentException(
"Unable to create backup dir " + unvalidatedBackupDir.getPath() + ".");
}
}
if (!unvalidatedBackupDir.canWrite()) {
throw new IllegalArgumentException(
"Backup dir " + unvalidatedBackupDir.getPath() + " is not writable.");
}
return unvalidatedBackupDir;
}
@Provides
BackupConfig provideBackupConfig(File backupDir) {
return new BackupConfig(backupDir, MAX_SAVED_BACKUPS.get(), BACKUP_INTERVAL.get());
}
}