kvm: Add ceph RBD snapshot rollback (#3502)

Add CephSnapshotStrategy to handle RBD revert (rollback) snapshot. In order to support RBD revert (rbd_rollback), this PR adds a CephSnapshotStrategy class to handle Ceph/RBD snapshot actions.
This commit is contained in:
Gabriel Beims Bräscher 2019-07-23 11:10:56 -03:00 committed by Rohit Yadav
parent 281148d551
commit 6a511fce40
7 changed files with 283 additions and 29 deletions

View File

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

View File

@ -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) {

View File

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

View File

@ -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());
}
}
}
}

View File

@ -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<RevertSnapshotCommand, Answer, LibvirtComputingResource> {
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 CommandWrapper<Re
return new Answer(command, true, "RevertSnapshotCommand executes successfully");
} catch (CloudRuntimeException e) {
return new Answer(command, false, e.toString());
} catch (RadosException e) {
s_logger.error("Failed to connect to Rados pool while trying to revert snapshot. Exception: ", e);
return new Answer(command, false, e.toString());
} catch (RbdException e) {
s_logger.error("Failed to connect to revert snapshot due to RBD exception: ", e);
return new Answer(command, false, e.toString());
}
}
}

View File

@ -153,7 +153,7 @@
<cs.neethi.version>2.0.4</cs.neethi.version>
<cs.nitro.version>10.1</cs.nitro.version>
<cs.opensaml.version>2.6.4</cs.opensaml.version>
<cs.rados-java.version>0.2.0</cs.rados-java.version>
<cs.rados-java.version>0.5.0</cs.rados-java.version>
<cs.rampart.version>1.5.1</cs.rampart.version>
<cs.reflections.version>0.9.11</cs.reflections.version>
<cs.servicemix.version>2.5.8_1</cs.servicemix.version>

View File

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