Skip to content

Commit 6a511fc

Browse files
Gabriel Beims Bräscheryadvr
authored andcommitted
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.
1 parent 281148d commit 6a511fc

7 files changed

Lines changed: 283 additions & 29 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.storage.snapshot;
20+
21+
import javax.inject.Inject;
22+
23+
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
24+
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
25+
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
26+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
27+
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
28+
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
29+
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
30+
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
31+
import org.apache.log4j.Logger;
32+
import org.springframework.stereotype.Component;
33+
34+
import com.cloud.storage.DataStoreRole;
35+
import com.cloud.storage.Snapshot;
36+
import com.cloud.storage.Storage.ImageFormat;
37+
import com.cloud.storage.Storage.StoragePoolType;
38+
import com.cloud.storage.VolumeVO;
39+
import com.cloud.storage.dao.VolumeDao;
40+
41+
public class CephSnapshotStrategy extends StorageSystemSnapshotStrategy {
42+
@Inject
43+
private SnapshotDataStoreDao snapshotStoreDao;
44+
@Inject
45+
private PrimaryDataStoreDao primaryDataStoreDao;
46+
@Inject
47+
private VolumeDao volumeDao;
48+
49+
private static final Logger s_logger = Logger.getLogger(CephSnapshotStrategy.class);
50+
51+
@Override
52+
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
53+
long volumeId = snapshot.getVolumeId();
54+
VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
55+
boolean baseVolumeExists = volumeVO.getRemoved() == null;
56+
if (!baseVolumeExists) {
57+
return StrategyPriority.CANT_HANDLE;
58+
}
59+
60+
if (SnapshotOperation.REVERT.equals(op) && isSnapshotStoredOnRbdStoragePool(snapshot)) {
61+
return StrategyPriority.HIGHEST;
62+
}
63+
return StrategyPriority.CANT_HANDLE;
64+
}
65+
66+
@Override
67+
public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
68+
VolumeInfo volumeInfo = snapshotInfo.getBaseVolume();
69+
ImageFormat imageFormat = volumeInfo.getFormat();
70+
if (!ImageFormat.RAW.equals(imageFormat)) {
71+
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));
72+
return false;
73+
}
74+
75+
executeRevertSnapshot(snapshotInfo, volumeInfo);
76+
77+
return true;
78+
}
79+
80+
protected boolean isSnapshotStoredOnRbdStoragePool(Snapshot snapshot) {
81+
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
82+
long snapshotStoragePoolId = snapshotStore.getDataStoreId();
83+
StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapshotStoragePoolId);
84+
return storagePoolVO.getPoolType() == StoragePoolType.RBD;
85+
}
86+
}

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
102102
@Inject private SnapshotDataFactory snapshotDataFactory;
103103
@Inject private SnapshotDetailsDao snapshotDetailsDao;
104104
@Inject private SnapshotDataStoreDao snapshotStoreDao;
105-
@Inject private VolumeDetailsDao volumeDetailsDao;
106105
@Inject private VMInstanceDao vmInstanceDao;
107106
@Inject private VMSnapshotDao vmSnapshotDao;
108107
@Inject private VMSnapshotService vmSnapshotService;
@@ -307,8 +306,7 @@ public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
307306
}
308307
}
309308

310-
boolean storageSystemSupportsCapability = storageSystemSupportsCapability(volumeInfo.getPoolId(),
311-
DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
309+
boolean storageSystemSupportsCapability = storageSystemSupportsCapability(volumeInfo.getPoolId(), DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString());
312310

313311
if (!storageSystemSupportsCapability) {
314312
String errMsg = "Storage pool revert capability not supported";
@@ -318,6 +316,18 @@ public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
318316
throw new CloudRuntimeException(errMsg);
319317
}
320318

319+
executeRevertSnapshot(snapshotInfo, volumeInfo);
320+
321+
return true;
322+
}
323+
324+
/**
325+
* Executes the SnapshotStrategyBase.revertSnapshot(SnapshotInfo) method, and handles the SnapshotVO table update and the Volume.Event state machine (RevertSnapshotRequested).
326+
*/
327+
protected void executeRevertSnapshot(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo) {
328+
Long hostId = null;
329+
boolean success = false;
330+
321331
SnapshotVO snapshotVO = snapshotDao.acquireInLockTable(snapshotInfo.getId());
322332

323333
if (snapshotVO == null) {
@@ -328,9 +338,6 @@ public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
328338
throw new CloudRuntimeException(errMsg);
329339
}
330340

331-
Long hostId = null;
332-
boolean success = false;
333-
334341
try {
335342
volumeInfo.stateTransit(Volume.Event.RevertSnapshotRequested);
336343

@@ -350,14 +357,14 @@ public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
350357
success = snapshotSvr.revertSnapshot(snapshotInfo);
351358

352359
if (!success) {
353-
String errMsg = "Failed to revert a volume to a snapshot state";
360+
String errMsg = String.format("Failed to revert volume [name:%s, format:%s] to snapshot [id:%s] state", volumeInfo.getName(), volumeInfo.getFormat(),
361+
snapshotInfo.getSnapshotId());
354362

355363
s_logger.error(errMsg);
356364

357365
throw new CloudRuntimeException(errMsg);
358366
}
359-
}
360-
finally {
367+
} finally {
361368
if (getHypervisorRequiresResignature(volumeInfo)) {
362369
if (hostId != null) {
363370
HostVO hostVO = hostDao.findById(hostId);
@@ -371,15 +378,12 @@ public boolean revertSnapshot(SnapshotInfo snapshotInfo) {
371378

372379
if (success) {
373380
volumeInfo.stateTransit(Volume.Event.OperationSucceeded);
374-
}
375-
else {
381+
} else {
376382
volumeInfo.stateTransit(Volume.Event.OperationFailed);
377383
}
378384

379385
snapshotDao.releaseFromLockTable(snapshotInfo.getId());
380386
}
381-
382-
return true;
383387
}
384388

385389
private Long getHostId(VolumeInfo volumeInfo) {

engine/storage/snapshot/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232

3333
<bean id="storageSystemSnapshotStrategy"
3434
class="org.apache.cloudstack.storage.snapshot.StorageSystemSnapshotStrategy" />
35+
36+
<bean id="cephSnapshotStrategy"
37+
class="org.apache.cloudstack.storage.snapshot.CephSnapshotStrategy" />
3538

3639
<bean id="DefaultVMSnapshotStrategy"
3740
class="org.apache.cloudstack.storage.vmsnapshot.DefaultVMSnapshotStrategy" />
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.storage.snapshot;
20+
21+
import java.util.Date;
22+
23+
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
24+
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
25+
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
26+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
27+
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
28+
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
29+
import org.junit.Assert;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.mockito.InjectMocks;
33+
import org.mockito.Mock;
34+
import org.mockito.Mockito;
35+
import org.mockito.Spy;
36+
import org.mockito.runners.MockitoJUnitRunner;
37+
38+
import com.cloud.storage.Snapshot;
39+
import com.cloud.storage.Storage.ImageFormat;
40+
import com.cloud.storage.VolumeVO;
41+
import com.cloud.storage.dao.VolumeDao;
42+
43+
@RunWith(MockitoJUnitRunner.class)
44+
public class CephSnapshotStrategyTest {
45+
46+
@Spy
47+
@InjectMocks
48+
private CephSnapshotStrategy cephSnapshotStrategy;
49+
@Mock
50+
private SnapshotDataStoreDao snapshotStoreDao;
51+
@Mock
52+
private PrimaryDataStoreDao primaryDataStoreDao;
53+
@Mock
54+
private VolumeDao volumeDao;
55+
56+
@Test
57+
public void canHandleTestNotReomvedAndSnapshotStoredOnRbd() {
58+
configureAndVerifyCanHandle(null, true);
59+
}
60+
61+
@Test
62+
public void canHandleTestNotReomvedAndSnapshotNotStoredOnRbd() {
63+
configureAndVerifyCanHandle(null, false);
64+
}
65+
66+
@Test
67+
public void canHandleTestReomvedAndSnapshotNotStoredOnRbd() {
68+
configureAndVerifyCanHandle(null, false);
69+
}
70+
71+
@Test
72+
public void canHandleTestReomvedAndSnapshotStoredOnRbd() {
73+
configureAndVerifyCanHandle(null, true);
74+
}
75+
76+
private void configureAndVerifyCanHandle(Date removed, boolean isSnapshotStoredOnRbdStoragePool) {
77+
Snapshot snapshot = Mockito.mock(Snapshot.class);
78+
SnapshotOperation[] snapshotOps = SnapshotOperation.values();
79+
80+
Mockito.when(snapshot.getVolumeId()).thenReturn(0l);
81+
VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
82+
Mockito.when(volumeVO.getRemoved()).thenReturn(removed);
83+
Mockito.when(volumeDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(volumeVO);
84+
Mockito.doReturn(isSnapshotStoredOnRbdStoragePool).when(cephSnapshotStrategy).isSnapshotStoredOnRbdStoragePool(Mockito.any());
85+
86+
for (int i = 0; i < snapshotOps.length - 1; i++) {
87+
StrategyPriority strategyPriority = cephSnapshotStrategy.canHandle(snapshot, snapshotOps[i]);
88+
if (snapshotOps[i] == SnapshotOperation.REVERT && isSnapshotStoredOnRbdStoragePool) {
89+
Assert.assertEquals(StrategyPriority.HIGHEST, strategyPriority);
90+
} else {
91+
Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
92+
}
93+
}
94+
}
95+
96+
@Test
97+
public void revertSnapshotTest() {
98+
ImageFormat[] imageFormatValues = ImageFormat.values();
99+
100+
for (int i = 0; i < imageFormatValues.length - 1; i++) {
101+
Mockito.reset(cephSnapshotStrategy);
102+
SnapshotInfo snapshotInfo = Mockito.mock(SnapshotInfo.class);
103+
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
104+
Mockito.when(snapshotInfo.getBaseVolume()).thenReturn(volumeInfo);
105+
Mockito.when(volumeInfo.getFormat()).thenReturn(imageFormatValues[i]);
106+
Mockito.doNothing().when(cephSnapshotStrategy).executeRevertSnapshot(Mockito.any(), Mockito.any());
107+
108+
boolean revertResult = cephSnapshotStrategy.revertSnapshot(snapshotInfo);
109+
110+
if (imageFormatValues[i] == ImageFormat.RAW) {
111+
Assert.assertTrue(revertResult);
112+
Mockito.verify(cephSnapshotStrategy).executeRevertSnapshot(Mockito.any(), Mockito.any());
113+
} else {
114+
Assert.assertFalse(revertResult);
115+
Mockito.verify(cephSnapshotStrategy, Mockito.times(0)).executeRevertSnapshot(Mockito.any(), Mockito.any());
116+
}
117+
}
118+
119+
}
120+
121+
}

0 commit comments

Comments
 (0)