/*************************************************************************
* (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.blockstorage.async;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.Future;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import com.eucalyptus.blockstorage.LogicalStorageManager;
import com.eucalyptus.blockstorage.S3SnapshotTransfer;
import com.eucalyptus.blockstorage.SnapshotProgressCallback;
import com.eucalyptus.blockstorage.SnapshotTransfer;
import com.eucalyptus.blockstorage.StorageResource;
import com.eucalyptus.blockstorage.StorageResourceWithCallback;
import com.eucalyptus.blockstorage.entities.SnapshotInfo;
import com.eucalyptus.blockstorage.entities.SnapshotTransferConfiguration;
import com.eucalyptus.blockstorage.entities.StorageInfo;
import com.eucalyptus.blockstorage.util.BlockStorageUtilSvc;
import com.eucalyptus.blockstorage.util.BlockStorageUtilSvcImpl;
import com.eucalyptus.blockstorage.util.StorageProperties;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.metrics.MonitoredAction;
import com.eucalyptus.util.metrics.ThruputMetrics;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import edu.ucsb.eucalyptus.util.EucaSemaphore;
import edu.ucsb.eucalyptus.util.EucaSemaphoreDirectory;
public class SnapshotCreator implements Runnable {
private static Logger LOG = Logger.getLogger(SnapshotCreator.class);
private String volumeId;
private String snapshotId;
private String snapPointId;
private LogicalStorageManager blockManager;
private SnapshotTransfer snapshotTransfer;
private SnapshotProgressCallback progressCallback;
private BlockStorageUtilSvc blockStorageUtilSvc;
/**
* Initializes the Snapshotter task. snapPointId should be null if no snap point has been created yet.
*
* @param volumeId
* @param snapshotId
* @param snapPointId
* @param blockManager TODO
*/
public SnapshotCreator(String volumeId, String snapshotId, String snapPointId, LogicalStorageManager blockManager) {
this.volumeId = volumeId;
this.snapshotId = snapshotId;
this.snapPointId = snapPointId;
this.blockManager = blockManager;
this.blockStorageUtilSvc = new BlockStorageUtilSvcImpl();
}
/**
* Strictly for use by unit tests only, SnapshotTransfer and ProgressCallback are mocked and instantiated which should never be the case for actual
* use
*
* @param volumeId
* @param snapshotId
* @param snapPointId
* @param mockBlockManager
* @param mockSnapshotTransfer
* @param mockProgressCallback
*/
protected SnapshotCreator(String volumeId, String snapshotId, String snapPointId, LogicalStorageManager mockBlockManager,
SnapshotTransfer mockSnapshotTransfer, SnapshotProgressCallback mockProgressCallback) {
this(volumeId, snapshotId, snapPointId, mockBlockManager);
this.snapshotTransfer = mockSnapshotTransfer;
this.progressCallback = mockProgressCallback;
}
@Override
public void run() {
LOG.trace("Starting SnapshotCreator task");
EucaSemaphore semaphore = EucaSemaphoreDirectory.getSolitarySemaphore(this.volumeId);
try {
Boolean shouldTransferSnapshots = true;
// SnapshotTransfer snapshotTransfer = null;
String bucket = null;
// SnapshotProgressCallback progressCallback = null;
// Check whether the snapshot needs to be uploaded
shouldTransferSnapshots = StorageInfo.getStorageInfo().getShouldTransferSnapshots();
if (shouldTransferSnapshots) {
// Prepare for the snapshot upload (fetch credentials for snapshot upload to osg, create the bucket). Error out if this fails without
// creating the snapshot on the blockstorage backend
if (null == snapshotTransfer) {
snapshotTransfer = new S3SnapshotTransfer(this.snapshotId, this.snapshotId);
}
bucket = snapshotTransfer.prepareForUpload();
if (snapshotTransfer == null || StringUtils.isBlank(bucket)) {
throw new EucalyptusCloudException("Failed to initialize snapshot transfer mechanism for uploading " + this.snapshotId);
}
}
// Acquire the semaphore here and release it here as well
try {
try {
semaphore.acquire();
} catch (InterruptedException ex) {
throw new EucalyptusCloudException("Failed to create snapshot " + this.snapshotId + " as the semaphore could not be acquired");
}
// Check to ensure that a failed/cancellation has not be set
if (!isSnapshotMarkedFailed(this.snapshotId)) {
if (null == progressCallback) {
progressCallback = new SnapshotProgressCallback(this.snapshotId); // Setup the progress callback, that should start the progress
}
blockManager.createSnapshot(this.volumeId, this.snapshotId, this.snapPointId);
progressCallback.updateBackendProgress(50); // to indicate that backend snapshot process is 50% done
} else {
throw new EucalyptusCloudException("Snapshot " + this.snapshotId + " marked as failed by another thread");
}
} finally {
semaphore.release();
}
SnapshotInfo prevSnap = null;
SnapshotInfo currSnap = null;
Future<String> uploadFuture = null;
if (!isSnapshotMarkedFailed(this.snapshotId)) {
if (shouldTransferSnapshots) {
// TODO move this check down further
// generate snapshot location
String snapshotLocation = SnapshotInfo.generateSnapshotLocationURI(SnapshotTransferConfiguration.OSG, bucket, this.snapshotId);
// gather what needs to be uploaded
try {
// Check if backend supports snap deltas
Integer maxDeltaLimit = StorageInfo.getStorageInfo().getMaxSnapshotDeltas();
maxDeltaLimit = maxDeltaLimit != null ? maxDeltaLimit : 0;
if (maxDeltaLimit > 0 && blockManager.supportsIncrementalSnapshots()) { // backend supports delta, evaluate if a delta can be uploaded
LOG.debug("EBS backend supports incremental snapshots");
int attempts = 0;
do {
attempts++;
prevSnap = fetchPreviousSnapshot(maxDeltaLimit);
if (prevSnap != null) {
// Acquire a semaphore to previous snapshot before updating the metadata for current snapshot
EucaSemaphore prevSnapSemaphore = EucaSemaphoreDirectory.getSolitarySemaphore(prevSnap.getSnapshotId());
try {
try {
prevSnapSemaphore.acquire();
} catch (InterruptedException ex) {
LOG.warn("Cannot update metadata for snapshot " + this.snapshotId + " due to an error acquiring semaphore for a previous snapshot "
+ prevSnap.getSnapshotId() + ". May retry again later");
continue;
}
currSnap = updateSnapshotInfo(prevSnap.getSnapshotId(), snapshotLocation);
} finally {
prevSnapSemaphore.release();
}
} else {
currSnap = updateSnapshotInfo(snapshotLocation);
}
} while (currSnap == null && attempts < 10);
} else { // backend does not support deltas, upload entire snapshot
LOG.debug("Either EBS backend does not support incremental snapshots or the feature is disabled");
currSnap = updateSnapshotInfo(snapshotLocation);
}
if (currSnap == null) {
LOG.warn("Failed to update metadata for snapshot " + this.snapshotId);
throw new EucalyptusCloudException("Failed to update metadata for snapshot " + this.snapshotId);
}
} catch (EucalyptusCloudException e) {
throw e;
} catch (Exception e) {
LOG.warn("Unable to evaluate snapshot location and upload specifics, failing snapshot " + this.snapshotId, e);
throw new EucalyptusCloudException("Unable to evaluate snapshot location and upload specifics, failing snapshot " + this.snapshotId, e);
}
StorageResourceWithCallback srwc = null;
StorageResource snapshotResource = null;
if (prevSnap != null) {
LOG.info("Generate delta between penultimate snapshot " + prevSnap.getSnapshotId() + " and latest snapshot " + this.snapshotId);
srwc =
blockManager.prepIncrementalSnapshotForUpload(this.volumeId, this.snapshotId, this.snapPointId, prevSnap.getSnapshotId(), prevSnap.getSnapPointId());
snapshotResource = srwc.getSr();
} else {
LOG.info("Upload entire content of snapshot " + this.snapshotId);
snapshotResource = blockManager.prepSnapshotForUpload(this.volumeId, this.snapshotId, this.snapPointId);
}
if (snapshotResource == null) {
LOG.warn("Unable to upload snapshot " + this.snapshotId + " due to invalid source");
throw new EucalyptusCloudException("Unable to upload snapshot " + this.snapshotId + " due to invalid source");
}
try {
uploadFuture = snapshotTransfer.upload(snapshotResource, progressCallback);
} catch (Exception e) {
throw new EucalyptusCloudException("Failed to upload snapshot " + this.snapshotId + " to objectstorage", e);
} finally {
if (srwc != null && srwc.getCallback() != null) {
// Call the callback even if the upload fails, to clean up temp snapshot artifacts
blockManager.executeCallback(srwc.getCallback(), srwc.getSr());
}
}
} else {
// Snapshot does not have to be transferred
LOG.debug("Snapshot uploads are disabled, skipping upload step for " + this.snapshotId);
}
} else {
LOG.warn("Snapshot " + this.snapshotId + " marked as failed, aborting upload process");
throw new EucalyptusCloudException("Snapshot " + this.snapshotId + " marked as failed, aborting upload process");
}
// finish the snapshot on backend - sever iscsi connection, disconnect and wait for it to complete
try {
LOG.debug("Finishing up " + this.snapshotId + " on block storage backend");
blockManager.finishVolume(this.snapshotId);
LOG.info("Finished creating " + this.snapshotId + " on block storage backend");
progressCallback.updateBackendProgress(50); // to indicate that backend snapshot process is 50% done
} catch (EucalyptusCloudException ex) {
LOG.warn("Failed to complete snapshot " + this.snapshotId + " on backend", ex);
throw ex;
}
// If uploading, wait for upload to complete
if (uploadFuture != null) {
LOG.debug("Waiting for upload of " + this.snapshotId + " to complete");
if (uploadFuture.get() != null) {
LOG.info("Uploaded " + this.snapshotId + " to object storage gateway, etag result - " + uploadFuture.get());
} else {
LOG.warn("Failed to upload " + this.snapshotId + " to object storage gateway failed. Check prior logs for exact errors");
throw new EucalyptusCloudException("Failed to upload " + this.snapshotId + " to object storage gateway");
}
}
// Now that the snapshot is complete, if it's a delta, wait until the
// snapshot transfer timeout for the parent to go into a final state
// (good or bad). If good, declare this snapshot available.
// If bad, of if it never competes, declare this snapshot failed.
// (Artifacts will be cleaned up by periodic cleanup tasks.)
if (prevSnap != null) {
final int MILLIS_PER_HOUR = 60 * 60 * 1000;
final int timeoutMillis = StorageInfo.getStorageInfo().getSnapshotTransferTimeoutInHours()
* MILLIS_PER_HOUR;
final int pollPeriodMillis = 10000; // 10 sec
int waitTimeSoFar = 0;
while (waitTimeSoFar < timeoutMillis) {
try {
if (isSnapshotMarkedFinalized(prevSnap.getSnapshotId())) {
break;
}
} catch (NoSuchElementException nsee) {
LOG.error("Previous snapshot " + prevSnap.getSnapshotId() +
" no longer recorded in the database. " +
"Setting this snapshot delta " + this.snapshotId +
" to 'failed' state because it requires an intact previous snapshot.");
throw nsee; // will be caught later in this method
} catch (TransactionException te) {
LOG.error("General database error. " +
"Setting this snapshot delta " + this.snapshotId +
" to 'failed' state because it requires an intact previous snapshot.");
throw te; // will be caught later in this method
}
try {
Thread.sleep(pollPeriodMillis);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for previous snapshot to complete", e);
}
waitTimeSoFar += pollPeriodMillis;
}
if (isSnapshotMarkedFinalized(prevSnap.getSnapshotId())) {
if (isSnapshotMarkedAvailable(prevSnap.getSnapshotId())) {
markSnapshotAvailable();
} else {
markSnapshotFailed();
LOG.error("Previous snapshot " + prevSnap.getSnapshotId() + " finalized into the " +
prevSnap.getStatus() + " state. This snapshot delta " + this.snapshotId +
" set to 'failed' state because it requires an intact previous snapshot.");
}
} else {
markSnapshotFailed();
LOG.error("Previous snapshot " + prevSnap.getSnapshotId() + " never finalized after " +
"waiting " + (timeoutMillis / MILLIS_PER_HOUR) + " hours. " +
"This snapshot delta " + this.snapshotId + " set to 'failed' state because " +
"it requires an intact previous snapshot.");
}
} else {
// No previous snapshot, therefore we're done
markSnapshotAvailable();
LOG.debug("Snapshot " + this.snapshotId + " set to 'available' state");
}
} catch (Exception ex) {
LOG.error("Failed to create snapshot " + this.snapshotId, ex);
try {
markSnapshotFailed();
LOG.debug("Snapshot " + this.snapshotId + " set to 'failed' state");
} catch (TransactionException | NoSuchElementException e) {
LOG.warn("Cannot update snapshot " + this.snapshotId + " status to 'failed' in DB", e);
}
}
LOG.trace("Finished SnapshotCreator task");
}
/* @return the given snapshot's info from the DB, or
* null if DB entry not found
*/
private SnapshotInfo getSnapshotInfo(String snapshotId) throws TransactionException, NoSuchElementException {
try (TransactionResource tran = Entities.transactionFor(SnapshotInfo.class)) {
tran.setRollbackOnly();
return Entities.uniqueResult(new SnapshotInfo(snapshotId));
} catch (TransactionException | NoSuchElementException dbe) {
// Database exception, toss it upstairs
LOG.error("Database error checking for status of snapshot " + snapshotId + ": " + dbe);
throw dbe;
}
}
/*
* Does a check of the snapshot's status as reflected in the DB.
* @return true if snapshot is in the 'failed' state,
* false otherwise
*/
private boolean isSnapshotMarkedFailed(String snapshotId) throws TransactionException, NoSuchElementException {
SnapshotInfo snapshotInfo = getSnapshotInfo(snapshotId);
if (snapshotInfo != null) {
return StorageProperties.Status.failed.toString().equals(snapshotInfo.getStatus());
} else {
return false;
}
}
/*
* Does a check of the snapshot's status as reflected in the DB.
* @return false if snapshot is not (yet) recorded in database
* or it's in the 'creating' or 'pending' state,
* true otherwise
*/
private boolean isSnapshotMarkedFinalized(String snapshotId) throws TransactionException, NoSuchElementException {
SnapshotInfo snapshotInfo = getSnapshotInfo(snapshotId);
if (snapshotInfo != null) {
return !(StorageProperties.Status.creating.toString().equals(snapshotInfo.getStatus()) ||
StorageProperties.Status.pending.toString().equals(snapshotInfo.getStatus()));
} else {
return false;
}
}
/*
* Does a check of the snapshot's status as reflected in the DB.
* @return true if snapshot is in the 'available' state,
* false otherwise
*/
private boolean isSnapshotMarkedAvailable(String snapshotId) throws TransactionException, NoSuchElementException {
SnapshotInfo snapshotInfo = getSnapshotInfo(snapshotId);
if (snapshotInfo != null) {
return StorageProperties.Status.available.toString().equals(snapshotInfo.getStatus());
} else {
return false;
}
}
private void markSnapshotAvailable() throws TransactionException, NoSuchElementException {
Function<String, SnapshotInfo> updateFunction = new Function<String, SnapshotInfo>() {
@Override
public SnapshotInfo apply(String arg0) {
SnapshotInfo snap;
try {
snap = Entities.uniqueResult(new SnapshotInfo(arg0));
snap.setStatus(StorageProperties.Status.available.toString());
snap.setProgress("100");
return snap;
} catch (TransactionException | NoSuchElementException e) {
LOG.error("Failed to retrieve snapshot entity from DB for " + arg0, e);
}
return null;
}
};
Entities.asTransaction(SnapshotInfo.class, updateFunction).apply(snapshotId);
ThruputMetrics.endOperation(MonitoredAction.CREATE_SNAPSHOT, snapshotId, System.currentTimeMillis());
}
private void markSnapshotFailed() throws TransactionException, NoSuchElementException {
Function<String, SnapshotInfo> updateFunction = new Function<String, SnapshotInfo>() {
@Override
public SnapshotInfo apply(String arg0) {
SnapshotInfo snap;
try {
snap = Entities.uniqueResult(new SnapshotInfo(arg0));
snap.setStatus(StorageProperties.Status.failed.toString());
snap.setProgress("0");
return snap;
} catch (TransactionException | NoSuchElementException e) {
LOG.error("Failed to retrieve snapshot entity from DB for " + arg0, e);
}
return null;
}
};
Entities.asTransaction(SnapshotInfo.class, updateFunction).apply(snapshotId);
}
private SnapshotInfo fetchPreviousSnapshot(int maxDeltas) throws Exception {
SnapshotInfo prevSnapToAssign = null;
SnapshotInfo currSnap = Transactions.find(new SnapshotInfo(snapshotId));
try (TransactionResource tr = Entities.transactionFor(SnapshotInfo.class)) {
// Find the most recent snapshot that is not in one of the states that
// is ineligible to use for creating a snap delta.
SnapshotInfo prevEligibleSnapSearch = new SnapshotInfo();
prevEligibleSnapSearch.setVolumeId(currSnap.getVolumeId());
Criteria search = Entities.createCriteria(SnapshotInfo.class);
search.add(Example.create(prevEligibleSnapSearch).enableLike(MatchMode.EXACT));
search.add(Restrictions.and(StorageProperties.SNAPSHOT_DELTA_GENERATION_CRITERION, Restrictions.lt("startTime", currSnap.getStartTime())));
search.addOrder(Order.desc("startTime"));
search.setReadOnly(true);
search.setMaxResults(1); // only return the latest one
List<SnapshotInfo> prevEligibleSnapList = (List<SnapshotInfo>) search.list();
boolean committed = false;
if (prevEligibleSnapList != null && prevEligibleSnapList.size() > 0 &&
(prevSnapToAssign = prevEligibleSnapList.get(0)) != null) {
// Found an eligible previous snapshot to use as a parent for this
// snapshot, if we make it a delta.
if (prevSnapToAssign.getSnapshotLocation() != null && prevSnapToAssign.getIsOrigin() != null) {
LOG.info(this.volumeId + " has been snapshotted and uploaded before. Most recent such snapshot is " + prevSnapToAssign.getSnapshotId());
// Get all the restorable snapshots for this volume, earlier than the current snapshot
SnapshotInfo prevRestorableSnapsSearch = new SnapshotInfo();
prevRestorableSnapsSearch.setVolumeId(currSnap.getVolumeId());
search = Entities.createCriteria(SnapshotInfo.class);
search.add(Example.create(prevRestorableSnapsSearch).enableLike(MatchMode.EXACT));
search.add(Restrictions.and(StorageProperties.SNAPSHOT_DELTA_RESTORATION_CRITERION, Restrictions.lt("startTime", currSnap.getStartTime())));
search.addOrder(Order.desc("startTime"));
search.setReadOnly(true);
List<SnapshotInfo> prevRestorableSnapsList = (List<SnapshotInfo>) search.list();
tr.commit();
committed = true;
// Get the snap chain ending with the previous snapshot (not the current)
List<SnapshotInfo> snapChain = blockStorageUtilSvc.getSnapshotChain(prevRestorableSnapsList, prevSnapToAssign.getSnapshotId());
int numDeltas = 0;
if (snapChain == null || snapChain.size() == 0) {
// This should never happen. The chain should always include the
// parent (previous) snapshot we already found. But create it as a
// full snapshot instead of failing, to account for the unknown case
// that might not prevent an OK full snapshot.
LOG.error("Did not find the current snapshot's previous snapshot " +
prevSnapToAssign.getSnapshotId() + " in the restorable snapshots list. " +
"The current snapshot " + currSnap.getSnapshotId() +
" will be created as a full snapshot.");
} else if (snapChain.get(0).getPreviousSnapshotId() != null) {
// This should never happen. The first snapshot in the chain
// should always be a full snapshot. But create it as a
// full snapshot instead of failing, to account for the unknown case
// that might not prevent an OK full snapshot.
LOG.error("First snapshot " + snapChain.get(0).getSnapshotId() +
" in the chain of " + snapChain.size() + " snapshots is not a full snapshot. The current snapshot " +
currSnap.getSnapshotId() + " will be created as a full snapshot.");
} else {
numDeltas = snapChain.size() - 1;
LOG.info(this.volumeId + " has " + numDeltas + " delta(s) since the last full checkpoint. Max limit is " + maxDeltas);
if (numDeltas < maxDeltas) {
return prevSnapToAssign;
}
}
} else {
LOG.info(this.volumeId + " has not been snapshotted and/or uploaded after the support for incremental snapshots was added");
}
} else {
LOG.info(this.volumeId + " has no prior active snapshots in the system");
}
if (!committed) {
tr.commit();
}
} catch (Exception e) {
LOG.warn("Failed to look up previous snapshots for " + this.volumeId, e); // return null on exception, forces entire snapshot to get uploaded
}
return null;
}
private SnapshotInfo updateSnapshotInfo(final String prevSnapId, final String snapshotLocation) throws Exception {
return Entities.asTransaction(SnapshotInfo.class, new Function<String, SnapshotInfo>() {
@Override
public SnapshotInfo apply(String arg0) {
SnapshotInfo currSnap = null;
SnapshotInfo prevSnap = null;
try {
prevSnap = Entities.uniqueResult(new SnapshotInfo(prevSnapId));
if (!StorageProperties.DELTA_GENERATION_STATE_EXCLUSION.contains(prevSnap.getStatus())) {
currSnap = Entities.uniqueResult(new SnapshotInfo(snapshotId));
currSnap.setSnapshotLocation(snapshotLocation);
currSnap.setPreviousSnapshotId(prevSnapId);
return currSnap;
} else {
LOG.warn("Snapshot " + prevSnapId + " has already been marked as " + prevSnap.getStatus()
+ ". It cannot be used as the source for incremental snapshot upload of " + snapshotId);
}
} catch (TransactionException | NoSuchElementException e) {
LOG.debug("Failed to update snapshot upload location and previous snapshot ID for snapshot " + snapshotId, e);
}
return null;
}
}).apply(prevSnapId);
}
private SnapshotInfo updateSnapshotInfo(final String snapshotLocation) throws Exception {
return Entities.asTransaction(SnapshotInfo.class, new Function<String, SnapshotInfo>() {
@Override
public SnapshotInfo apply(String arg0) {
SnapshotInfo currSnap = null;
try {
currSnap = Entities.uniqueResult(new SnapshotInfo(snapshotId));
currSnap.setSnapshotLocation(snapshotLocation);
return currSnap;
} catch (TransactionException | NoSuchElementException e) {
LOG.warn("Failed to update snapshot upload location for snapshot " + snapshotId, e);
}
return null;
}
}).apply(this.snapshotId);
}
}