CLOUDSTACK-8746: vm snapshot implementation for KVM

(1) add support to create/delete/revert vm snapshots on running vms with QCOW2 format
(2) add new API to create volume snapshot from vm snapshot
(3) delete metadata of vm snapshots before stopping/migrating and recover vm snapshots after starting/migrating
(4) enable deleting of VM snapshot on stopped vm or vm snapshot is not listed in qcow2 image.
(5) enable smoke tests for vmsnaphsots on KVM
This commit is contained in:
Wei Zhou 2015-12-14 11:17:48 +01:00
parent 9513053f42
commit a2428508e2
34 changed files with 1602 additions and 257 deletions

View File

@ -102,4 +102,6 @@ public interface VolumeApiService {
boolean isDisplayResourceEnabled(Long id);
void updateDisplay(Volume volume, Boolean displayVolume);
Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException;
}

View File

@ -108,5 +108,7 @@ public interface SnapshotApiService {
Snapshot revertSnapshot(Long snapshotId);
Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId);
SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd);
}

View File

@ -36,7 +36,7 @@ public interface VMSnapshotService {
VMSnapshot getVMSnapshotById(Long id);
VMSnapshot creatVMSnapshot(Long vmId, Long vmSnapshotId, Boolean quiescevm);
VMSnapshot createVMSnapshot(Long vmId, Long vmSnapshotId, Boolean quiescevm);
VMSnapshot allocVMSnapshot(Long vmId, String vsDisplayName, String vsDescription, Boolean snapshotMemory) throws ResourceAllocationException;

View File

@ -240,6 +240,7 @@ public class ApiConstants {
public static final String SIGNATURE = "signature";
public static final String SIGNATURE_VERSION = "signatureversion";
public static final String SIZE = "size";
public static final String SNAPSHOT = "snapshot";
public static final String SNAPSHOT_ID = "snapshotid";
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
public static final String SNAPSHOT_TYPE = "snapshottype";

View File

@ -143,7 +143,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
}
public static String getResultObjectName() {
return "snapshot";
return ApiConstants.SNAPSHOT;
}
@Override

View File

@ -0,0 +1,219 @@
// 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.api.command.user.snapshot;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandJobType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.VMSnapshotResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.projects.Project;
import com.cloud.storage.Snapshot;
import com.cloud.user.Account;
import com.cloud.uservm.UserVm;
import com.cloud.vm.snapshot.VMSnapshot;
@APICommand(name = "createSnapshotFromVMSnapshot", description = "Creates an instant snapshot of a volume from existing vm snapshot.", responseObject = SnapshotResponse.class, entityType = {Snapshot.class}, since = "4.10.0",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class CreateSnapshotFromVMSnapshotCmd extends BaseAsyncCreateCmd {
public static final Logger s_logger = Logger.getLogger(CreateSnapshotFromVMSnapshotCmd.class.getName());
private static final String s_name = "createsnapshotfromvmsnapshotresponse";
// ///////////////////////////////////////////////////
// ////////////// API parameters /////////////////////
// ///////////////////////////////////////////////////
@Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the disk volume")
private Long volumeId;
@Parameter(name=ApiConstants.VM_SNAPSHOT_ID, type=CommandType.UUID, entityType=VMSnapshotResponse.class,
required=true, description="The ID of the VM snapshot")
private Long vmSnapshotId;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the snapshot")
private String snapshotName;
private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;
// ///////////////////////////////////////////////////
// ///////////////// Accessors ///////////////////////
// ///////////////////////////////////////////////////
public Long getVolumeId() {
return volumeId;
}
public Long getVMSnapshotId() {
return vmSnapshotId;
}
public String getSnapshotName() {
return snapshotName;
}
private Long getVmId() {
VMSnapshot vmsnapshot = _entityMgr.findById(VMSnapshot.class, getVMSnapshotId());
if (vmsnapshot == null) {
throw new InvalidParameterValueException("Unable to find vm snapshot by id=" + getVMSnapshotId());
}
UserVm vm = _entityMgr.findById(UserVm.class, vmsnapshot.getVmId());
if (vm == null) {
throw new InvalidParameterValueException("Unable to find vm by vm snapshot id=" + getVMSnapshotId());
}
return vm.getId();
}
private Long getHostId() {
VMSnapshot vmsnapshot = _entityMgr.findById(VMSnapshot.class, getVMSnapshotId());
if (vmsnapshot == null) {
throw new InvalidParameterValueException("Unable to find vm snapshot by id=" + getVMSnapshotId());
}
UserVm vm = _entityMgr.findById(UserVm.class, vmsnapshot.getVmId());
if (vm != null) {
if(vm.getHostId() != null) {
return vm.getHostId();
} else if(vm.getLastHostId() != null) {
return vm.getLastHostId();
}
}
return null;
}
// ///////////////////////////////////////////////////
// ///////////// API Implementation///////////////////
// ///////////////////////////////////////////////////
@Override
public String getCommandName() {
return s_name;
}
public static String getResultObjectName() {
return ApiConstants.SNAPSHOT;
}
@Override
public long getEntityOwnerId() {
VMSnapshot vmsnapshot = _entityMgr.findById(VMSnapshot.class, getVMSnapshotId());
if (vmsnapshot == null) {
throw new InvalidParameterValueException("Unable to find vmsnapshot by id=" + getVMSnapshotId());
}
Account account = _accountService.getAccount(vmsnapshot.getAccountId());
//Can create templates for enabled projects/accounts only
if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) {
Project project = _projectService.findByProjectAccountId(vmsnapshot.getAccountId());
if (project == null) {
throw new InvalidParameterValueException("Unable to find project by account id=" + account.getUuid());
}
if (project.getState() != Project.State.Active) {
throw new PermissionDeniedException("Can't add resources to the project id=" + project.getUuid() + " in state=" + project.getState() + " as it's no longer active");
}
} else if (account.getState() == Account.State.disabled) {
throw new PermissionDeniedException("The owner of template is disabled: " + account);
}
return vmsnapshot.getAccountId();
}
@Override
public String getEventType() {
return EventTypes.EVENT_SNAPSHOT_CREATE;
}
@Override
public String getEventDescription() {
return "creating snapshot from vm snapshot : " + getVMSnapshotId();
}
@Override
public ApiCommandJobType getInstanceType() {
return ApiCommandJobType.Snapshot;
}
@Override
public void create() throws ResourceAllocationException {
Snapshot snapshot = this._volumeService.allocSnapshotForVm(getVmId(), getVolumeId(), getSnapshotName());
if (snapshot != null) {
this.setEntityId(snapshot.getId());
this.setEntityUuid(snapshot.getUuid());
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot from vm snapshot");
}
}
@Override
public void execute() {
s_logger.info("CreateSnapshotFromVMSnapshotCmd with vm snapshot id:" + getVMSnapshotId() + " and snapshot id:" + getEntityId() + " starts:" + System.currentTimeMillis());
CallContext.current().setEventDetails("Vm Snapshot Id: "+ getVMSnapshotId());
Snapshot snapshot = null;
try {
snapshot = _snapshotService.backupSnapshotFromVmSnapshot(getEntityId(), getVmId(), getVolumeId(), getVMSnapshotId());
if (snapshot != null) {
SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot from vm snapshot " + getVMSnapshotId());
}
} catch (InvalidParameterValueException ex) {
throw ex;
} catch (Exception e) {
s_logger.debug("Failed to create snapshot", e);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot from vm snapshot " + getVMSnapshotId());
} finally {
if (snapshot == null) {
try {
_snapshotService.deleteSnapshot(getEntityId());
} catch (Exception e) {
s_logger.debug("Failed to clean failed snapshot" + getEntityId());
}
}
}
}
@Override
public String getSyncObjType() {
if (getSyncObjId() != null) {
return syncObjectType;
}
return null;
}
@Override
public Long getSyncObjId() {
if (getHostId() != null) {
return getHostId();
}
return null;
}
}

View File

@ -110,7 +110,7 @@ public class CreateVMSnapshotCmd extends BaseAsyncCreateCmd {
@Override
public void execute() {
CallContext.current().setEventDetails("VM Id: " + getVmId());
VMSnapshot result = _vmSnapshotService.creatVMSnapshot(getVmId(), getEntityId(), getQuiescevm());
VMSnapshot result = _vmSnapshotService.createVMSnapshot(getVmId(), getEntityId(), getQuiescevm());
if (result != null) {
VMSnapshotResponse response = _responseGenerator.createVMSnapshotResponse(result);
response.setResponseName(getCommandName());

View File

@ -0,0 +1,63 @@
//
// 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 com.cloud.agent.api;
import java.util.List;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import com.cloud.vm.VirtualMachine;
public class RestoreVMSnapshotAnswer extends Answer {
private List<VolumeObjectTO> volumeTOs;
private VirtualMachine.PowerState vmState;
public RestoreVMSnapshotAnswer(RestoreVMSnapshotCommand cmd, boolean result, String message) {
super(cmd, result, message);
}
public RestoreVMSnapshotAnswer() {
super();
}
public RestoreVMSnapshotAnswer(RestoreVMSnapshotCommand cmd, List<VolumeObjectTO> volumeTOs, VirtualMachine.PowerState vmState) {
super(cmd, true, "");
this.volumeTOs = volumeTOs;
this.vmState = vmState;
}
public VirtualMachine.PowerState getVmState() {
return vmState;
}
public List<VolumeObjectTO> getVolumeTOs() {
return volumeTOs;
}
public void setVolumeTOs(List<VolumeObjectTO> volumeTOs) {
this.volumeTOs = volumeTOs;
}
public void setVmState(VirtualMachine.PowerState vmState) {
this.vmState = vmState;
}
}

View File

@ -0,0 +1,52 @@
//
// 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 com.cloud.agent.api;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
public class RestoreVMSnapshotCommand extends VMSnapshotBaseCommand {
List<VMSnapshotTO> snapshots;
Map<Long, VMSnapshotTO> snapshotAndParents;
public RestoreVMSnapshotCommand(String vmName, VMSnapshotTO snapshot, List<VolumeObjectTO> volumeTOs, String guestOSType) {
super(vmName, snapshot, volumeTOs, guestOSType);
}
public List<VMSnapshotTO> getSnapshots() {
return snapshots;
}
public void setSnapshots(List<VMSnapshotTO> snapshots) {
this.snapshots = snapshots;
}
public Map<Long, VMSnapshotTO> getSnapshotAndParents() {
return snapshotAndParents;
}
public void setSnapshotAndParents(Map<Long, VMSnapshotTO> snapshotAndParents) {
this.snapshotAndParents = snapshotAndParents;
}
}

View File

@ -17,7 +17,11 @@
package com.cloud.vm.snapshot;
import java.util.List;
import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.utils.component.Manager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
public interface VMSnapshotManager extends VMSnapshotService, Manager {
@ -42,4 +46,7 @@ public interface VMSnapshotManager extends VMSnapshotService, Manager {
boolean syncVMSnapshot(VMInstanceVO vm, Long hostId);
boolean hasActiveVMSnapshotTasks(Long vmId);
RestoreVMSnapshotCommand createRestoreCommand(UserVmVO userVm, List<VMSnapshotVO> vmSnapshotVOs);
}

View File

@ -85,6 +85,8 @@ import com.cloud.agent.api.PlugNicCommand;
import com.cloud.agent.api.PrepareForMigrationCommand;
import com.cloud.agent.api.RebootAnswer;
import com.cloud.agent.api.RebootCommand;
import com.cloud.agent.api.RestoreVMSnapshotAnswer;
import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.agent.api.ScaleVmCommand;
import com.cloud.agent.api.StartAnswer;
import com.cloud.agent.api.StartCommand;
@ -201,6 +203,7 @@ import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.VMSnapshotManager;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable {
@ -1721,6 +1724,18 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
}
UserVmVO userVm = _userVmDao.findById(vm.getId());
if (userVm != null) {
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vm.getId());
RestoreVMSnapshotCommand command = _vmSnapshotMgr.createRestoreCommand(userVm, vmSnapshots);
if (command != null) {
RestoreVMSnapshotAnswer restoreVMSnapshotAnswer = (RestoreVMSnapshotAnswer) _agentMgr.send(hostId, command);
if (restoreVMSnapshotAnswer == null || !restoreVMSnapshotAnswer.getResult()) {
s_logger.warn("Unable to restore the vm snapshot from image file after live migration of vm with vmsnapshots: " + restoreVMSnapshotAnswer.getDetails());
}
}
}
return true;
}
@ -2603,7 +2618,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
private void orchestrateReboot(final String vmUuid, final Map<VirtualMachineProfile.Param, Object> params) throws InsufficientCapacityException, ConcurrentOperationException,
ResourceUnavailableException {
final VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
// if there are active vm snapshots task, state change is not allowed
if(_vmSnapshotMgr.hasActiveVMSnapshotTasks(vm.getId())){
s_logger.error("Unable to reboot VM " + vm + " due to: " + vm.getInstanceName() + " has active VM snapshots tasks");
throw new CloudRuntimeException("Unable to reboot VM " + vm + " due to: " + vm.getInstanceName() + " has active VM snapshots tasks");
}
final DataCenter dc = _entityMgr.findById(DataCenter.class, vm.getDataCenterId());
final Host host = _hostDao.findById(vm.getHostId());
if (host == null) {

View File

@ -218,6 +218,12 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
@Override
public boolean deleteSnapshot(Long snapshotId) {
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
if (snapshotVO.getState() == Snapshot.State.Allocated) {
snapshotDao.remove(snapshotId);
return true;
}
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
return true;
}

View File

@ -22,6 +22,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
@ -44,6 +45,9 @@ import java.util.regex.Pattern;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.google.common.base.Strings;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
@ -62,7 +66,14 @@ import org.libvirt.Domain;
import org.libvirt.DomainBlockStats;
import org.libvirt.DomainInfo;
import org.libvirt.DomainInfo.DomainState;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.libvirt.DomainInterfaceStats;
import org.libvirt.DomainSnapshot;
import org.libvirt.LibvirtException;
import org.libvirt.MemoryStatistic;
import org.libvirt.NodeInfo;
@ -142,6 +153,7 @@ import com.cloud.utils.NumbersUtil;
import com.cloud.utils.StringUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.PropertiesUtil;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.OutputInterpreter;
@ -2776,6 +2788,22 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
DomainState state = null;
Domain dm = null;
// delete the metadata of vm snapshots before stopping
try {
dm = conn.domainLookupByName(vmName);
cleanVMSnapshotMetadata(dm);
} catch (LibvirtException e) {
s_logger.debug("Failed to get vm :" + e.getMessage());
} finally {
try {
if (dm != null) {
dm.free();
}
} catch (LibvirtException l) {
s_logger.trace("Ignoring libvirt error.", l);
}
}
s_logger.debug("Try to stop the vm at first");
String ret = stopVM(conn, vmName, false);
if (ret == Script.ERR_TIMEOUT) {
@ -3481,4 +3509,77 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
}
return device;
}
public List<Ternary<String, Boolean, String>> cleanVMSnapshotMetadata(Domain dm) throws LibvirtException {
s_logger.debug("Cleaning the metadata of vm snapshots of vm " + dm.getName());
List<Ternary<String, Boolean, String>> vmsnapshots = new ArrayList<Ternary<String, Boolean, String>>();
if (dm.snapshotNum() == 0) {
return vmsnapshots;
}
String currentSnapshotName = null;
try {
DomainSnapshot snapshotCurrent = dm.snapshotCurrent();
String snapshotXML = snapshotCurrent.getXMLDesc();
snapshotCurrent.free();
DocumentBuilder builder;
try {
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(snapshotXML));
Document doc = builder.parse(is);
Element rootElement = doc.getDocumentElement();
currentSnapshotName = getTagValue("name", rootElement);
} catch (ParserConfigurationException e) {
s_logger.debug(e.toString());
} catch (SAXException e) {
s_logger.debug(e.toString());
} catch (IOException e) {
s_logger.debug(e.toString());
}
} catch (LibvirtException e) {
s_logger.debug("Fail to get the current vm snapshot for vm: " + dm.getName() + ", continue");
}
int flags = 2; // VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = 2
String[] snapshotNames = dm.snapshotListNames();
Arrays.sort(snapshotNames);
for (String snapshotName: snapshotNames) {
DomainSnapshot snapshot = dm.snapshotLookupByName(snapshotName);
Boolean isCurrent = (currentSnapshotName != null && currentSnapshotName.equals(snapshotName)) ? true: false;
vmsnapshots.add(new Ternary<String, Boolean, String>(snapshotName, isCurrent, snapshot.getXMLDesc()));
}
for (String snapshotName: snapshotNames) {
DomainSnapshot snapshot = dm.snapshotLookupByName(snapshotName);
snapshot.delete(flags); // clean metadata of vm snapshot
}
return vmsnapshots;
}
private static String getTagValue(String tag, Element eElement) {
NodeList nlList = eElement.getElementsByTagName(tag).item(0).getChildNodes();
Node nValue = nlList.item(0);
return nValue.getNodeValue();
}
public void restoreVMSnapshotMetadata(Domain dm, String vmName, List<Ternary<String, Boolean, String>> vmsnapshots) {
s_logger.debug("Restoring the metadata of vm snapshots of vm " + vmName);
for (Ternary<String, Boolean, String> vmsnapshot: vmsnapshots) {
String snapshotName = vmsnapshot.first();
Boolean isCurrent = vmsnapshot.second();
String snapshotXML = vmsnapshot.third();
s_logger.debug("Restoring vm snapshot " + snapshotName + " on " + vmName + " with XML:\n " + snapshotXML);
try {
int flags = 1; // VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE = 1
if (isCurrent) {
flags += 2; // VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT = 2
}
dm.snapshotCreateXML(snapshotXML, flags);
} catch (LibvirtException e) {
s_logger.debug("Failed to restore vm snapshot " + snapshotName + ", continue");
continue;
}
}
}
}

View File

@ -0,0 +1,82 @@
//
// 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 com.cloud.hypervisor.kvm.resource.wrapper;
import org.apache.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainInfo.DomainState;
import org.libvirt.LibvirtException;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CreateVMSnapshotAnswer;
import com.cloud.agent.api.CreateVMSnapshotCommand;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
@ResourceWrapper(handles = CreateVMSnapshotCommand.class)
public final class LibvirtCreateVMSnapshotCommandWrapper extends CommandWrapper<CreateVMSnapshotCommand, Answer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtCreateVMSnapshotCommandWrapper.class);
@Override
public Answer execute(final CreateVMSnapshotCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
String vmName = cmd.getVmName();
String vmSnapshotName = cmd.getTarget().getSnapshotName();
Domain dm = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
Connect conn = libvirtUtilitiesHelper.getConnection();
dm = libvirtComputingResource.getDomain(conn, vmName);
if (dm == null) {
return new CreateVMSnapshotAnswer(cmd, false,
"Create VM Snapshot Failed due to can not find vm: " + vmName);
}
DomainState domainState = dm.getInfo().state ;
if (domainState != DomainState.VIR_DOMAIN_RUNNING) {
return new CreateVMSnapshotAnswer(cmd, false,
"Create VM Snapshot Failed due to vm is not running: " + vmName + " with domainState = " + domainState);
}
String vmSnapshotXML = "<domainsnapshot>" + " <name>" + vmSnapshotName + "</name>"
+ " <memory snapshot='internal' />" + "</domainsnapshot>";
dm.snapshotCreateXML(vmSnapshotXML);
return new CreateVMSnapshotAnswer(cmd, cmd.getTarget(), cmd.getVolumeTOs());
} catch (LibvirtException e) {
String msg = " Create VM snapshot failed due to " + e.toString();
s_logger.warn(msg, e);
return new CreateVMSnapshotAnswer(cmd, false, msg);
} finally {
if (dm != null) {
try {
dm.free();
} catch (LibvirtException l) {
s_logger.trace("Ignoring libvirt error.", l);
};
}
}
}
}

View File

@ -0,0 +1,110 @@
//
// 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 com.cloud.hypervisor.kvm.resource.wrapper;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainSnapshot;
import org.libvirt.LibvirtException;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.DeleteVMSnapshotAnswer;
import com.cloud.agent.api.DeleteVMSnapshotCommand;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Volume;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = DeleteVMSnapshotCommand.class)
public final class LibvirtDeleteVMSnapshotCommandWrapper extends CommandWrapper<DeleteVMSnapshotCommand, Answer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtDeleteVMSnapshotCommandWrapper.class);
@Override
public Answer execute(final DeleteVMSnapshotCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
String vmName = cmd.getVmName();
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
Domain dm = null;
DomainSnapshot snapshot = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
Connect conn = libvirtUtilitiesHelper.getConnection();
dm = libvirtComputingResource.getDomain(conn, vmName);
snapshot = dm.snapshotLookupByName(cmd.getTarget().getSnapshotName());
snapshot.delete(0); // only remove this snapshot, not children
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
} catch (LibvirtException e) {
String msg = " Delete VM snapshot failed due to " + e.toString();
if (dm == null) {
s_logger.debug("Can not find running vm: " + vmName + ", now we are trying to delete the vm snapshot using qemu-img if the format of root volume is QCOW2");
VolumeObjectTO rootVolume = null;
for (VolumeObjectTO volume: cmd.getVolumeTOs()) {
if (volume.getVolumeType() == Volume.Type.ROOT) {
rootVolume = volume;
break;
}
}
if (rootVolume != null && ImageFormat.QCOW2.equals(rootVolume.getFormat())) {
PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO) rootVolume.getDataStore();
KVMPhysicalDisk rootDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(),
primaryStore.getUuid(), rootVolume.getPath());
String qemu_img_snapshot = Script.runSimpleBashScript("qemu-img snapshot -l " + rootDisk.getPath() + " | tail -n +3 | awk -F ' ' '{print $2}' | grep ^" + cmd.getTarget().getSnapshotName() + "$");
if (qemu_img_snapshot == null) {
s_logger.info("Cannot find snapshot " + cmd.getTarget().getSnapshotName() + " in file " + rootDisk.getPath() + ", return true");
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
}
int result = Script.runSimpleBashScriptForExitValue("qemu-img snapshot -d " + cmd.getTarget().getSnapshotName() + " " + rootDisk.getPath());
if (result != 0) {
return new DeleteVMSnapshotAnswer(cmd, false,
"Delete VM Snapshot Failed due to can not remove snapshot from image file " + rootDisk.getPath() + " : " + result);
} else {
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
}
}
} else if (snapshot == null) {
s_logger.debug("Can not find vm snapshot " + cmd.getTarget().getSnapshotName() + " on vm: " + vmName + ", return true");
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
}
s_logger.warn(msg, e);
return new DeleteVMSnapshotAnswer(cmd, false, msg);
} finally {
if (dm != null) {
try {
dm.free();
} catch (LibvirtException l) {
s_logger.trace("Ignoring libvirt error.", l);
};
}
}
}
}

View File

@ -44,6 +44,7 @@ import com.cloud.hypervisor.kvm.resource.MigrateKVMAsync;
import com.cloud.hypervisor.kvm.resource.VifDriver;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.Ternary;
@ResourceWrapper(handles = MigrateCommand.class)
public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, LibvirtComputingResource> {
@ -67,6 +68,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
Domain destDomain = null;
Connect conn = null;
String xmlDesc = null;
List<Ternary<String, Boolean, String>> vmsnapshots = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
@ -99,6 +101,9 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
xmlDesc = dm.getXMLDesc(xmlFlag);
xmlDesc = replaceIpForVNCInDescFile(xmlDesc, target);
// delete the metadata of vm snapshots before migration
vmsnapshots = libvirtComputingResource.cleanVMSnapshotMetadata(dm);
dconn = libvirtUtilitiesHelper.retrieveQemuConnection("qemu+tcp://" + command.getDestinationIp() + "/system");
//run migration in thread so we can monitor it
@ -149,6 +154,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
libvirtComputingResource.cleanupDisk(disk);
}
}
} catch (final LibvirtException e) {
s_logger.debug("Can't migrate domain: " + e.getMessage());
result = e.getMessage();
@ -163,6 +169,12 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
result = e.getMessage();
} finally {
try {
if (dm != null && result != null) {
// restore vm snapshots in case of failed migration
if (vmsnapshots != null) {
libvirtComputingResource.restoreVMSnapshotMetadata(dm, vmName, vmsnapshots);
}
}
if (dm != null) {
if (dm.isPersistent() == 1) {
dm.undefine();

View File

@ -0,0 +1,96 @@
//
// 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 com.cloud.hypervisor.kvm.resource.wrapper;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.LibvirtException;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.RestoreVMSnapshotAnswer;
import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.vm.VirtualMachine;
@ResourceWrapper(handles = RestoreVMSnapshotCommand.class)
public final class LibvirtRestoreVMSnapshotCommandWrapper extends CommandWrapper<RestoreVMSnapshotCommand, Answer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtRestoreVMSnapshotCommandWrapper.class);
@Override
public Answer execute(final RestoreVMSnapshotCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
String vmName = cmd.getVmName();
List<VolumeObjectTO> listVolumeTo = cmd.getVolumeTOs();
VirtualMachine.PowerState vmState = VirtualMachine.PowerState.PowerOn;
Domain dm = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
Connect conn = libvirtUtilitiesHelper.getConnection();
dm = libvirtComputingResource.getDomain(conn, vmName);
if (dm == null) {
return new RestoreVMSnapshotAnswer(cmd, false,
"Restore VM Snapshot Failed due to can not find vm: " + vmName);
}
String xmlDesc = dm.getXMLDesc(0);
List<VMSnapshotTO> snapshots = cmd.getSnapshots();
Map<Long, VMSnapshotTO> snapshotAndParents = cmd.getSnapshotAndParents();
for (VMSnapshotTO snapshot: snapshots) {
VMSnapshotTO parent = snapshotAndParents.get(snapshot.getId());
String vmSnapshotXML = libvirtUtilitiesHelper.generateVMSnapshotXML(snapshot, parent, xmlDesc);
s_logger.debug("Restoring vm snapshot " + snapshot.getSnapshotName() + " on " + vmName + " with XML:\n " + vmSnapshotXML);
try {
int flags = 1; // VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE = 1
if (snapshot.getCurrent()) {
flags += 2; // VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT = 2
}
dm.snapshotCreateXML(vmSnapshotXML, flags);
} catch (LibvirtException e) {
s_logger.debug("Failed to restore vm snapshot " + snapshot.getSnapshotName() + " on " + vmName);
return new RestoreVMSnapshotAnswer(cmd, false, e.toString());
}
}
return new RestoreVMSnapshotAnswer(cmd, listVolumeTo, vmState);
} catch (LibvirtException e) {
String msg = " Restore snapshot failed due to " + e.toString();
s_logger.warn(msg, e);
return new RestoreVMSnapshotAnswer(cmd, false, msg);
} finally {
if (dm != null) {
try {
dm.free();
} catch (LibvirtException l) {
s_logger.trace("Ignoring libvirt error.", l);
};
}
}
}
}

View File

@ -0,0 +1,95 @@
//
// 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 com.cloud.hypervisor.kvm.resource.wrapper;
import java.util.List;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainSnapshot;
import org.libvirt.LibvirtException;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.RevertToVMSnapshotAnswer;
import com.cloud.agent.api.RevertToVMSnapshotCommand;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.snapshot.VMSnapshot;
@ResourceWrapper(handles = RevertToVMSnapshotCommand.class)
public final class LibvirtRevertToVMSnapshotCommandWrapper extends CommandWrapper<RevertToVMSnapshotCommand, Answer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtRevertToVMSnapshotCommandWrapper.class);
@Override
public Answer execute(final RevertToVMSnapshotCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
String vmName = cmd.getVmName();
List<VolumeObjectTO> listVolumeTo = cmd.getVolumeTOs();
VMSnapshot.Type vmSnapshotType = cmd.getTarget().getType();
Boolean snapshotMemory = vmSnapshotType == VMSnapshot.Type.DiskAndMemory;
VirtualMachine.PowerState vmState = null;
Domain dm = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
Connect conn = libvirtUtilitiesHelper.getConnection();
dm = libvirtComputingResource.getDomain(conn, vmName);
if (dm == null) {
return new RevertToVMSnapshotAnswer(cmd, false,
"Revert to VM Snapshot Failed due to can not find vm: " + vmName);
}
DomainSnapshot snapshot = dm.snapshotLookupByName(cmd.getTarget().getSnapshotName());
if (snapshot == null)
return new RevertToVMSnapshotAnswer(cmd, false, "Cannot find vmSnapshot with name: " + cmd.getTarget().getSnapshotName());
dm.revertToSnapshot(snapshot);
snapshot.free();
if (!snapshotMemory) {
dm.destroy();
if (dm.isPersistent() == 1)
dm.undefine();
vmState = VirtualMachine.PowerState.PowerOff;
} else {
vmState = VirtualMachine.PowerState.PowerOn;
}
return new RevertToVMSnapshotAnswer(cmd, listVolumeTo, vmState);
} catch (LibvirtException e) {
String msg = " Revert to VM snapshot failed due to " + e.toString();
s_logger.warn(msg, e);
return new RevertToVMSnapshotAnswer(cmd, false, msg);
} finally {
if (dm != null) {
try {
dm.free();
} catch (LibvirtException l) {
s_logger.trace("Ignoring libvirt error.", l);
};
}
}
}
}

View File

@ -25,6 +25,7 @@ import javax.naming.ConfigurationException;
import org.libvirt.Connect;
import org.libvirt.LibvirtException;
import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.storage.StorageLayer;
@ -96,4 +97,16 @@ public class LibvirtUtilitiesHelper {
final Script script = new Script(scriptPath, TIMEOUT);
return script;
}
public String generateVMSnapshotXML(VMSnapshotTO snapshot, VMSnapshotTO parent, String domainXmlDesc) {
String parentName = (parent == null)? "": (" <parent><name>" + parent.getSnapshotName() + "</name></parent>\n");
String vmSnapshotXML = "<domainsnapshot>\n"
+ " <name>" + snapshot.getSnapshotName() + "</name>\n"
+ " <state>running</state>\n"
+ parentName
+ " <creationTime>" + (int) Math.rint(snapshot.getCreateTime()/1000) + "</creationTime>\n"
+ domainXmlDesc
+ "</domainsnapshot>";
return vmSnapshotXML;
}
}

View File

@ -695,8 +695,10 @@ public class KVMStorageProcessor implements StorageProcessor {
final String secondaryStoragePoolUrl = nfsImageStore.getUrl();
// NOTE: snapshot name is encoded in snapshot path
final int index = snapshot.getPath().lastIndexOf("/");
final boolean isCreatedFromVmSnapshot = (index == -1) ? true: false; // -1 means the snapshot is created from existing vm snapshot
final String snapshotName = snapshot.getPath().substring(index + 1);
String descName = snapshotName;
final String volumePath = snapshot.getVolume().getPath();
String snapshotDestPath = null;
String snapshotRelPath = null;
@ -768,20 +770,23 @@ public class KVMStorageProcessor implements StorageProcessor {
command.add("-b", snapshotDisk.getPath());
command.add("-n", snapshotName);
command.add("-p", snapshotDestPath);
command.add("-t", snapshotName);
if (isCreatedFromVmSnapshot) {
descName = UUID.randomUUID().toString();
}
command.add("-t", descName);
final String result = command.execute();
if (result != null) {
s_logger.debug("Failed to backup snaptshot: " + result);
return new CopyCmdAnswer(result);
}
final File snapFile = new File(snapshotDestPath + "/" + snapshotName);
final File snapFile = new File(snapshotDestPath + "/" + descName);
if(snapFile.exists()){
size = snapFile.length();
}
}
final SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
newSnapshot.setPath(snapshotRelPath + File.separator + snapshotName);
newSnapshot.setPath(snapshotRelPath + File.separator + descName);
newSnapshot.setPhysicalSize(size);
return new CopyCmdAnswer(newSnapshot);
} catch (final LibvirtException e) {
@ -791,6 +796,9 @@ public class KVMStorageProcessor implements StorageProcessor {
s_logger.debug("Failed to backup snapshot: ", e);
return new CopyCmdAnswer(e.toString());
} finally {
if (isCreatedFromVmSnapshot) {
s_logger.debug("Ignoring removal of vm snapshot on primary as this snapshot is created from vm snapshot");
} else {
try {
/* Delete the snapshot on primary */
DomainInfo.DomainState state = null;
@ -834,6 +842,7 @@ public class KVMStorageProcessor implements StorageProcessor {
} catch (final Exception ex) {
s_logger.debug("Failed to delete snapshots on primary", ex);
}
}
try {
if (secondaryStoragePool != null) {

View File

@ -577,6 +577,7 @@ public class ApiResponseHelper implements ResponseGenerator {
vmSnapshotResponse.setParentName(vmSnapshotParent.getDisplayName());
}
}
populateOwner(vmSnapshotResponse, vmSnapshot);
Project project = ApiDBUtils.findProjectByProjectAccountId(vmSnapshot.getAccountId());
if (project != null) {
vmSnapshotResponse.setProjectId(project.getUuid());

View File

@ -409,6 +409,7 @@ import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCm
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupEgressCmd;
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
@ -2837,6 +2838,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(RevokeSecurityGroupEgressCmd.class);
cmdList.add(RevokeSecurityGroupIngressCmd.class);
cmdList.add(CreateSnapshotCmd.class);
cmdList.add(CreateSnapshotFromVMSnapshotCmd.class);
cmdList.add(DeleteSnapshotCmd.class);
cmdList.add(CreateSnapshotPolicyCmd.class);
cmdList.add(UpdateSnapshotPolicyCmd.class);

View File

@ -2195,6 +2195,53 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return snapshotMgr.allocSnapshot(volumeId, policyId, snapshotName, locationType);
}
@Override
public Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
VMInstanceVO vm = _vmInstanceDao.findById(vmId);
if (vm == null) {
throw new InvalidParameterValueException("Creating snapshot failed due to vm:" + vmId + " doesn't exist");
}
_accountMgr.checkAccess(caller, null, true, vm);
VolumeInfo volume = volFactory.getVolume(volumeId);
if (volume == null) {
throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
}
_accountMgr.checkAccess(caller, null, true, volume);
VirtualMachine attachVM = volume.getAttachedVM();
if (attachVM == null || attachVM.getId() != vm.getId()) {
throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't attach to vm :" + vm);
}
DataCenter zone = _dcDao.findById(volume.getDataCenterId());
if (zone == null) {
throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId());
}
if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getName());
}
if (volume.getState() != Volume.State.Ready) {
throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
}
if ( volume.getTemplateId() != null ) {
VMTemplateVO template = _templateDao.findById(volume.getTemplateId());
if( template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM ) {
throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported");
}
}
StoragePool storagePool = (StoragePool)volume.getDataStore();
if (storagePool == null) {
throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it");
}
return snapshotMgr.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, snapshotName, null);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_EXTRACT, eventDescription = "extracting volume", async = true)
public String extractVolume(ExtractVolumeCmd cmd) {

View File

@ -101,6 +101,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
@ -262,6 +263,11 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
if (vm.getState() != State.Stopped && vm.getState() != State.Shutdowned) {
throw new InvalidParameterValueException("The VM the specified disk is attached to is not in the shutdown state.");
}
// If target VM has associated VM snapshots then don't allow to revert from snapshot
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(instanceId);
if (vmSnapshots.size() > 0) {
throw new InvalidParameterValueException("Unable to revert snapshot for VM, please remove VM snapshots before reverting VM from snapshot");
}
}
SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image);
@ -363,6 +369,76 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
return snapshotSrv.backupSnapshot(snapshot);
}
@Override
public Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId) {
VMInstanceVO vm = _vmDao.findById(vmId);
if (vm == null) {
throw new InvalidParameterValueException("Creating snapshot failed due to vm:" + vmId + " doesn't exist");
}
if (! HypervisorType.KVM.equals(vm.getHypervisorType())) {
throw new InvalidParameterValueException("Unsupported hypervisor type " + vm.getHypervisorType() + ". This supports KVM only");
}
VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(vmSnapshotId);
if (vmSnapshot == null) {
throw new InvalidParameterValueException("Creating snapshot failed due to vmSnapshot:" + vmSnapshotId + " doesn't exist");
}
// check vmsnapshot permissions
Account caller = CallContext.current().getCallingAccount();
_accountMgr.checkAccess(caller, null, true, vmSnapshot);
SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
if (snapshot == null) {
throw new InvalidParameterValueException("Creating snapshot failed due to snapshot:" + snapshotId + " doesn't exist");
}
VolumeInfo volume = volFactory.getVolume(volumeId);
if (volume == null) {
throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
}
if (volume.getState() != Volume.State.Ready) {
throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
}
DataStore store = volume.getDataStore();
SnapshotDataStoreVO parentSnapshotDataStoreVO = _snapshotStoreDao.findParent(store.getRole(), store.getId(), volumeId);
if (parentSnapshotDataStoreVO != null) {
//Double check the snapshot is removed or not
SnapshotVO parentSnap = _snapshotDao.findById(parentSnapshotDataStoreVO.getSnapshotId());
if (parentSnap != null && parentSnapshotDataStoreVO.getInstallPath() != null && parentSnapshotDataStoreVO.getInstallPath().equals(vmSnapshot.getName())) {
throw new InvalidParameterValueException("Creating snapshot failed due to snapshot : " + parentSnap.getUuid() + " is created from the same vm snapshot");
}
}
SnapshotInfo snapshotInfo = this.snapshotFactory.getSnapshot(snapshotId, store);
snapshotInfo = (SnapshotInfo) store.create(snapshotInfo);
SnapshotDataStoreVO snapshotOnPrimaryStore = this._snapshotStoreDao.findBySnapshot(snapshot.getId(), store.getRole());
snapshotOnPrimaryStore.setState(ObjectInDataStoreStateMachine.State.Ready);
snapshotOnPrimaryStore.setInstallPath(vmSnapshot.getName());
_snapshotStoreDao.update(snapshotOnPrimaryStore.getId(), snapshotOnPrimaryStore);
snapshot.setState(Snapshot.State.CreatedOnPrimary);
_snapshotDao.update(snapshot.getId(), snapshot);
snapshotInfo = this.snapshotFactory.getSnapshot(snapshotId, store);
Long snapshotOwnerId = vm.getAccountId();
try {
SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.BACKUP);
if (snapshotStrategy == null) {
throw new CloudRuntimeException("Unable to find snaphot strategy to handle snapshot with id '" + snapshotId + "'");
}
snapshotInfo = snapshotStrategy.backupSnapshot(snapshotInfo);
} catch(Exception e) {
s_logger.debug("Failed to backup snapshot from vm snapshot", e);
_resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.snapshot);
_resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.secondary_storage, new Long(volume.getSize()));
throw new CloudRuntimeException("Failed to backup snapshot from vm snapshot", e);
}
return snapshotInfo;
}
@Override
public SnapshotVO getParentSnapshot(VolumeInfo volume) {
long preId = _snapshotDao.getLastSnapshot(volume.getId(), DataStoreRole.Primary);

View File

@ -102,6 +102,8 @@ import com.cloud.agent.api.GetVmIpAddressCommand;
import com.cloud.agent.api.GetVmStatsAnswer;
import com.cloud.agent.api.GetVmStatsCommand;
import com.cloud.agent.api.PvlanSetupCommand;
import com.cloud.agent.api.RestoreVMSnapshotAnswer;
import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.agent.api.StartAnswer;
import com.cloud.agent.api.VmDiskStatsEntry;
import com.cloud.agent.api.VmStatsEntry;
@ -3804,11 +3806,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
finalizeCommandsOnStart(cmds, profile);
return true;
}
@Override
public boolean finalizeCommandsOnStart(Commands cmds, VirtualMachineProfile profile) {
UserVmVO vm = _vmDao.findById(profile.getId());
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vm.getId());
RestoreVMSnapshotCommand command = _vmSnapshotMgr.createRestoreCommand(vm, vmSnapshots);
if (command != null)
cmds.addCommand("restoreVMSnapshot", command);
return true;
}
@ -3893,6 +3901,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return false;
}
Answer answer = cmds.getAnswer("restoreVMSnapshot");
if (answer != null && answer instanceof RestoreVMSnapshotAnswer) {
RestoreVMSnapshotAnswer restoreVMSnapshotAnswer = (RestoreVMSnapshotAnswer) answer;
if (restoreVMSnapshotAnswer == null || !restoreVMSnapshotAnswer.getResult()) {
s_logger.warn("Unable to restore the vm snapshot from image file to the VM: " + restoreVMSnapshotAnswer.getDetails());
}
}
return true;
}

View File

@ -33,6 +33,8 @@ import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotOptions;
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotStrategy;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
@ -44,8 +46,11 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl;
import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO;
import org.apache.cloudstack.jobs.JobInfo;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.api.query.MutualExclusiveIdsManagerBase;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
@ -59,8 +64,10 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.projects.Project.ListProjectResourcesCriteria;
import com.cloud.service.dao.ServiceOfferingDetailsDao;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Volume;
import com.cloud.storage.Volume.Type;
import com.cloud.storage.VolumeVO;
@ -119,7 +126,8 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
@Inject HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
@Inject
StorageStrategyFactory storageStrategyFactory;
@Inject
VolumeDataFactory volumeDataFactory;
@Inject
EntityManager _entityMgr;
@Inject
@ -263,7 +271,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
throw new InvalidParameterValueException("Creating VM snapshot failed due to VM:" + vmId + " is a system VM or does not exist");
}
if (_snapshotDao.listByInstanceId(vmId, Snapshot.State.BackedUp).size() > 0) {
if (_snapshotDao.listByInstanceId(vmId, Snapshot.State.BackedUp).size() > 0 && ! HypervisorType.KVM.equals(userVmVo.getHypervisorType())) {
throw new InvalidParameterValueException(
"VM snapshot for this VM is not allowed. This VM has volumes attached which has snapshots, please remove all snapshots before taking VM snapshot");
}
@ -298,8 +306,8 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
throw new InvalidParameterValueException("Creating vm snapshot failed due to VM:" + vmId + " is not in the running or Stopped state");
}
if(snapshotMemory && userVmVo.getState() == VirtualMachine.State.Stopped){
throw new InvalidParameterValueException("Can not snapshot memory when VM is in stopped state");
if(snapshotMemory && userVmVo.getState() != VirtualMachine.State.Running){
throw new InvalidParameterValueException("Can not snapshot memory when VM is not in Running state");
}
// for KVM, only allow snapshot with memory when VM is in running state
@ -323,6 +331,9 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
if (activeSnapshots.size() > 0) {
throw new CloudRuntimeException("There is other active volume snapshot tasks on the instance to which the volume is attached, please try again later.");
}
if (userVmVo.getHypervisorType() == HypervisorType.KVM && volume.getFormat() != ImageFormat.QCOW2) {
throw new CloudRuntimeException("We only support create vm snapshots from vm with QCOW2 image");
}
}
// check if there are other active VM snapshot tasks
@ -367,7 +378,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_SNAPSHOT_CREATE, eventDescription = "creating VM snapshot", async = true)
public VMSnapshot creatVMSnapshot(Long vmId, Long vmSnapshotId, Boolean quiescevm) {
public VMSnapshot createVMSnapshot(Long vmId, Long vmSnapshotId, Boolean quiescevm) {
UserVmVO userVm = _userVMDao.findById(vmId);
if (userVm == null) {
throw new InvalidParameterValueException("Create vm to snapshot failed due to vm: " + vmId + " is not found");
@ -717,6 +728,40 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
}
}
@Override
public RestoreVMSnapshotCommand createRestoreCommand(UserVmVO userVm, List<VMSnapshotVO> vmSnapshotVOs) {
if (!HypervisorType.KVM.equals(userVm.getHypervisorType()))
return null;
List<VMSnapshotTO> snapshots = new ArrayList<VMSnapshotTO>();
Map<Long, VMSnapshotTO> snapshotAndParents = new HashMap<Long, VMSnapshotTO>();
for (VMSnapshotVO vmSnapshotVO: vmSnapshotVOs) {
if (vmSnapshotVO.getType() == VMSnapshot.Type.DiskAndMemory) {
VMSnapshotVO snapshot = _vmSnapshotDao.findById(vmSnapshotVO.getId());
VMSnapshotTO parent = getSnapshotWithParents(snapshot).getParent();
VMSnapshotOptions options = snapshot.getOptions();
boolean quiescevm = true;
if (options != null)
quiescevm = options.needQuiesceVM();
VMSnapshotTO vmSnapshotTO = new VMSnapshotTO(snapshot.getId(), snapshot.getName(), snapshot.getType(),
snapshot.getCreated().getTime(), snapshot.getDescription(), snapshot.getCurrent(), parent, quiescevm);
snapshots.add(vmSnapshotTO);
snapshotAndParents.put(vmSnapshotVO.getId(), parent);
}
}
if (snapshotAndParents.isEmpty())
return null;
// prepare RestoreVMSnapshotCommand
String vmInstanceName = userVm.getInstanceName();
List<VolumeObjectTO> volumeTOs = getVolumeTOList(userVm.getId());
GuestOSVO guestOS = _guestOSDao.findById(userVm.getGuestOSId());
RestoreVMSnapshotCommand restoreSnapshotCommand = new RestoreVMSnapshotCommand(vmInstanceName, null, volumeTOs, guestOS.getDisplayName());
restoreSnapshotCommand.setSnapshots(snapshots);
restoreSnapshotCommand.setSnapshotAndParents(snapshotAndParents);
return restoreSnapshotCommand;
}
@Override
public VMSnapshot getVMSnapshotById(Long id) {
VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(id);
@ -1050,4 +1095,42 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
return workJob;
}
private List<VolumeObjectTO> getVolumeTOList(Long vmId) {
List<VolumeObjectTO> volumeTOs = new ArrayList<VolumeObjectTO>();
List<VolumeVO> volumeVos = _volumeDao.findByInstance(vmId);
VolumeInfo volumeInfo = null;
for (VolumeVO volume : volumeVos) {
volumeInfo = volumeDataFactory.getVolume(volume.getId());
volumeTOs.add((VolumeObjectTO)volumeInfo.getTO());
}
return volumeTOs;
}
private VMSnapshotTO convert2VMSnapshotTO(VMSnapshotVO vo) {
return new VMSnapshotTO(vo.getId(), vo.getName(), vo.getType(), vo.getCreated().getTime(), vo.getDescription(), vo.getCurrent(), null, true);
}
private VMSnapshotTO getSnapshotWithParents(VMSnapshotVO snapshot) {
Map<Long, VMSnapshotVO> snapshotMap = new HashMap<Long, VMSnapshotVO>();
List<VMSnapshotVO> allSnapshots = _vmSnapshotDao.findByVm(snapshot.getVmId());
for (VMSnapshotVO vmSnapshotVO : allSnapshots) {
snapshotMap.put(vmSnapshotVO.getId(), vmSnapshotVO);
}
VMSnapshotTO currentTO = convert2VMSnapshotTO(snapshot);
VMSnapshotTO result = currentTO;
VMSnapshotVO current = snapshot;
while (current.getParent() != null) {
VMSnapshotVO parent = snapshotMap.get(current.getParent());
if (parent == null) {
break;
}
currentTO.setParent(convert2VMSnapshotTO(parent));
current = snapshotMap.get(current.getParent());
currentTO = currentTO.getParent();
}
return result;
}
}

View File

@ -56,6 +56,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
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.junit.After;
import org.junit.Assert;
@ -89,6 +91,8 @@ public class SnapshotManagerTest {
@Mock
VMSnapshotDao _vmSnapshotDao;
@Mock
VMSnapshotVO vmSnapshotMock;
@Mock
Account account;
@Mock
UserVmVO vmMock;
@ -118,11 +122,16 @@ public class SnapshotManagerTest {
ResourceManager _resourceMgr;
@Mock
DataStore storeMock;
@Mock
SnapshotDataStoreDao _snapshotStoreDao;
@Mock
SnapshotDataStoreVO snapshotStoreMock;
private static final long TEST_SNAPSHOT_ID = 3L;
private static final long TEST_VOLUME_ID = 4L;
private static final long TEST_VM_ID = 5L;
private static final long TEST_STORAGE_POOL_ID = 6L;
private static final long TEST_VM_SNAPSHOT_ID = 6L;
@Before
public void setup() throws ResourceAllocationException {
@ -138,6 +147,7 @@ public class SnapshotManagerTest {
_snapshotMgr._storagePoolDao = _storagePoolDao;
_snapshotMgr._resourceMgr = _resourceMgr;
_snapshotMgr._vmSnapshotDao = _vmSnapshotDao;
_snapshotMgr._snapshotStoreDao = _snapshotStoreDao;
when(_snapshotDao.findById(anyLong())).thenReturn(snapshotMock);
when(snapshotMock.getVolumeId()).thenReturn(TEST_VOLUME_ID);
@ -147,9 +157,11 @@ public class SnapshotManagerTest {
when(volumeMock.getState()).thenReturn(Volume.State.Ready);
when(volumeFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
when(volumeInfoMock.getDataStore()).thenReturn(storeMock);
when(volumeInfoMock.getState()).thenReturn(Volume.State.Ready);
when(storeMock.getId()).thenReturn(TEST_STORAGE_POOL_ID);
when(snapshotFactory.getSnapshot(anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(snapshotInfoMock);
when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.BACKUP))).thenReturn(snapshotStrategy);
when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.REVERT))).thenReturn(snapshotStrategy);
when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.DELETE))).thenReturn(snapshotStrategy);
@ -278,4 +290,46 @@ public class SnapshotManagerTest {
Snapshot snapshot = _snapshotMgr.revertSnapshot(TEST_SNAPSHOT_ID);
Assert.assertNotNull(snapshot);
}
// vm on Xenserver, expected exception
@Test(expected = InvalidParameterValueException.class)
public void testBackupSnapshotFromVmSnapshotF1() {
when(_vmDao.findById(anyLong())).thenReturn(vmMock);
when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer);
Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID);
Assert.assertNull(snapshot);
}
// vm on KVM, first time
@Test
public void testBackupSnapshotFromVmSnapshotF2() {
when(_vmDao.findById(anyLong())).thenReturn(vmMock);
when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(_vmSnapshotDao.findById(anyLong())).thenReturn(vmSnapshotMock);
when(_snapshotStoreDao.findParent(any(DataStoreRole.class), anyLong(), anyLong())).thenReturn(null);
when(snapshotFactory.getSnapshot(anyLong(), Mockito.any(DataStore.class))).thenReturn(snapshotInfoMock);
when(storeMock.create(snapshotInfoMock)).thenReturn(snapshotInfoMock);
when(_snapshotStoreDao.findBySnapshot(anyLong(), any(DataStoreRole.class))).thenReturn(snapshotStoreMock);
when(_snapshotStoreDao.update(anyLong(), any(SnapshotDataStoreVO.class))).thenReturn(true);
when(_snapshotDao.update(anyLong(), any(SnapshotVO.class))).thenReturn(true);
when(vmMock.getAccountId()).thenReturn(2L);
when(snapshotStrategy.backupSnapshot(any(SnapshotInfo.class))).thenReturn(snapshotInfoMock);;;
Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID);
Assert.assertNotNull(snapshot);
}
// vm on KVM, already backed up
@Test(expected = InvalidParameterValueException.class)
public void testBackupSnapshotFromVmSnapshotF3() {
when(_vmDao.findById(anyLong())).thenReturn(vmMock);
when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(_vmSnapshotDao.findById(anyLong())).thenReturn(vmSnapshotMock);
when(_snapshotStoreDao.findParent(any(DataStoreRole.class), anyLong(), anyLong())).thenReturn(snapshotStoreMock);
when(snapshotStoreMock.getInstallPath()).thenReturn("VM_SNAPSHOT_NAME");
when(vmSnapshotMock.getName()).thenReturn("VM_SNAPSHOT_NAME");
Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID);
Assert.assertNull(snapshot);
}
}

View File

@ -53,3 +53,12 @@ ALTER TABLE `cloud`.`image_store_details` CHANGE COLUMN `value` `value` VARCHAR(
NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user' AFTER `value`;
ALTER TABLE `snapshots` ADD COLUMN `location_type` VARCHAR(32) COMMENT 'Location of snapshot (ex. Primary)';
-- Database change for CLOUDSTACK-8746 (VM Snapshotting implementation for KVM)
UPDATE `cloud`.`hypervisor_capabilities` SET `vm_snapshot_enabled` = 1 WHERE `hypervisor_type` ='KVM' AND `hypervisor_version` = 'default';
-- [VM-SNAPSHOT] add role permissions for new API command createSnapshotFromVMSnapshot
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'createSnapshotFromVMSnapshot', 'ALLOW', 318) ON DUPLICATE KEY UPDATE rule=rule;
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'createSnapshotFromVMSnapshot', 'ALLOW', 302) ON DUPLICATE KEY UPDATE rule=rule;
INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'createSnapshotFromVMSnapshot', 'ALLOW', 260) ON DUPLICATE KEY UPDATE rule=rule;

View File

@ -42,7 +42,7 @@ class TestVmSnapshot(cloudstackTestCase):
cls._cleanup = []
cls.unsupportedHypervisor = False
cls.hypervisor = testClient.getHypervisorInfo()
if cls.hypervisor.lower() in (KVM.lower(), "hyperv", "lxc"):
if cls.hypervisor.lower() in ("hyperv", "lxc"):
cls.unsupportedHypervisor = True
return

View File

@ -2830,6 +2830,10 @@ div.detail-group.actions td {
background-position: -82px -686px;
}
#navigation ul li.vmsnapshots span.icon {
background: url(../images/sprites.png) no-repeat -34px -666px;
}
#navigation ul li.affinityGroups span.icon {
background-position: -73px -87px;
}
@ -12822,6 +12826,7 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
}
.revertSnapshot .icon,
.revertToVMSnapshot .icon,
.restoreVM .icon,
.restore .icon,
.recover .icon {
@ -12838,6 +12843,7 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
}
.revertSnapshot:hover .icon,
.revertToVMSnapshot:hover .icon,
.restoreVM:hover .icon,
.restore:hover .icon {
background-position: -168px -613px;

View File

@ -1865,6 +1865,7 @@ var dictionary = {"ICMP.code":"ICMP Code",
"message.action.stop.systemvm":"Please confirm that you want to stop this system VM.",
"message.action.take.snapshot":"Please confirm that you want to take a snapshot of this volume.",
"message.action.unmanage.cluster":"Please confirm that you want to unmanage the cluster.",
"message.action.vmsnapshot.create":"Please confirm that you want to take a snapshot of this instance. <br>Please notice that the instance will be paused during the snapshoting, and resumed after snapshotting, if it runs on KVM.",
"message.action.vmsnapshot.delete":"Please confirm that you want to delete this VM snapshot.",
"message.action.vmsnapshot.revert":"Revert VM snapshot",
"message.activate.project":"Are you sure you want to activate this project?",

View File

@ -28,6 +28,7 @@
addRow: 'false',
createForm: {
title: 'label.action.vmsnapshot.create',
desc: 'message.action.vmsnapshot.create',
fields: {
name: {
label: 'label.name',
@ -423,7 +424,7 @@
path: 'storage.volumes',
label: 'label.volumes'
}, {
path: 'vmsnapshots',
path: 'storage.vmsnapshots',
label: 'label.snapshots'
}, {
path: 'affinityGroups',
@ -2701,8 +2702,7 @@
allowedActions.push("stop");
allowedActions.push("restart");
if ((jsonObj.hypervisor != 'KVM' || g_kvmsnapshotenabled == true)
&& (jsonObj.hypervisor != 'LXC')) {
if (jsonObj.hypervisor != 'LXC') {
allowedActions.push("snapshot");
}
@ -2738,8 +2738,7 @@
allowedActions.push("destroy");
allowedActions.push("reinstall");
if ((jsonObj.hypervisor != 'KVM' || g_kvmsnapshotenabled == true)
&& (jsonObj.hypervisor != 'LXC')) {
if (jsonObj.hypervisor != 'KVM' && jsonObj.hypervisor != 'LXC') {
allowedActions.push("snapshot");
}

View File

@ -2330,6 +2330,349 @@
}
}
}
},
},
/**
* VM Snapshots
*/
vmsnapshots: {
type: 'select',
title: 'label.vmsnapshot',
listView: {
id: 'vmsnapshots',
isMaximized: true,
fields: {
displayname: {
label: 'label.name'
},
state: {
label: 'label.state',
indicator: {
'Ready': 'on',
'Error': 'off'
}
},
type: {
label: 'label.vmsnapshot.type'
},
current: {
label: 'label.vmsnapshot.current',
converter: cloudStack.converters.toBooleanText
},
parentName: {
label: 'label.vmsnapshot.parentname'
},
created: {
label: 'label.date',
converter: cloudStack.converters.toLocalDate
}
},
advSearchFields: {
name: {
label: 'label.name'
},
domainid: {
label: 'label.domain',
select: function(args) {
if (isAdmin() || isDomainAdmin()) {
$.ajax({
url: createURL('listDomains'),
data: {
listAll: true,
details: 'min'
},
success: function(json) {
var array1 = [{
id: '',
description: ''
}];
var domains = json.listdomainsresponse.domain;
if (domains != null && domains.length > 0) {
for (var i = 0; i < domains.length; i++) {
array1.push({
id: domains[i].id,
description: domains[i].path
});
}
}
array1.sort(function(a, b) {
return a.description.localeCompare(b.description);
});
args.response.success({
data: array1
});
}
});
} else {
args.response.success({
data: null
});
}
},
isHidden: function(args) {
if (isAdmin() || isDomainAdmin())
return false;
else
return true;
}
},
account: {
label: 'label.account',
isHidden: function(args) {
if (isAdmin() || isDomainAdmin())
return false;
else
return true;
}
},
tagKey: {
label: 'label.tag.key'
},
tagValue: {
label: 'label.tag.value'
}
},
dataProvider: function(args) {
var data = {};
listViewDataProvider(args, data);
if (args.context != null) {
if ("instances" in args.context) {
$.extend(data, {
virtualMachineId: args.context.instances[0].id
});
}
}
$.ajax({
url: createURL('listVMSnapshot&listAll=true'),
data: data,
dataType: "json",
async: true,
success: function(json) {
var jsonObj;
jsonObj = json.listvmsnapshotresponse.vmSnapshot;
args.response.success({
actionFilter: vmSnapshotActionfilter,
data: jsonObj
});
}
});
},
//dataProvider end
detailView: {
tabs: {
details: {
title: 'label.details',
fields: {
id: {
label: 'label.id'
},
name: {
label: 'label.name'
},
displayname: {
label: 'label.display.name'
},
type: {
label: 'label.vmsnapshot.type'
},
description: {
label: 'label.description'
},
state: {
label: 'label.state',
indicator: {
'Ready': 'on',
'Error': 'off'
}
},
current: {
label: 'label.vmsnapshot.current',
converter: cloudStack.converters.toBooleanText
},
parentName: {
label: 'label.vmsnapshot.parentname'
},
domain: {
label: 'label.domain'
},
account: {
label: 'label.account'
},
virtualmachineid: {
label: 'label.vm.id'
},
created: {
label: 'label.date',
converter: cloudStack.converters.toLocalDate
}
},
dataProvider: function(args) {
$.ajax({
url: createURL("listVMSnapshot&listAll=true&vmsnapshotid=" + args.context.vmsnapshots[0].id),
dataType: "json",
async: true,
success: function(json) {
var jsonObj;
jsonObj = json.listvmsnapshotresponse.vmSnapshot[0];
args.response.success({
actionFilter: vmSnapshotActionfilter,
data: jsonObj
});
}
});
},
tags: cloudStack.api.tags({
resourceType: 'VMSnapshot',
contextId: 'vmsnapshots'
})
}
},
actions: {
//delete a snapshot
remove: {
label: 'label.action.vmsnapshot.delete',
messages: {
confirm: function(args) {
return 'message.action.vmsnapshot.delete';
},
notification: function(args) {
return 'label.action.vmsnapshot.delete';
}
},
action: function(args) {
$.ajax({
url: createURL("deleteVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id),
dataType: "json",
async: true,
success: function(json) {
var jid = json.deletevmsnapshotresponse.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
},
revertToVMSnapshot: {
label: 'label.action.vmsnapshot.revert',
messages: {
confirm: function(args) {
return 'label.action.vmsnapshot.revert';
},
notification: function(args) {
return 'message.action.vmsnapshot.revert';
}
},
action: function(args) {
$.ajax({
url: createURL("revertToVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id),
dataType: "json",
async: true,
success: function(json) {
var jid = json.reverttovmsnapshotresponse.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
},
takeSnapshot: {
label: 'Create Snapshot From VM Snapshot',
messages: {
confirm: function(args) {
return 'Please confirm that you want to create a volume snapshot from the vm snapshot.';
},
notification: function(args) {
return 'Volume snapshot is created from vm snapshot';
}
},
createForm: {
title: 'label.action.take.snapshot',
desc: 'message.action.take.snapshot',
fields: {
name: {
label: 'label.name',
},
volume: {
label: 'label.volume',
validation: {
required: true
},
select: function(args) {
$.ajax({
url: createURL("listVolumes&virtualMachineId=" + args.context.vmsnapshots[0].virtualmachineid),
dataType: "json",
async: true,
success: function(json) {
var volumes = json.listvolumesresponse.volume;
var items = [];
$(volumes).each(function() {
items.push({
id: this.id,
description: this.name
});
});
args.response.success({
data: items
});
}
});
}
}
}
},
action: function(args) {
var data = {
volumeid: args.data.volume,
vmsnapshotid: args.context.vmsnapshots[0].id
};
if (args.data.name != null && args.data.name.length > 0) {
$.extend(data, {
name: args.data.name
});
}
$.ajax({
url: createURL("createSnapshotFromVMSnapshot"),
data: data,
dataType: "json",
async: true,
success: function(json) {
var jid = json.createsnapshotfromvmsnapshotresponse.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
}
}
}
//detailview end
}
}
}
@ -2422,6 +2765,23 @@
}
allowedActions.push("remove");
return allowedActions;
};
var vmSnapshotActionfilter = cloudStack.actionFilter.vmSnapshotActionfilter = function(args) {
var jsonObj = args.context.item;
if (jsonObj.state == 'Error') {
return ["remove"];
}
var allowedActions = [];
if (jsonObj.state == "Ready") {
allowedActions.push("remove");
allowedActions.push("revertToVMSnapshot");
allowedActions.push("takeSnapshot");
}
return allowedActions;
}

View File

@ -1,198 +0,0 @@
// 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.
(function($, cloudStack) {
cloudStack.sections.vmsnapshots = {
title: 'label.vmsnapshot',
id: 'vmsnapshots',
listView: {
id: 'vmsnapshots',
isMaximized: true,
fields: {
displayname: {
label: 'label.name'
},
state: {
label: 'label.state',
indicator: {
'Ready': 'on',
'Error': 'off'
}
},
type: {
label: 'label.vmsnapshot.type'
},
current: {
label: 'label.vmsnapshot.current',
converter: cloudStack.converters.toBooleanText
},
parentName: {
label: 'label.vmsnapshot.parentname'
},
created: {
label: 'label.date',
converter: cloudStack.converters.toLocalDate
}
},
dataProvider: function(args) {
var apiCmd = "listVMSnapshot&listAll=true";
if (args.context != null) {
if ("instances" in args.context) {
apiCmd += "&virtualmachineid=" + args.context.instances[0].id;
}
}
$.ajax({
url: createURL(apiCmd),
dataType: "json",
async: true,
success: function(json) {
var jsonObj;
jsonObj = json.listvmsnapshotresponse.vmSnapshot;
args.response.success({
data: jsonObj
});
}
});
},
//dataProvider end
detailView: {
tabs: {
details: {
title: 'label.details',
fields: {
id: {
label: 'label.id'
},
name: {
label: 'label.name'
},
displayname: {
label: 'label.display.name'
},
type: {
label: 'label.vmsnapshot.type'
},
description: {
label: 'label.description'
},
state: {
label: 'label.state',
indicator: {
'Ready': 'on',
'Error': 'off'
}
},
current: {
label: 'label.vmsnapshot.current',
converter: cloudStack.converters.toBooleanText
},
parentName: {
label: 'label.vmsnapshot.parentname'
},
created: {
label: 'label.date',
converter: cloudStack.converters.toLocalDate
}
},
dataProvider: function(args) {
$.ajax({
url: createURL("listVMSnapshot&listAll=true&vmsnapshotid=" + args.context.vmsnapshots[0].id),
dataType: "json",
async: true,
success: function(json) {
var jsonObj;
jsonObj = json.listvmsnapshotresponse.vmSnapshot[0];
args.response.success({
//actionFilter: vmActionfilter,
data: jsonObj
});
}
});
},
tags: cloudStack.api.tags({
resourceType: 'VMSnapshot',
contextId: 'vmsnapshots'
})
}
},
actions: {
//delete a snapshot
remove: {
label: 'label.action.vmsnapshot.delete',
messages: {
confirm: function(args) {
return 'message.action.vmsnapshot.delete';
},
notification: function(args) {
return 'label.action.vmsnapshot.delete';
}
},
action: function(args) {
$.ajax({
url: createURL("deleteVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id),
dataType: "json",
async: true,
success: function(json) {
var jid = json.deletevmsnapshotresponse.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
},
restart: {
label: 'label.action.vmsnapshot.revert',
messages: {
confirm: function(args) {
return 'label.action.vmsnapshot.revert';
},
notification: function(args) {
return 'message.action.vmsnapshot.revert';
}
},
action: function(args) {
$.ajax({
url: createURL("revertToVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id),
dataType: "json",
async: true,
success: function(json) {
var jid = json.reverttovmsnapshotresponse.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
}
}
}
//detailview end
}
}
})(jQuery, cloudStack);