blob: 85e0a02c5f7bb3920767c498358ab8d397ebfab3 [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.cloudstack.storage.snapshot;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import com.cloud.storage.VolumeDetailVO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.log4j.Logger;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import com.cloud.agent.api.to.DataTO;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.db.DB;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
private static final String SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER = "secondary storage";
private static final String PRIMARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER = "primary storage";
private static final Logger s_logger = Logger.getLogger(DefaultSnapshotStrategy.class);
@Inject
SnapshotService snapshotSvr;
@Inject
DataStoreManager dataStoreMgr;
@Inject
SnapshotDataStoreDao snapshotStoreDao;
@Inject
ConfigurationDao configDao;
@Inject
SnapshotDao snapshotDao;
@Inject
VolumeDao volumeDao;
@Inject
SnapshotDataFactory snapshotDataFactory;
@Inject
private SnapshotDetailsDao _snapshotDetailsDao;
@Inject
VolumeDetailsDao _volumeDetailsDaoImpl;
@Override
public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
SnapshotInfo parentSnapshot = snapshot.getParent();
if (parentSnapshot != null && snapshot.getPath().equalsIgnoreCase(parentSnapshot.getPath())) {
// don't need to backup this snapshot
SnapshotDataStoreVO parentSnapshotOnBackupStore = snapshotStoreDao.findBySnapshot(parentSnapshot.getId(), DataStoreRole.Image);
if (parentSnapshotOnBackupStore != null && parentSnapshotOnBackupStore.getState() == State.Ready) {
DataStore store = dataStoreMgr.getDataStore(parentSnapshotOnBackupStore.getDataStoreId(), parentSnapshotOnBackupStore.getRole());
SnapshotInfo snapshotOnImageStore = (SnapshotInfo)store.create(snapshot);
snapshotOnImageStore.processEvent(Event.CreateOnlyRequested);
SnapshotObjectTO snapTO = new SnapshotObjectTO();
snapTO.setPath(parentSnapshotOnBackupStore.getInstallPath());
CreateObjectAnswer createSnapshotAnswer = new CreateObjectAnswer(snapTO);
snapshotOnImageStore.processEvent(Event.OperationSuccessed, createSnapshotAnswer);
SnapshotObject snapObj = castSnapshotInfoToSnapshotObject(snapshot);
try {
snapObj.processEvent(Snapshot.Event.OperationNotPerformed);
} catch (NoTransitionException e) {
s_logger.debug("Failed to change state: " + snapshot.getId() + ": " + e.toString());
throw new CloudRuntimeException(e.toString());
}
return snapshotDataFactory.getSnapshot(snapObj.getId(), store);
} else {
s_logger.debug("parent snapshot hasn't been backed up yet");
}
}
// determine full snapshot backup or not
boolean fullBackup = true;
SnapshotDataStoreVO parentSnapshotOnBackupStore = snapshotStoreDao.findLatestSnapshotForVolume(snapshot.getVolumeId(), DataStoreRole.Image);
SnapshotDataStoreVO parentSnapshotOnPrimaryStore = snapshotStoreDao.findLatestSnapshotForVolume(snapshot.getVolumeId(), DataStoreRole.Primary);
HypervisorType hypervisorType = snapshot.getBaseVolume().getHypervisorType();
if (parentSnapshotOnPrimaryStore != null && parentSnapshotOnBackupStore != null && hypervisorType == Hypervisor.HypervisorType.XenServer) { // CS does incremental backup only for XenServer
// In case of volume migration from one pool to other pool, CS should take full snapshot to avoid any issues with delta chain,
// to check if this is a migrated volume, compare the current pool id of volume and store_id of oldest snapshot on primary for this volume.
// Why oldest? Because at this point CS has two snapshot on primary entries for same volume, one with old pool_id and other one with
// current pool id. So, verify and if volume found to be migrated, delete snapshot entry with previous pool store_id.
SnapshotDataStoreVO oldestSnapshotOnPrimary = snapshotStoreDao.findOldestSnapshotForVolume(snapshot.getVolumeId(), DataStoreRole.Primary);
VolumeVO volume = volumeDao.findById(snapshot.getVolumeId());
if (oldestSnapshotOnPrimary != null) {
if (oldestSnapshotOnPrimary.getDataStoreId() == volume.getPoolId() && oldestSnapshotOnPrimary.getId() != parentSnapshotOnPrimaryStore.getId()) {
int _deltaSnapshotMax = NumbersUtil.parseInt(configDao.getValue("snapshot.delta.max"),
SnapshotManager.DELTAMAX);
int deltaSnap = _deltaSnapshotMax;
int i;
for (i = 1; i < deltaSnap; i++) {
Long prevBackupId = parentSnapshotOnBackupStore.getParentSnapshotId();
if (prevBackupId == 0) {
break;
}
parentSnapshotOnBackupStore = snapshotStoreDao.findBySnapshot(prevBackupId, DataStoreRole.Image);
if (parentSnapshotOnBackupStore == null) {
break;
}
}
if (i >= deltaSnap) {
fullBackup = true;
} else {
fullBackup = false;
}
} else if (oldestSnapshotOnPrimary.getId() != parentSnapshotOnPrimaryStore.getId()){
// if there is an snapshot entry for previousPool(primary storage) of migrated volume, delete it becasue CS created one more snapshot entry for current pool
snapshotStoreDao.remove(oldestSnapshotOnPrimary.getId());
}
}
}
snapshot.setFullBackup(fullBackup);
return snapshotSvr.backupSnapshot(snapshot);
}
private final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storage) {
DataTO snapshotTo = snapshot.getTO();
s_logger.debug(String.format("Deleting %s chain of snapshots.", snapshotTo));
boolean result = false;
boolean resultIsSet = false;
try {
while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState())) {
SnapshotInfo child = snapshot.getChild();
if (child != null) {
s_logger.debug(String.format("Snapshot [%s] has child [%s], not deleting it on the storage [%s]", snapshotTo, child.getTO(), storage));
break;
}
s_logger.debug(String.format("Snapshot [%s] does not have children; therefore, we will delete it and its parents.", snapshotTo));
SnapshotInfo parent = snapshot.getParent();
boolean deleted = false;
if (parent != null) {
if (parent.getPath() != null && parent.getPath().equalsIgnoreCase(snapshot.getPath())) {
//NOTE: if both snapshots share the same path, it's for xenserver's empty delta snapshot. We can't delete the snapshot on the backend, as parent snapshot still reference to it
//Instead, mark it as destroyed in the db.
s_logger.debug(String.format("Snapshot [%s] is an empty delta snapshot; therefore, we will only mark it as destroyed in the database.", snapshotTo));
snapshot.processEvent(Event.DestroyRequested);
snapshot.processEvent(Event.OperationSuccessed);
deleted = true;
if (!resultIsSet) {
result = true;
resultIsSet = true;
}
}
}
if (!deleted) {
try {
boolean r = snapshotSvr.deleteSnapshot(snapshot);
if (r) {
List<SnapshotInfo> cacheSnaps = snapshotDataFactory.listSnapshotOnCache(snapshot.getId());
for (SnapshotInfo cacheSnap : cacheSnaps) {
s_logger.debug(String.format("Deleting snapshot %s from image cache [%s].", snapshotTo, cacheSnap.getDataStore().getName()));
cacheSnap.delete();
}
}
if (!resultIsSet) {
result = r;
resultIsSet = true;
}
} catch (Exception e) {
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storage, e.getMessage()), e);
}
}
snapshot = parent;
}
} catch (Exception e) {
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storage, e.getMessage()), e);
}
return result;
}
@Override
public boolean deleteSnapshot(Long snapshotId) {
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
if (snapshotVO.getState() == Snapshot.State.Allocated) {
snapshotDao.remove(snapshotId);
return true;
}
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
return true;
}
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
List<SnapshotDataStoreVO> storeRefs = snapshotStoreDao.findBySnapshotId(snapshotId);
for (SnapshotDataStoreVO ref : storeRefs) {
snapshotStoreDao.expunge(ref.getId());
}
snapshotDao.remove(snapshotId);
return true;
}
if (snapshotVO.getState() == Snapshot.State.CreatedOnPrimary) {
snapshotVO.setState(Snapshot.State.Destroyed);
snapshotDao.update(snapshotId, snapshotVO);
return true;
}
if (!Snapshot.State.BackedUp.equals(snapshotVO.getState()) &&
!Snapshot.State.Destroying.equals(snapshotVO.getState())) {
throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
}
return destroySnapshotEntriesAndFiles(snapshotVO);
}
/**
* Destroys the snapshot entries and files on both primary and secondary storage (if it exists).
* @return true if destroy successfully, else false.
*/
protected boolean destroySnapshotEntriesAndFiles(SnapshotVO snapshotVo) {
if (!deleteSnapshotInfos(snapshotVo)) {
return false;
}
updateSnapshotToDestroyed(snapshotVo);
return true;
}
/**
* Updates the snapshot to {@link Snapshot.State#Destroyed}.
*/
protected void updateSnapshotToDestroyed(SnapshotVO snapshotVo) {
snapshotVo.setState(Snapshot.State.Destroyed);
snapshotDao.update(snapshotVo.getId(), snapshotVo);
}
protected boolean deleteSnapshotInfos(SnapshotVO snapshotVo) {
Map<String, SnapshotInfo> snapshotInfos = retrieveSnapshotEntries(snapshotVo.getId());
boolean result = false;
for (var infoEntry : snapshotInfos.entrySet()) {
if (BooleanUtils.toBooleanDefaultIfNull(deleteSnapshotInfo(infoEntry.getValue(), infoEntry.getKey(), snapshotVo), false)) {
result = true;
}
}
return result;
}
/**
* Destroys the snapshot entry and file.
* @return true if destroy successfully, else false.
*/
protected Boolean deleteSnapshotInfo(SnapshotInfo snapshotInfo, String storage, SnapshotVO snapshotVo) {
if (snapshotInfo == null) {
s_logger.debug(String.format("Could not find %s entry on %s. Skipping deletion on %s.", snapshotVo, storage, storage));
return SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER.equals(storage) ? null : true;
}
DataStore dataStore = snapshotInfo.getDataStore();
String storageToString = String.format("%s {uuid: \"%s\", name: \"%s\"}", storage, dataStore.getUuid(), dataStore.getName());
try {
SnapshotObject snapshotObject = castSnapshotInfoToSnapshotObject(snapshotInfo);
snapshotObject.processEvent(Snapshot.Event.DestroyRequested);
if (SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER.equals(storage)) {
verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObject);
if (deleteSnapshotChain(snapshotInfo, storageToString)) {
s_logger.debug(String.format("%s was deleted on %s. We will mark the snapshot as destroyed.", snapshotVo, storageToString));
} else {
s_logger.debug(String.format("%s was not deleted on %s; however, we will mark the snapshot as destroyed for future garbage collecting.", snapshotVo,
storageToString));
}
snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
return true;
} else if (deleteSnapshotInPrimaryStorage(snapshotInfo, snapshotVo, storageToString, snapshotObject)) {
return true;
}
s_logger.debug(String.format("Failed to delete %s on %s.", snapshotVo, storageToString));
snapshotObject.processEvent(Snapshot.Event.OperationFailed);
} catch (NoTransitionException ex) {
s_logger.warn(String.format("Failed to delete %s on %s due to %s.", snapshotVo, storageToString, ex.getMessage()), ex);
}
return false;
}
protected boolean deleteSnapshotInPrimaryStorage(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo, String storageToString, SnapshotObject snapshotObject) throws NoTransitionException {
try {
if (snapshotSvr.deleteSnapshot(snapshotInfo)) {
snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
s_logger.debug(String.format("%s was deleted on %s. We will mark the snapshot as destroyed.", snapshotVo, storageToString));
return true;
}
} catch (CloudRuntimeException ex) {
s_logger.warn(String.format("Unable do delete snapshot %s on %s due to [%s]. The reference will be marked as 'Destroying' for future garbage collecting.",
snapshotVo, storageToString, ex.getMessage()), ex);
}
return false;
}
protected void verifyIfTheSnapshotIsBeingUsedByAnyVolume(SnapshotObject snapshotObject) throws NoTransitionException {
List<VolumeDetailVO> volumesFromSnapshot = _volumeDetailsDaoImpl.findDetails("SNAPSHOT_ID", String.valueOf(snapshotObject.getSnapshotId()), null);
if (CollectionUtils.isEmpty(volumesFromSnapshot)) {
return;
}
snapshotObject.processEvent(Snapshot.Event.OperationFailed);
throw new CloudRuntimeException(String.format("Unable to delete snapshot [%s] because it is being used by the following volumes: %s.",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(snapshotObject.getSnapshotVO(), "id", "uuid", "volumeId", "name"),
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(volumesFromSnapshot, "resourceId")));
}
/**
* Cast SnapshotInfo to SnapshotObject.
* @return SnapshotInfo cast to SnapshotObject.
*/
protected SnapshotObject castSnapshotInfoToSnapshotObject(SnapshotInfo snapshotInfo) {
return (SnapshotObject) snapshotInfo;
}
/**
* Retrieves the snapshot infos on primary and secondary storage.
* @param snapshotId The snapshot to retrieve the infos.
* @return A map of snapshot infos.
*/
protected Map<String, SnapshotInfo> retrieveSnapshotEntries(long snapshotId) {
Map<String, SnapshotInfo> snapshotInfos = new LinkedHashMap<>();
snapshotInfos.put(SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER, snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Image, false));
snapshotInfos.put(PRIMARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER, snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary, false));
return snapshotInfos;
}
@Override
public boolean revertSnapshot(SnapshotInfo snapshot) {
if (canHandle(snapshot, SnapshotOperation.REVERT) == StrategyPriority.CANT_HANDLE) {
throw new CloudRuntimeException("Reverting not supported. Create a template or volume based on the snapshot instead.");
}
SnapshotVO snapshotVO = snapshotDao.acquireInLockTable(snapshot.getId());
if (snapshotVO == null) {
throw new CloudRuntimeException("Failed to get lock on snapshot:" + snapshot.getId());
}
try {
VolumeInfo volumeInfo = snapshot.getBaseVolume();
StoragePool store = (StoragePool)volumeInfo.getDataStore();
if (store != null && store.getStatus() != StoragePoolStatus.Up) {
snapshot.processEvent(Event.OperationFailed);
throw new CloudRuntimeException("store is not in up state");
}
volumeInfo.stateTransit(Volume.Event.RevertSnapshotRequested);
boolean result = false;
try {
result = snapshotSvr.revertSnapshot(snapshot);
if (!result) {
s_logger.debug("Failed to revert snapshot: " + snapshot.getId());
throw new CloudRuntimeException("Failed to revert snapshot: " + snapshot.getId());
}
} finally {
if (result) {
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
} else {
volumeInfo.stateTransit(Volume.Event.OperationFailed);
}
}
return result;
} finally {
if (snapshotVO != null) {
snapshotDao.releaseFromLockTable(snapshot.getId());
}
}
}
@Override
@DB
public SnapshotInfo takeSnapshot(SnapshotInfo snapshot) {
SnapshotInfo snapshotOnPrimary = null;
Object payload = snapshot.getPayload();
CreateSnapshotPayload createSnapshotPayload = null;
if (payload != null) {
createSnapshotPayload = (CreateSnapshotPayload)payload;
if (createSnapshotPayload.getQuiescevm()) {
throw new InvalidParameterValueException("can't handle quiescevm equal true for volume snapshot");
}
}
SnapshotVO snapshotVO = snapshotDao.acquireInLockTable(snapshot.getId());
if (snapshotVO == null) {
throw new CloudRuntimeException("Failed to get lock on snapshot:" + snapshot.getId());
}
try {
VolumeInfo volumeInfo = snapshot.getBaseVolume();
volumeInfo.stateTransit(Volume.Event.SnapshotRequested);
SnapshotResult result = null;
try {
result = snapshotSvr.takeSnapshot(snapshot);
if (result.isFailed()) {
s_logger.debug("Failed to take snapshot: " + result.getResult());
throw new CloudRuntimeException(result.getResult());
}
} finally {
if (result != null && result.isSuccess()) {
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
} else {
volumeInfo.stateTransit(Volume.Event.OperationFailed);
}
}
snapshotOnPrimary = result.getSnapshot();
snapshotOnPrimary.addPayload(snapshot.getPayload());
/*The Management Server ID is stored in snapshot_details table with the snapshot id and (name, value): (MS_ID, <ms_id>), to know which snapshots have not been completed in case of some failure situation like
* Mgmt server down etc. and by fetching the entries on restart the cleaning up of failed snapshots is done*/
_snapshotDetailsDao.addDetail(castSnapshotInfoToSnapshotObject(snapshotOnPrimary).getId(), AsyncJob.Constants.MS_ID, Long.toString(ManagementServerNode.getManagementServerId()), false);
return snapshotOnPrimary;
} finally {
if (snapshotVO != null) {
snapshotDao.releaseFromLockTable(snapshot.getId());
}
}
}
@Override
public void postSnapshotCreation(SnapshotInfo snapshotOnPrimary) {
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
_snapshotDetailsDao.removeDetail(castSnapshotInfoToSnapshotObject(snapshotOnPrimary).getId(), AsyncJob.Constants.MS_ID);
DataStore primaryStore = snapshotOnPrimary.getDataStore();
try {
SnapshotInfo parent = snapshotOnPrimary.getParent();
if (parent != null && primaryStore instanceof PrimaryDataStoreImpl) {
if (((PrimaryDataStoreImpl)primaryStore).getPoolType() != StoragePoolType.RBD) {
Long parentSnapshotId = parent.getId();
while (parentSnapshotId != null && parentSnapshotId != 0L) {
SnapshotDataStoreVO snapshotDataStoreVO = snapshotStoreDao.findByStoreSnapshot(primaryStore.getRole(), primaryStore.getId(), parentSnapshotId);
if (snapshotDataStoreVO != null) {
parentSnapshotId = snapshotDataStoreVO.getParentSnapshotId();
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_OFF_PRIMARY, parent.getAccountId(), parent.getDataCenterId(), parent.getId(),
parent.getName(), null, null, 0L, 0L, parent.getClass().getName(), parent.getUuid());
snapshotStoreDao.remove(snapshotDataStoreVO.getId());
} else {
parentSnapshotId = null;
}
}
}
SnapshotDataStoreVO snapshotDataStoreVO = snapshotStoreDao.findByStoreSnapshot(primaryStore.getRole(), primaryStore.getId(), snapshotOnPrimary.getId());
if (snapshotDataStoreVO != null) {
snapshotDataStoreVO.setParentSnapshotId(0L);
snapshotStoreDao.update(snapshotDataStoreVO.getId(), snapshotDataStoreVO);
}
}
} catch (Exception e) {
s_logger.debug("Failed to clean up snapshots on primary storage", e);
}
}
});
}
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
if (SnapshotOperation.REVERT.equals(op)) {
long volumeId = snapshot.getVolumeId();
VolumeVO volumeVO = volumeDao.findById(volumeId);
if (volumeVO != null && ImageFormat.QCOW2.equals(volumeVO.getFormat())) {
return StrategyPriority.DEFAULT;
}
return StrategyPriority.CANT_HANDLE;
}
return StrategyPriority.DEFAULT;
}
}