| /* |
| * 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.logging.internal.log4j.api.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); |
| } |
| } |