blob: b7a43410214761cbed1a83f8e8277ce7c231cc01 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.geode.internal.cache.backup;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.apache.logging.log4j.Logger;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.cache.DiskStore;
import org.apache.geode.cache.persistence.PersistentID;
import org.apache.geode.internal.cache.DiskStoreImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.Oplog;
import org.apache.geode.internal.logging.LogService;
/**
* This class manages the state an logic to backup a single cache.
*/
class BackupTask {
private static final Logger logger = LogService.getLogger();
private final Map<DiskStoreImpl, DiskStoreBackup> backupByDiskStore = new HashMap<>();
private final RestoreScript restoreScript = new RestoreScript();
private final InternalCache cache;
private final CountDownLatch allowDestroys = new CountDownLatch(1);
private final CountDownLatch locksAcquired = new CountDownLatch(1);
private final CountDownLatch otherMembersReady = new CountDownLatch(1);
private final HashSet<PersistentID> diskStoresWithData = new HashSet<>();
private final BackupWriter backupWriter;
private volatile boolean isCancelled;
private TemporaryBackupFiles temporaryFiles;
private BackupFileCopier fileCopier;
BackupTask(InternalCache cache, BackupWriter backupWriter) {
this.cache = cache;
this.backupWriter = backupWriter;
}
HashSet<PersistentID> getPreparedDiskStores() throws InterruptedException {
locksAcquired.await();
return diskStoresWithData;
}
void notifyOtherMembersReady() {
otherMembersReady.countDown();
}
HashSet<PersistentID> backup() throws InterruptedException, IOException {
prepareForBackup();
locksAcquired.countDown();
try {
otherMembersReady.await();
} catch (InterruptedException e) {
cleanup();
throw e;
}
if (isCancelled) {
cleanup();
return new HashSet<>();
}
return doBackup();
}
private void prepareForBackup() {
for (DiskStore store : cache.listDiskStoresIncludingRegionOwned()) {
DiskStoreImpl storeImpl = (DiskStoreImpl) store;
storeImpl.lockStoreBeforeBackup();
if (logger.isDebugEnabled()) {
logger.debug("Acquired lock for backup on disk store {}", store.getName());
}
if (storeImpl.hasPersistedData()) {
diskStoresWithData.add(storeImpl.getPersistentID());
storeImpl.getStats().startBackup();
}
}
}
private HashSet<PersistentID> doBackup() throws IOException {
if (isCancelled) {
cleanup();
return new HashSet<>();
}
try {
Collection<DiskStore> diskStores = cache.listDiskStoresIncludingRegionOwned();
temporaryFiles = TemporaryBackupFiles.create();
fileCopier = new BackupFileCopier(cache, temporaryFiles);
Map<DiskStoreImpl, DiskStoreBackup> backupByDiskStores = startDiskStoreBackups(diskStores);
allowDestroys.countDown();
HashSet<PersistentID> persistentIds = finishDiskStoreBackups(backupByDiskStores);
if (!backupByDiskStores.isEmpty()) {
backupAdditionalFiles();
BackupDefinition backupDefinition = fileCopier.getBackupDefinition();
backupDefinition.setRestoreScript(restoreScript);
backupWriter.backupFiles(backupDefinition);
}
return persistentIds;
} finally {
cleanup();
}
}
private HashSet<PersistentID> finishDiskStoreBackups(
Map<DiskStoreImpl, DiskStoreBackup> backupByDiskStores) throws IOException {
HashSet<PersistentID> persistentIds = new HashSet<>();
for (Map.Entry<DiskStoreImpl, DiskStoreBackup> entry : backupByDiskStores.entrySet()) {
DiskStoreImpl diskStore = entry.getKey();
completeBackup(diskStore, entry.getValue());
diskStore.getStats().endBackup();
persistentIds.add(diskStore.getPersistentID());
}
return persistentIds;
}
private Map<DiskStoreImpl, DiskStoreBackup> startDiskStoreBackups(
Collection<DiskStore> diskStores) throws IOException {
Map<DiskStoreImpl, DiskStoreBackup> backupByDiskStore = new HashMap<>();
for (DiskStore store : diskStores) {
DiskStoreImpl diskStore = (DiskStoreImpl) store;
try {
if (diskStore.hasPersistedData()) {
DiskStoreBackup backup = startDiskStoreBackup(diskStore);
backupByDiskStore.put(diskStore, backup);
}
} finally {
diskStore.releaseBackupLock();
if (logger.isDebugEnabled()) {
logger.debug("Released lock for backup on disk store {}", store.getName());
}
}
}
return backupByDiskStore;
}
void abort() {
isCancelled = true;
otherMembersReady.countDown();
}
boolean isCancelled() {
return isCancelled;
}
void waitTillBackupFilesAreCopiedToTemporaryLocation() {
try {
allowDestroys.await();
} catch (InterruptedException e) {
throw new InternalGemFireError(e);
}
}
private void cleanup() {
isCancelled = true;
allowDestroys.countDown();
if (temporaryFiles != null) {
temporaryFiles.cleanupFiles();
}
releaseBackupLocks();
}
private void releaseBackupLocks() {
for (DiskStore store : cache.listDiskStoresIncludingRegionOwned()) {
((DiskStoreImpl) store).releaseBackupLock();
}
}
private void backupAdditionalFiles() throws IOException {
fileCopier.copyConfigFiles();
fileCopier.copyUserFiles();
fileCopier.copyDeployedJars();
}
/**
* Copy the oplogs to the backup directory. This is the final step of the backup process. The
* oplogs we copy are defined in the startDiskStoreBackup method.
*/
private void completeBackup(DiskStoreImpl diskStore, DiskStoreBackup backup) throws IOException {
if (backup == null) {
return;
}
try {
// Wait for oplogs to be unpreblown before backing them up.
diskStore.waitForDelayedWrites();
// Backup all of the oplogs
for (Oplog oplog : backup.getPendingBackup()) {
if (isCancelled()) {
break;
}
oplog.finishKrf();
fileCopier.copyOplog(diskStore, oplog);
// Allow the oplog to be deleted, and process any pending delete
backup.backupFinished(oplog);
}
} finally {
backup.cleanup();
}
}
/**
* Start the backup process. This is the second step of the backup process. In this method, we
* define the data we're backing up by copying the init file and rolling to the next file. After
* this method returns operations can proceed as normal, except that we don't remove oplogs.
*/
private DiskStoreBackup startDiskStoreBackup(DiskStoreImpl diskStore) throws IOException {
DiskStoreBackup backup = null;
boolean done = false;
try {
for (;;) {
Oplog childOplog = diskStore.getPersistentOplogSet().getChild();
if (childOplog == null) {
backup = new DiskStoreBackup(new Oplog[0]);
backupByDiskStore.put(diskStore, backup);
break;
}
// Get an appropriate lock object for each set of oplogs.
Object childLock = childOplog.getLock();
// TODO: We really should move this lock into the disk store, but until then we need to do
// this magic to make sure we're actually locking the latest child for both types of oplogs
// This ensures that all writing to disk is blocked while we are creating the snapshot
synchronized (childLock) {
if (logger.isDebugEnabled()) {
logger.debug("Synchronized on lock for oplog {} on disk store {}",
childOplog.getOplogId(), diskStore.getName());
}
if (diskStore.getPersistentOplogSet().getChild() != childOplog) {
continue;
}
if (logger.isDebugEnabled()) {
logger.debug("Creating snapshot of oplogs for disk store {}", diskStore.getName());
}
restoreScript.addExistenceTest(diskStore.getDiskInitFile().getIFFile());
// Contains all oplogs that will backed up
Oplog[] allOplogs = diskStore.getAllOplogsForBackup();
backup = new DiskStoreBackup(allOplogs);
backupByDiskStore.put(diskStore, backup);
fileCopier.copyDiskInitFile(diskStore);
diskStore.getPersistentOplogSet().forceRoll(null);
if (logger.isDebugEnabled()) {
logger.debug("Finished backup of disk store {}", diskStore.getName());
}
break;
}
}
done = true;
} finally {
if (!done && backup != null) {
backupByDiskStore.remove(diskStore);
backup.cleanup();
}
}
return backup;
}
DiskStoreBackup getBackupForDiskStore(DiskStoreImpl diskStore) {
return backupByDiskStore.get(diskStore);
}
}