diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java
new file mode 100644
index 00000000000..24684d1e862
--- /dev/null
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java
@@ -0,0 +1,86 @@
+/*
+ * 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 javax.inject.Inject;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
+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 org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+
+public class CephSnapshotStrategy extends StorageSystemSnapshotStrategy {
+ @Inject
+ private SnapshotDataStoreDao snapshotStoreDao;
+ @Inject
+ private PrimaryDataStoreDao primaryDataStoreDao;
+ @Inject
+ private VolumeDao volumeDao;
+
+ private static final Logger s_logger = Logger.getLogger(CephSnapshotStrategy.class);
+
+ @Override
+ public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
+ long volumeId = snapshot.getVolumeId();
+ VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
+ boolean baseVolumeExists = volumeVO.getRemoved() == null;
+ if (!baseVolumeExists) {
+ return StrategyPriority.CANT_HANDLE;
+ }
+
+ if (SnapshotOperation.REVERT.equals(op) && isSnapshotStoredOnRbdStoragePool(snapshot)) {
+ return StrategyPriority.HIGHEST;
+ }
+ return StrategyPriority.CANT_HANDLE;
+ }
+
+ @Override
+ public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
+ VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
+ ImageFormat imageFormat = volumeInfo.getFormat();
+ if (!ImageFormat.RAW.equals(imageFormat)) {
+ s_logger.error(String.format("Does not support revert snapshot of the image format [%s] on Ceph/RBD. Can only rollback snapshots of format RAW", imageFormat));
+ return false;
+ }
+
+ executeRevertSnapshot(snapshotInfo, volumeInfo);
+
+ return true;
+ }
+
+ protected boolean isSnapshotStoredOnRbdStoragePool(Snapshot snapshot) {
+ SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+ long snapshotStoragePoolId = snapshotStore.getDataStoreId();
+ StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapshotStoragePoolId);
+ return storagePoolVO.getPoolType() == StoragePoolType.RBD;
+ }
+}
\ No newline at end of file
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
index a6fe50e2581..33d43d708b0 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
@@ -102,7 +102,6 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
@Inject private SnapshotDataFactory snapshotDataFactory;
@Inject private SnapshotDetailsDao snapshotDetailsDao;
@Inject private SnapshotDataStoreDao snapshotStoreDao;
- @Inject private VolumeDetailsDao volumeDetailsDao;
@Inject private VMInstanceDao vmInstanceDao;
@Inject private VMSnapshotDao vmSnapshotDao;
@Inject private VMSnapshotService vmSnapshotService;
@@ -307,8 +306,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
}
}
- boolean storageSystemSupportsCapability = storageSystemSupportsCapability(volumeInfo.getPoolId(),
- DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
+ boolean storageSystemSupportsCapability = storageSystemSupportsCapability(volumeInfo.getPoolId(), DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
if (!storageSystemSupportsCapability) {
String errMsg = "Storage pool revert capability not supported";
@@ -318,6 +316,18 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
throw new CloudRuntimeException(errMsg);
}
+ executeRevertSnapshot(snapshotInfo, volumeInfo);
+
+ return true;
+ }
+
+ /**
+ * Executes the SnapshotStrategyBase.revertSnapshot(SnapshotInfo) method, and handles the SnapshotVO table update and the Volume.Event state machine (RevertSnapshotRequested).
+ */
+ protected void executeRevertSnapshot(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo) {
+ Long hostId = null;
+ boolean success = false;
+
SnapshotVO snapshotVO = snapshotDao.acquireInLockTable(snapshotInfo.getId());
if (snapshotVO == null) {
@@ -328,9 +338,6 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
throw new CloudRuntimeException(errMsg);
}
- Long hostId = null;
- boolean success = false;
-
try {
volumeInfo.stateTransit(Volume.Event.RevertSnapshotRequested);
@@ -350,14 +357,14 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
success = snapshotSvr.revertSnapshot(snapshotInfo);
if (!success) {
- String errMsg = "Failed to revert a volume to a snapshot state";
+ String errMsg = String.format("Failed to revert volume [name:%s, format:%s] to snapshot [id:%s] state", volumeInfo.getName(), volumeInfo.getFormat(),
+ snapshotInfo.getSnapshotId());
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
- }
- finally {
+ } finally {
if (getHypervisorRequiresResignature(volumeInfo)) {
if (hostId != null) {
HostVO hostVO = hostDao.findById(hostId);
@@ -371,15 +378,12 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
if (success) {
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
- }
- else {
+ } else {
volumeInfo.stateTransit(Volume.Event.OperationFailed);
}
snapshotDao.releaseFromLockTable(snapshotInfo.getId());
}
-
- return true;
}
private Long getHostId(VolumeInfo volumeInfo) {
diff --git a/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml b/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml
index e94ee0780a6..b295398144c 100644
--- a/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml
+++ b/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml
@@ -32,6 +32,9 @@
+
+
diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java
new file mode 100644
index 00000000000..a4c4867f7e0
--- /dev/null
+++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.Date;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
+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 org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CephSnapshotStrategyTest {
+
+ @Spy
+ @InjectMocks
+ private CephSnapshotStrategy cephSnapshotStrategy;
+ @Mock
+ private SnapshotDataStoreDao snapshotStoreDao;
+ @Mock
+ private PrimaryDataStoreDao primaryDataStoreDao;
+ @Mock
+ private VolumeDao volumeDao;
+
+ @Test
+ public void canHandleTestNotReomvedAndSnapshotStoredOnRbd() {
+ configureAndVerifyCanHandle(null, true);
+ }
+
+ @Test
+ public void canHandleTestNotReomvedAndSnapshotNotStoredOnRbd() {
+ configureAndVerifyCanHandle(null, false);
+ }
+
+ @Test
+ public void canHandleTestReomvedAndSnapshotNotStoredOnRbd() {
+ configureAndVerifyCanHandle(null, false);
+ }
+
+ @Test
+ public void canHandleTestReomvedAndSnapshotStoredOnRbd() {
+ configureAndVerifyCanHandle(null, true);
+ }
+
+ private void configureAndVerifyCanHandle(Date removed, boolean isSnapshotStoredOnRbdStoragePool) {
+ Snapshot snapshot = Mockito.mock(Snapshot.class);
+ SnapshotOperation[] snapshotOps = SnapshotOperation.values();
+
+ Mockito.when(snapshot.getVolumeId()).thenReturn(0l);
+ VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+ Mockito.when(volumeVO.getRemoved()).thenReturn(removed);
+ Mockito.when(volumeDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(volumeVO);
+ Mockito.doReturn(isSnapshotStoredOnRbdStoragePool).when(cephSnapshotStrategy).isSnapshotStoredOnRbdStoragePool(Mockito.any());
+
+ for (int i = 0; i < snapshotOps.length - 1; i++) {
+ StrategyPriority strategyPriority = cephSnapshotStrategy.canHandle(snapshot, snapshotOps[i]);
+ if (snapshotOps[i] == SnapshotOperation.REVERT && isSnapshotStoredOnRbdStoragePool) {
+ Assert.assertEquals(StrategyPriority.HIGHEST, strategyPriority);
+ } else {
+ Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
+ }
+ }
+ }
+
+ @Test
+ public void revertSnapshotTest() {
+ ImageFormat[] imageFormatValues = ImageFormat.values();
+
+ for (int i = 0; i < imageFormatValues.length - 1; i++) {
+ Mockito.reset(cephSnapshotStrategy);
+ SnapshotInfo snapshotInfo = Mockito.mock(SnapshotInfo.class);
+ VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
+ Mockito.when(snapshotInfo.getBaseVolume()).thenReturn(volumeInfo);
+ Mockito.when(volumeInfo.getFormat()).thenReturn(imageFormatValues[i]);
+ Mockito.doNothing().when(cephSnapshotStrategy).executeRevertSnapshot(Mockito.any(), Mockito.any());
+
+ boolean revertResult = cephSnapshotStrategy.revertSnapshot(snapshotInfo);
+
+ if (imageFormatValues[i] == ImageFormat.RAW) {
+ Assert.assertTrue(revertResult);
+ Mockito.verify(cephSnapshotStrategy).executeRevertSnapshot(Mockito.any(), Mockito.any());
+ } else {
+ Assert.assertFalse(revertResult);
+ Mockito.verify(cephSnapshotStrategy, Mockito.times(0)).executeRevertSnapshot(Mockito.any(), Mockito.any());
+ }
+ }
+
+ }
+
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java
index 22edf814fc8..5aea457a755 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java
@@ -27,6 +27,12 @@ import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
+import com.ceph.rados.IoCTX;
+import com.ceph.rados.Rados;
+import com.ceph.rados.exceptions.RadosException;
+import com.ceph.rbd.Rbd;
+import com.ceph.rbd.RbdException;
+import com.ceph.rbd.RbdImage;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.NfsTO;
@@ -40,42 +46,64 @@ import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script;
-@ResourceWrapper(handles = RevertSnapshotCommand.class)
+@ResourceWrapper(handles = RevertSnapshotCommand.class)
public final class LibvirtRevertSnapshotCommandWrapper extends CommandWrapper {
private static final Logger s_logger = Logger.getLogger(LibvirtRevertSnapshotCommandWrapper.class);
+ private static final String MON_HOST = "mon_host";
+ private static final String KEY = "key";
+ private static final String CLIENT_MOUNT_TIMEOUT = "client_mount_timeout";
+ private static final String RADOS_CONNECTION_TIMEOUT = "30";
@Override
public Answer execute(final RevertSnapshotCommand command, final LibvirtComputingResource libvirtComputingResource) {
SnapshotObjectTO snapshot = command.getData();
VolumeObjectTO volume = snapshot.getVolume();
- PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO) volume.getDataStore();
+ PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO)volume.getDataStore();
DataStoreTO snapshotImageStore = snapshot.getDataStore();
- if (!(snapshotImageStore instanceof NfsTO)) {
- return new Answer(command, false, "revert snapshot on object storage is not implemented yet");
+ if (!(snapshotImageStore instanceof NfsTO) && primaryStore.getPoolType() != StoragePoolType.RBD) {
+ return new Answer(command, false,
+ String.format("Revert snapshot does not support storage pool of type [%s]. Revert snapshot is supported by storage pools of type 'NFS' or 'RBD'",
+ primaryStore.getPoolType()));
}
- NfsTO nfsImageStore = (NfsTO) snapshotImageStore;
-
- String secondaryStoragePoolUrl = nfsImageStore.getUrl();
String volumePath = volume.getPath();
String snapshotPath = null;
String snapshotRelPath = null;
KVMStoragePool secondaryStoragePool = null;
try {
- final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
- secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolUrl);
- String ssPmountPath = secondaryStoragePool.getLocalPath();
snapshotRelPath = snapshot.getPath();
- snapshotPath = ssPmountPath + File.separator + snapshotRelPath;
+ KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
- KVMPhysicalDisk snapshotDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(),
- primaryStore.getUuid(), volumePath);
+ KVMPhysicalDisk snapshotDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), volumePath);
KVMStoragePool primaryPool = snapshotDisk.getPool();
if (primaryPool.getType() == StoragePoolType.RBD) {
- return new Answer(command, false, "revert snapshot to RBD is not implemented yet");
+ Rados rados = new Rados(primaryPool.getAuthUserName());
+ rados.confSet(MON_HOST, primaryPool.getSourceHost() + ":" + primaryPool.getSourcePort());
+ rados.confSet(KEY, primaryPool.getAuthSecret());
+ rados.confSet(CLIENT_MOUNT_TIMEOUT, RADOS_CONNECTION_TIMEOUT);
+ rados.connect();
+
+ String[] rbdPoolAndVolumeAndSnapshot = snapshotRelPath.split("/");
+
+ IoCTX io = rados.ioCtxCreate(primaryPool.getSourceDir());
+ Rbd rbd = new Rbd(io);
+ RbdImage image = rbd.open(rbdPoolAndVolumeAndSnapshot[1]);
+
+ s_logger.debug(String.format("Attempting to rollback RBD snapshot [name:%s], [pool:%s], [volumeid:%s], [snapshotid:%s]", snapshot.getName(),
+ rbdPoolAndVolumeAndSnapshot[0], rbdPoolAndVolumeAndSnapshot[1], rbdPoolAndVolumeAndSnapshot[2]));
+ image.snapRollBack(rbdPoolAndVolumeAndSnapshot[2]);
+
+ rbd.close(image);
+ rados.ioCtxDestroy(io);
} else {
+ NfsTO nfsImageStore = (NfsTO)snapshotImageStore;
+ String secondaryStoragePoolUrl = nfsImageStore.getUrl();
+ secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolUrl);
+ String ssPmountPath = secondaryStoragePool.getLocalPath();
+ snapshotPath = ssPmountPath + File.separator + snapshotRelPath;
+
Script cmd = new Script(libvirtComputingResource.manageSnapshotPath(), libvirtComputingResource.getCmdsTimeout(), s_logger);
cmd.add("-v", snapshotPath);
cmd.add("-n", snapshotDisk.getName());
@@ -90,6 +118,12 @@ public final class LibvirtRevertSnapshotCommandWrapper extends CommandWrapper2.0.4
10.1
2.6.4
- 0.2.0
+ 0.5.0
1.5.1
0.9.11
2.5.8_1
diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
index 5d98c503ab9..8e1d685cd7a 100755
--- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
@@ -98,6 +98,7 @@ import com.cloud.storage.SnapshotScheduleVO;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateVO;
@@ -1263,7 +1264,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
}
}
- private static DataStoreRole getDataStoreRole(Snapshot snapshot, SnapshotDataStoreDao snapshotStoreDao, DataStoreManager dataStoreMgr) {
+ private DataStoreRole getDataStoreRole(Snapshot snapshot, SnapshotDataStoreDao snapshotStoreDao, DataStoreManager dataStoreMgr) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
@@ -1284,6 +1285,11 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
}
}
+ StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
+ if (storagePoolVO.getPoolType() == StoragePoolType.RBD && !BackupSnapshotAfterTakingSnapshot.value()) {
+ return DataStoreRole.Primary;
+ }
+
return DataStoreRole.Image;
}