Adding a snapshot strategy for systems that want to take snapshots that reside on their own system (as opposed to the default approach of taking a hypervisor snapshot and then copying it to secondary storage)

This commit is contained in:
Mike Tutkowski 2014-10-13 15:48:08 -06:00
parent 4b8bfe2627
commit 35a8b70799
5 changed files with 248 additions and 25 deletions

View File

@ -19,5 +19,6 @@
package org.apache.cloudstack.engine.subsystem.api.storage;
public enum DataStoreCapabilities {
VOLUME_SNAPSHOT_QUIESCEVM
VOLUME_SNAPSHOT_QUIESCEVM,
STORAGE_SYSTEM_SNAPSHOT // indicates to the StorageSystemSnapshotStrategy that this driver takes snapshots on its own system
}

View File

@ -30,6 +30,9 @@
<bean id="xenserverSnapshotStrategy"
class="org.apache.cloudstack.storage.snapshot.XenserverSnapshotStrategy" />
<bean id="storageSystemSnapshotStrategy"
class="org.apache.cloudstack.storage.snapshot.StorageSystemSnapshotStrategy" />
<bean id="DefaultVMSnapshotStrategy"
class="org.apache.cloudstack.storage.vmsnapshot.DefaultVMSnapshotStrategy" />

View File

@ -0,0 +1,208 @@
// 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.Map;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
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.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.db.DB;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
@Component
public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
private static final Logger s_logger = Logger.getLogger(StorageSystemSnapshotStrategy.class);
@Inject private DataStoreManager _dataStoreMgr;
@Inject private SnapshotDao _snapshotDao;
@Inject private SnapshotDataFactory _snapshotDataFactory;
@Inject private SnapshotDataStoreDao _snapshotStoreDao;
@Inject private PrimaryDataStoreDao _storagePoolDao;
@Inject private VolumeDao _volumeDao;
@Override
public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) {
return snapshotInfo;
}
@Override
public boolean deleteSnapshot(Long snapshotId) {
SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
if (Snapshot.State.Destroyed.equals(snapshotVO.getState())) {
return true;
}
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
_snapshotDao.remove(snapshotId);
return true;
}
if (!Snapshot.State.BackedUp.equals(snapshotVO.getState())) {
throw new InvalidParameterValueException("Unable to delete snapshotshot " + snapshotId + " because it is in the following state: " + snapshotVO.getState());
}
SnapshotObject snapshotObj = (SnapshotObject)_snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
if (snapshotObj == null) {
s_logger.debug("Can't find snapshot; deleting it in DB");
_snapshotDao.remove(snapshotId);
return true;
}
try {
snapshotObj.processEvent(Snapshot.Event.DestroyRequested);
}
catch (NoTransitionException e) {
s_logger.debug("Failed to set the state to destroying: ", e);
return false;
}
try {
snapshotSvr.deleteSnapshot(snapshotObj);
snapshotObj.processEvent(Snapshot.Event.OperationSucceeded);
}
catch (Exception e) {
s_logger.debug("Failed to delete snapshot: ", e);
try {
snapshotObj.processEvent(Snapshot.Event.OperationFailed);
}
catch (NoTransitionException e1) {
s_logger.debug("Failed to change snapshot state: " + e.toString());
}
return false;
}
return true;
}
@Override
public boolean revertSnapshot(Long snapshotId) {
return true;
}
@Override
@DB
public SnapshotInfo takeSnapshot(SnapshotInfo snapshotInfo) {
SnapshotVO snapshotVO = _snapshotDao.acquireInLockTable(snapshotInfo.getId());
if (snapshotVO == null) {
throw new CloudRuntimeException("Failed to acquire lock on the following snapshot: " + snapshotInfo.getId());
}
try {
VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
volumeInfo.stateTransit(Volume.Event.SnapshotRequested);
SnapshotResult result = null;
try {
result = snapshotSvr.takeSnapshot(snapshotInfo);
if (result.isFailed()) {
s_logger.debug("Failed to take the following snapshot: " + result.getResult());
throw new CloudRuntimeException(result.getResult());
}
markAsBackedUp((SnapshotObject)result.getSnashot());
}
finally {
if (result != null && result.isSuccess()) {
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
}
else {
volumeInfo.stateTransit(Volume.Event.OperationFailed);
}
}
}
finally {
if (snapshotVO != null) {
_snapshotDao.releaseFromLockTable(snapshotInfo.getId());
}
}
return snapshotInfo;
}
private void markAsBackedUp(SnapshotObject snapshotObj) {
try {
snapshotObj.processEvent(Snapshot.Event.BackupToSecondary);
snapshotObj.processEvent(Snapshot.Event.OperationSucceeded);
}
catch (NoTransitionException ex) {
s_logger.debug("Failed to change state: " + ex.toString());
try {
snapshotObj.processEvent(Snapshot.Event.OperationFailed);
}
catch (NoTransitionException ex2) {
s_logger.debug("Failed to change state: " + ex2.toString());
}
}
}
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
long volumeId = snapshot.getVolumeId();
VolumeVO volume = _volumeDao.findById(volumeId);
long storagePoolId = volume.getPoolId();
DataStore dataStore = _dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();
String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
Boolean supportsStorageSystemSnapshots = new Boolean(value);
if (supportsStorageSystemSnapshots) {
return StrategyPriority.HIGHEST;
}
return StrategyPriority.CANT_HANDLE;
}
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.storage.datastore.driver;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -25,6 +26,7 @@ import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
@ -32,7 +34,6 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
@ -90,7 +91,11 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Override
public Map<String, String> getCapabilities() {
return null;
Map<String, String> mapCapabilities = new HashMap<String, String>();
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
return mapCapabilities;
}
@Override
@ -473,28 +478,11 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Override
public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
if (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.SNAPSHOT) {
// in this situation, we don't want to copy the snapshot anywhere
CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(destData.getTO());
CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
result.setResult(null);
callback.complete(result);
return;
}
throw new UnsupportedOperationException();
}
@Override
public boolean canCopy(DataObject srcData, DataObject destData) {
if (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.SNAPSHOT) {
return true;
}
return false;
}
@ -595,6 +583,15 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
SolidFireUtil.deleteSolidFireSnapshot(sfConnection, getSolidFireSnapshotId(snapshotInfo.getId()));
_snapshotDetailsDao.removeDetails(snapshotInfo.getId());
StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
// getUsedBytes(StoragePool) will not include the snapshot to delete because it has already been deleted by this point
long usedBytes = getUsedBytes(storagePool);
storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes);
_storagePoolDao.update(storagePoolId, storagePool);
}
catch (Exception ex) {
s_logger.debug(SolidFireUtil.LOG_PREFIX + "Failed to delete SolidFire snapshot: " + snapshotInfo.getId(), ex);

View File

@ -412,33 +412,47 @@ public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager,
// Verify parameters
SnapshotVO snapshotCheck = _snapshotDao.findById(snapshotId);
if (snapshotCheck == null) {
throw new InvalidParameterValueException("unable to find a snapshot with id " + snapshotId);
}
_accountMgr.checkAccess(caller, null, true, snapshotCheck);
SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshotCheck, SnapshotOperation.DELETE);
if (snapshotStrategy == null) {
s_logger.error("Unable to find snaphot strategy to handle snapshot with id '" + snapshotId + "'");
return false;
}
SnapshotDataStoreVO snapshotStoreRef = _snapshotStoreDao.findBySnapshot(snapshotId, DataStoreRole.Image);
try {
boolean result = snapshotStrategy.deleteSnapshot(snapshotId);
if (result) {
if (snapshotCheck.getState() == Snapshot.State.BackedUp) {
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), snapshotCheck.getDataCenterId(), snapshotId,
snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid());
}
if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed)
_resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot);
if (snapshotCheck.getState() == Snapshot.State.BackedUp)
_resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.secondary_storage, new Long(snapshotStoreRef.getSize()));
if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed) {
_resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot);
}
if (snapshotCheck.getState() == Snapshot.State.BackedUp) {
SnapshotDataStoreVO snapshotStoreRef = _snapshotStoreDao.findBySnapshot(snapshotId, DataStoreRole.Image);
if (snapshotStoreRef != null) {
_resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.secondary_storage, new Long(snapshotStoreRef.getSize()));
}
}
}
return result;
} catch (Exception e) {
s_logger.debug("Failed to delete snapshot: " + snapshotCheck.getId() + ":" + e.toString());
throw new CloudRuntimeException("Failed to delete snapshot:" + e.toString());
}
}