Repository: cloudstack Updated Branches: refs/heads/master 4b8bfe262 -> 35a8b7079
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) Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/35a8b707 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/35a8b707 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/35a8b707 Branch: refs/heads/master Commit: 35a8b70799129f483fc4f3cdc76cb6e055cee90e Parents: 4b8bfe2 Author: Mike Tutkowski <mike.tutkow...@solidfire.com> Authored: Mon Oct 13 15:48:08 2014 -0600 Committer: Mike Tutkowski <mike.tutkow...@solidfire.com> Committed: Tue Oct 14 12:26:40 2014 -0600 ---------------------------------------------------------------------- .../api/storage/DataStoreCapabilities.java | 3 +- ...-engine-storage-snapshot-storage-context.xml | 3 + .../snapshot/StorageSystemSnapshotStrategy.java | 208 +++++++++++++++++++ .../driver/SolidFirePrimaryDataStoreDriver.java | 35 ++-- .../storage/snapshot/SnapshotManagerImpl.java | 24 ++- 5 files changed, 248 insertions(+), 25 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/35a8b707/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java ---------------------------------------------------------------------- diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java index 25c66cd..09883c6 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java @@ -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 } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/35a8b707/engine/storage/snapshot/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml ---------------------------------------------------------------------- diff --git a/engine/storage/snapshot/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml b/engine/storage/snapshot/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml index 7299750..de8d11e 100644 --- a/engine/storage/snapshot/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml +++ b/engine/storage/snapshot/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml @@ -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" /> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/35a8b707/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java ---------------------------------------------------------------------- diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java new file mode 100644 index 0000000..894aa5a --- /dev/null +++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/35a8b707/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java ---------------------------------------------------------------------- diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java index e966cf6..946dd18 100644 --- a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java @@ -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); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/35a8b707/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 76ab370..f2126ef 100755 --- a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -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()); } }