Skip to content

Commit 8171d95

Browse files
authored
Block use of internal and external snapshots on KVM (#11039)
1 parent 6dc259c commit 8171d95

File tree

8 files changed

+110
-6
lines changed

8 files changed

+110
-6
lines changed

api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ public interface BackupProvider {
124124
*/
125125
boolean supportsInstanceFromBackup();
126126

127+
default boolean supportsMemoryVmSnapshot() {
128+
return true;
129+
}
130+
127131
/**
128132
* Returns the backup storage usage (Used, Total) for a backup provider
129133
* @param zoneId the zone for which to return metrics

api/src/main/java/org/apache/cloudstack/backup/BackupService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,11 @@ public interface BackupService {
3434
* @return backup provider
3535
*/
3636
BackupProvider getBackupProvider(final Long zoneId);
37+
38+
/**
39+
* Find backup provider by name
40+
* @param name backup provider name
41+
* @return backup provider
42+
*/
43+
BackupProvider getBackupProvider(final String name);
3744
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,12 @@ private StrategyPriority validateVmSnapshot(Snapshot snapshot) {
672672
}
673673
}
674674

675+
if (CollectionUtils.isNotEmpty(vmSnapshotDao.findByVmAndByType(volumeVO.getInstanceId(), VMSnapshot.Type.DiskAndMemory))) {
676+
logger.debug("DefaultSnapshotStrategy cannot handle snapshot [{}] for volume [{}] as the volume is attached to a VM with disk-and-memory VM snapshots." +
677+
"Restoring the volume snapshot will corrupt any newer disk-and-memory VM snapshots.", snapshot);
678+
return StrategyPriority.CANT_HANDLE;
679+
}
680+
675681
return StrategyPriority.DEFAULT;
676682
}
677683

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,21 @@
2626
import javax.naming.ConfigurationException;
2727

2828
import com.cloud.hypervisor.Hypervisor;
29+
import com.cloud.storage.Snapshot;
30+
import com.cloud.storage.dao.SnapshotDao;
2931
import com.cloud.vm.snapshot.VMSnapshotDetailsVO;
3032
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
33+
import org.apache.cloudstack.backup.BackupManager;
34+
import org.apache.cloudstack.backup.BackupOfferingVO;
35+
import org.apache.cloudstack.backup.BackupProvider;
36+
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
3137
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
3238
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotOptions;
3339
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotStrategy;
3440
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
3541
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
3642
import org.apache.cloudstack.storage.to.VolumeObjectTO;
43+
import org.apache.commons.collections4.CollectionUtils;
3744
import org.apache.commons.lang3.StringUtils;
3845

3946
import com.cloud.agent.AgentManager;
@@ -104,7 +111,16 @@ public class DefaultVMSnapshotStrategy extends ManagerBase implements VMSnapshot
104111
PrimaryDataStoreDao primaryDataStoreDao;
105112

106113
@Inject
107-
VMSnapshotDetailsDao vmSnapshotDetailsDao;
114+
private VMSnapshotDetailsDao vmSnapshotDetailsDao;
115+
116+
@Inject
117+
private BackupManager backupManager;
118+
119+
@Inject
120+
private BackupOfferingDao backupOfferingDao;
121+
122+
@Inject
123+
private SnapshotDao snapshotDao;
108124

109125
protected static final String KVM_FILE_BASED_STORAGE_SNAPSHOT = "kvmFileBasedStorageSnapshot";
110126

@@ -480,24 +496,44 @@ public boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot, boolean unmanage) {
480496
@Override
481497
public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
482498
UserVmVO vm = userVmDao.findById(vmId);
499+
String cantHandleLog = String.format("Default VM snapshot cannot handle VM snapshot for [%s]", vm);
483500
if (State.Running.equals(vm.getState()) && !snapshotMemory) {
484-
logger.debug("Default VM snapshot strategy cannot handle VM snapshot for [{}] as it is running and its memory will not be affected.", vm);
501+
logger.debug("{} as it is running and its memory will not be affected.", cantHandleLog, vm);
485502
return StrategyPriority.CANT_HANDLE;
486503
}
487504

488505
if (vmHasKvmDiskOnlySnapshot(vm)) {
489-
logger.debug("Default VM snapshot strategy cannot handle VM snapshot for [{}] as it has a disk-only VM snapshot using kvmFileBasedStorageSnapshot strategy." +
490-
"These two strategies are not compatible, as reverting a disk-only VM snapshot will erase newer disk-and-memory VM snapshots.", vm);
506+
logger.debug("{} as it is not compatible with disk-only VM snapshot on KVM. As disk-and-memory snapshots use internal snapshots and disk-only VM snapshots use" +
507+
" external snapshots. When restoring external snapshots, any newer internal snapshots are lost.", cantHandleLog);
491508
return StrategyPriority.CANT_HANDLE;
492509
}
493510

494511
List<VolumeVO> volumes = volumeDao.findByInstance(vmId);
495512
for (VolumeVO volume : volumes) {
496513
if (volume.getFormat() != ImageFormat.QCOW2) {
497-
logger.debug("Default VM snapshot strategy cannot handle VM snapshot for [{}] as it has a volume [{}] that is not in the QCOW2 format.", vm, volume);
514+
logger.debug("{} as it has a volume [{}] that is not in the QCOW2 format.", cantHandleLog, vm, volume);
515+
return StrategyPriority.CANT_HANDLE;
516+
}
517+
518+
if (CollectionUtils.isNotEmpty(snapshotDao.listByVolumeIdAndTypeNotInAndStateNotRemoved(volume.getId(), Snapshot.Type.GROUP))) {
519+
logger.debug("{} as it has a volume [{}] with volume snapshots. As disk-and-memory snapshots use internal snapshots and volume snapshots use external" +
520+
" snapshots. When restoring external snapshots, any newer internal snapshots are lost.", cantHandleLog, volume);
498521
return StrategyPriority.CANT_HANDLE;
499522
}
500523
}
524+
525+
526+
BackupOfferingVO backupOfferingVO = backupOfferingDao.findById(vm.getBackupOfferingId());
527+
if (backupOfferingVO == null) {
528+
return StrategyPriority.DEFAULT;
529+
}
530+
531+
BackupProvider provider = backupManager.getBackupProvider(backupOfferingVO.getProvider());
532+
if (!provider.supportsMemoryVmSnapshot()) {
533+
logger.debug("{} as the VM has a backup offering for a provider that is not supported.", cantHandleLog);
534+
return StrategyPriority.CANT_HANDLE;
535+
}
536+
501537
return StrategyPriority.DEFAULT;
502538
}
503539

@@ -508,7 +544,7 @@ private boolean vmHasKvmDiskOnlySnapshot(UserVm vm) {
508544

509545
for (VMSnapshotVO vmSnapshotVO : vmSnapshotDao.findByVmAndByType(vm.getId(), VMSnapshot.Type.Disk)) {
510546
List<VMSnapshotDetailsVO> vmSnapshotDetails = vmSnapshotDetailsDao.listDetails(vmSnapshotVO.getId());
511-
if (vmSnapshotDetails.stream().anyMatch(vmSnapshotDetailsVO -> vmSnapshotDetailsVO.getName().equals(KVM_FILE_BASED_STORAGE_SNAPSHOT))) {
547+
if (vmSnapshotDetails.stream().anyMatch(detailsVO -> KVM_FILE_BASED_STORAGE_SNAPSHOT.equals(detailsVO.getName()) || STORAGE_SNAPSHOT.equals(detailsVO.getName()))) {
512548
return true;
513549
}
514550
}

engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
import javax.inject.Inject;
3131

32+
import org.apache.cloudstack.backup.BackupManager;
33+
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
3234
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
3335
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
3436
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
@@ -431,5 +433,15 @@ public DataStoreProviderManager manager() {
431433
public VMSnapshotDetailsDao vmSnapshotDetailsDao () {
432434
return Mockito.mock(VMSnapshotDetailsDao.class);
433435
}
436+
437+
@Bean
438+
public BackupOfferingDao backupOfferingDao() {
439+
return Mockito.mock(BackupOfferingDao.class);
440+
}
441+
442+
@Bean
443+
public BackupManager backupManager() {
444+
return Mockito.mock(BackupManager.class);
445+
}
434446
}
435447
}

engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525

2626
import javax.inject.Inject;
2727

28+
import com.cloud.storage.dao.SnapshotDao;
2829
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
30+
import org.apache.cloudstack.backup.BackupManager;
31+
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
2932
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotStrategy;
3033
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
3134
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@@ -318,5 +321,25 @@ public HostDao hostDao() {
318321
public PrimaryDataStoreDao primaryDataStoreDao() {
319322
return Mockito.mock(PrimaryDataStoreDao.class);
320323
}
324+
325+
@Bean
326+
public BackupOfferingDao backupOfferingDao() {
327+
return Mockito.mock(BackupOfferingDao.class);
328+
}
329+
330+
@Bean
331+
public VMSnapshotDetailsDao VMSnapshotDetailsDao() {
332+
return Mockito.mock(VMSnapshotDetailsDao.class);
333+
}
334+
335+
@Bean
336+
public SnapshotDao snapshotDao() {
337+
return Mockito.mock(SnapshotDao.class);
338+
}
339+
340+
@Bean
341+
public BackupManager backupManager() {
342+
return Mockito.mock(BackupManager.class);
343+
}
321344
}
322345
}

plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
5757
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
5858
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
59+
import org.apache.commons.collections4.CollectionUtils;
5960
import org.apache.logging.log4j.Logger;
6061
import org.apache.logging.log4j.LogManager;
6162

@@ -195,6 +196,12 @@ public Pair<Boolean, Backup> takeBackup(final VirtualMachine vm, Boolean quiesce
195196
throw new CloudRuntimeException("No valid backup repository found for the VM, please check the attached backup offering");
196197
}
197198

199+
if (CollectionUtils.isNotEmpty(vmSnapshotDao.findByVmAndByType(vm.getId(), VMSnapshot.Type.DiskAndMemory))) {
200+
logger.debug("NAS backup provider cannot take backups of a VM [{}] with disk-and-memory VM snapshots. Restoring the backup will corrupt any newer disk-and-memory " +
201+
"VM snapshots.", vm);
202+
throw new CloudRuntimeException(String.format("Cannot take backup of VM [%s] as it has disk-and-memory VM snapshots.", vm.getUuid()));
203+
}
204+
198205
final Date creationDate = new Date();
199206
final String backupPath = String.format("%s/%s", vm.getInstanceName(),
200207
new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(creationDate));
@@ -517,6 +524,11 @@ public boolean supportsInstanceFromBackup() {
517524
return true;
518525
}
519526

527+
@Override
528+
public boolean supportsMemoryVmSnapshot() {
529+
return false;
530+
}
531+
520532
@Override
521533
public Pair<Long, Long> getBackupStorageStats(Long zoneId) {
522534
final List<BackupRepository> repositories = backupRepositoryDao.listByZoneAndProvider(zoneId, getName());

plugins/backup/nas/src/test/java/org/apache/cloudstack/backup/NASBackupProviderTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Objects;
2525
import java.util.Optional;
2626

27+
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
2728
import org.junit.Assert;
2829
import org.junit.Test;
2930
import org.junit.runner.RunWith;
@@ -92,6 +93,9 @@ public class NASBackupProviderTest {
9293
@Mock
9394
private PrimaryDataStoreDao storagePoolDao;
9495

96+
@Mock
97+
private VMSnapshotDao vmSnapshotDaoMock;
98+
9599
@Test
96100
public void testDeleteBackup() throws OperationTimedoutException, AgentUnavailableException {
97101
Long hostId = 1L;

0 commit comments

Comments
 (0)