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; }