Skip to content

Commit 2b075ed

Browse files
authored
Storage-based Snapshots for KVM VMs (#3724)
* VM snapshots of running KVM instance using storage providers plugins for disk snapshots Added new virtual machine snapshot strategy which is using storage providers plugins to take/revert/delete snapshots. You can take VM snapshot without VM memory on KVM instance, using storage providers implementations for disk snapshots. Also revert and delete is added as functionality. Added Thaw/Freeze command for KVM instance. The snapshots will be consistent, because we freeze the VM during the snapshotting. Backup to secondary storage is executed after thaw of the VM and if it is enabled in global settings. * Removed duplicated functionality Set few methods in DefaultVMSnapshotStrategy to protected to reuse them without duplicating the code. Remove code that is actualy not needed * Added requirements in global setting kvm.vmstoragesnapshot.enabled Added more information in kvm.vmstoragesnapshot.enabled global setting, that it needs installation of: - qemu version 1.6+ - qemu-guest-agent installed on guest virtual machine when the option is enabled * Added Apache license header * Removed commented code * If "kvm.vmstoragesnapshot.enabled" is null should be considered as false * removed unused imports, replaced default template Removed unused imports which causing failures and replaced template to CentOS8 * "kvm.vmstoragesnapshot.enabled" set to dynamic * Getting status of freeze/thaw commands not the return code Will chacke the status if freeze/thaw of Guest VM succeded, rather than looking for return code. Code refactoring * removed "CreatingKVM" VMsnapshot state and events related to it * renamed AllocatedKVM to AllocatedVM the states should not be associated to a hypervisor type * loggin the result of "drive-backup" command * Check which VM snapshot strategy could handle the vm snapshots gets the best match of VM snapshot strategy which could handle the vm snapshots on KVM. Other storage plugins could integrate with this functionality to support group snapshots * Added poolId in canHandle for KVM hypervisors Added poolId into canHandle method used to check if all volumes are on the same PowerFlex's storage pool * skip smoke tests if the hypervisor's OS type is CentOS This PR works with functionality included in qemu-kvm-ev which does not come by default on CentOS. The smoke tests will be skipped if the hypervisor OS is CentOS * Added missed import in smoke test * Suggested change to use ` org.apache.commons.lang.StringUtils.isNotBlank` * Fix getting device on Ubuntu On Ubuntu the device isn't provided and we have to get it from node-name parameter. For drive-backup command (for Ubuntu) is needed and job-id which is the value of node-name (this extra param works on Ubuntu and CentOS as well). * Removed new snapshot states and functionality for NFS * throw CloudRuntimeException provide a properer error message when delete VM snapshot fails * exclude GROUP snapshots when listing snapshots * Skip tests if there is pool with NFS/Local * address comments
1 parent c1c381f commit 2b075ed

File tree

17 files changed

+1606
-49
lines changed

17 files changed

+1606
-49
lines changed

api/src/main/java/com/cloud/storage/Snapshot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
public interface Snapshot extends ControlledEntity, Identity, InternalIdentity, StateObject<Snapshot.State> {
2828
public enum Type {
29-
MANUAL, RECURRING, TEMPLATE, HOURLY, DAILY, WEEKLY, MONTHLY;
29+
MANUAL, RECURRING, TEMPLATE, HOURLY, DAILY, WEEKLY, MONTHLY, GROUP;
3030
private int max = 8;
3131

3232
public void setMax(int max) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
20+
package com.cloud.agent.api;
21+
22+
public class FreezeThawVMAnswer extends Answer {
23+
24+
public FreezeThawVMAnswer() {
25+
super();
26+
}
27+
28+
public FreezeThawVMAnswer(FreezeThawVMCommand command, boolean success, String details) {
29+
super(command, success, details);
30+
}
31+
32+
public FreezeThawVMAnswer(FreezeThawVMCommand command, Exception e) {
33+
super(command, e);
34+
}
35+
36+
public FreezeThawVMAnswer(FreezeThawVMCommand command) {
37+
super(command);
38+
}
39+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
20+
package com.cloud.agent.api;
21+
22+
public class FreezeThawVMCommand extends Command{
23+
24+
public static final String FREEZE = "frozen";
25+
public static final String THAW = "thawed";
26+
public static final String STATUS = "status";
27+
28+
private String vmName;
29+
private String option;
30+
31+
public FreezeThawVMCommand(String vmName) {
32+
this.vmName = vmName;
33+
}
34+
35+
public String getVmName() {
36+
return vmName;
37+
}
38+
39+
public void setVmName(String vmName) {
40+
this.vmName = vmName;
41+
}
42+
43+
public String getOption() {
44+
return option;
45+
}
46+
47+
public void setOption(String option) {
48+
this.option = option;
49+
}
50+
51+
@Override
52+
public boolean executeInSequence() {
53+
return false;
54+
}
55+
56+
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageStrategyFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ public interface StorageStrategyFactory {
3636

3737
VMSnapshotStrategy getVmSnapshotStrategy(VMSnapshot vmSnapshot);
3838

39+
/**
40+
* Used only for KVM hypervisors when allocating a VM snapshot
41+
* @param vmId the ID of the virtual machine
42+
* @param snapshotMemory for VM snapshots with memory
43+
* @return VMSnapshotStrategy
44+
*/
45+
VMSnapshotStrategy getVmSnapshotStrategy(Long vmId, Long rootPoolId, boolean snapshotMemory);
46+
3947
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VMSnapshotStrategy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public interface VMSnapshotStrategy {
2929

3030
StrategyPriority canHandle(VMSnapshot vmSnapshot);
3131

32+
/**
33+
* Used only for KVM hypervisors when allocating a VM snapshot
34+
* @param vmId the ID of the virtual machine
35+
* @param snapshotMemory for VM snapshots with memory
36+
* @return StrategyPriority
37+
*/
38+
StrategyPriority canHandle(Long vmId, Long poolId, boolean snapshotMemory);
39+
3240
/**
3341
* Delete vm snapshot only from database. Introduced as a Vmware optimization in which vm snapshots are deleted when
3442
* the vm gets deleted on hypervisor (no need to delete each vm snapshot before deleting vm, just mark them as deleted on DB)

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import com.cloud.storage.GuestOSHypervisorVO;
5454
import com.cloud.storage.GuestOSVO;
5555
import com.cloud.storage.VolumeVO;
56+
import com.cloud.storage.Storage.ImageFormat;
5657
import com.cloud.storage.dao.DiskOfferingDao;
5758
import com.cloud.storage.dao.GuestOSDao;
5859
import com.cloud.storage.dao.GuestOSHypervisorDao;
@@ -67,6 +68,7 @@
6768
import com.cloud.utils.exception.CloudRuntimeException;
6869
import com.cloud.utils.fsm.NoTransitionException;
6970
import com.cloud.vm.UserVmVO;
71+
import com.cloud.vm.VirtualMachine.State;
7072
import com.cloud.vm.dao.UserVmDao;
7173
import com.cloud.vm.snapshot.VMSnapshot;
7274
import com.cloud.vm.snapshot.VMSnapshotVO;
@@ -332,7 +334,7 @@ private void updateVolumePath(List<VolumeObjectTO> volumeTOs) {
332334
}
333335
}
334336

335-
private void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm, VolumeObjectTO volumeTo) {
337+
protected void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm, VolumeObjectTO volumeTo) {
336338
VolumeVO volume = volumeDao.findById(volumeTo.getId());
337339
Long diskOfferingId = volume.getDiskOfferingId();
338340
Long offeringId = null;
@@ -350,7 +352,7 @@ private void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm
350352
volumeTo.getSize(), VMSnapshot.class.getName(), vmSnapshot.getUuid(), details);
351353
}
352354

353-
private void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm, Long vmSnapSize, Long virtualSize) {
355+
protected void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm, Long vmSnapSize, Long virtualSize) {
354356
try {
355357
Map<String, String> details = new HashMap<>();
356358
if (vmSnapshot != null) {
@@ -449,4 +451,20 @@ public boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot, boolean unmanage) {
449451
}
450452
return vmSnapshotDao.remove(vmSnapshot.getId());
451453
}
454+
455+
@Override
456+
public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
457+
UserVmVO vm = userVmDao.findById(vmId);
458+
if (vm.getState() == State.Running && !snapshotMemory) {
459+
return StrategyPriority.CANT_HANDLE;
460+
}
461+
462+
List<VolumeVO> volumes = volumeDao.findByInstance(vmId);
463+
for (VolumeVO volume : volumes) {
464+
if (volume.getFormat() != ImageFormat.QCOW2) {
465+
return StrategyPriority.CANT_HANDLE;
466+
}
467+
}
468+
return StrategyPriority.DEFAULT;
469+
}
452470
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
3838
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
3939
import org.apache.cloudstack.storage.to.VolumeObjectTO;
40+
import org.apache.commons.collections.CollectionUtils;
4041
import org.apache.log4j.Logger;
4142

4243
import com.cloud.agent.api.VMSnapshotTO;
@@ -47,6 +48,7 @@
4748
import com.cloud.server.ManagementServerImpl;
4849
import com.cloud.storage.DiskOfferingVO;
4950
import com.cloud.storage.Storage;
51+
import com.cloud.storage.Storage.ImageFormat;
5052
import com.cloud.storage.VolumeVO;
5153
import com.cloud.storage.dao.DiskOfferingDao;
5254
import com.cloud.storage.dao.VolumeDao;
@@ -105,6 +107,13 @@ public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
105107
throw new CloudRuntimeException("Failed to get the volumes for the vm snapshot: " + vmSnapshot.getUuid());
106108
}
107109

110+
if (!VMSnapshot.State.Allocated.equals(vmSnapshot.getState())) {
111+
List<VMSnapshotDetailsVO> vmDetails = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), "SnapshotGroupId" );
112+
if (CollectionUtils.isEmpty(vmDetails)) {
113+
return StrategyPriority.CANT_HANDLE;
114+
}
115+
}
116+
108117
if (volumeTOs != null && !volumeTOs.isEmpty()) {
109118
for (VolumeObjectTO volumeTO: volumeTOs) {
110119
Long poolId = volumeTO.getPoolId();
@@ -118,6 +127,27 @@ public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
118127
return StrategyPriority.HIGHEST;
119128
}
120129

130+
@Override
131+
public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
132+
if (snapshotMemory) {
133+
return StrategyPriority.CANT_HANDLE;
134+
}
135+
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmId);
136+
if (volumeTOs == null || volumeTOs.isEmpty()) {
137+
return StrategyPriority.CANT_HANDLE;
138+
}
139+
140+
for (VolumeObjectTO volumeTO : volumeTOs) {
141+
Long poolId = volumeTO.getPoolId();
142+
Storage.StoragePoolType poolType = vmSnapshotHelper.getStoragePoolType(poolId);
143+
if (poolType != Storage.StoragePoolType.PowerFlex || volumeTO.getFormat() != ImageFormat.RAW || poolId != rootPoolId) {
144+
return StrategyPriority.CANT_HANDLE;
145+
}
146+
}
147+
148+
return StrategyPriority.HIGHEST;
149+
}
150+
121151
@Override
122152
public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
123153
UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());

0 commit comments

Comments
 (0)