Merge pull request #977 from ustcweizhou/vm-snapshot

[4.10] CLOUDSTACK-8746: VM Snapshotting implementation for KVM

* pr/977:
  Fixes for testing VM Snapshots on KVM. Related to PR 977
  CLOUDSTACK-8746: vm snapshot implementation for KVM

Signed-off-by: Rajani Karuturi <rajani.karuturi@accelerite.com>
This commit is contained in:
Rajani Karuturi 2017-01-31 05:58:56 +05:30
commit 7233ac37cd
34 changed files with 1620 additions and 267 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) {
@ -3485,4 +3513,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,48 +796,52 @@ public class KVMStorageProcessor implements StorageProcessor {
s_logger.debug("Failed to backup snapshot: ", e);
return new CopyCmdAnswer(e.toString());
} finally {
try {
/* Delete the snapshot on primary */
DomainInfo.DomainState state = null;
Domain vm = null;
if (vmName != null) {
try {
vm = resource.getDomain(conn, vmName);
state = vm.getInfo().state;
} catch (final LibvirtException e) {
s_logger.trace("Ignoring libvirt error.", e);
}
}
final KVMStoragePool primaryStorage = storagePoolMgr.getStoragePool(primaryStore.getPoolType(),
primaryStore.getUuid());
if (state == DomainInfo.DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) {
final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName);
snap.delete(0);
/*
* libvirt on RHEL6 doesn't handle resume event emitted from
* qemu
*/
vm = resource.getDomain(conn, vmName);
state = vm.getInfo().state;
if (state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) {
vm.resume();
}
} else {
if (primaryPool.getType() != StoragePoolType.RBD) {
final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger);
command.add("-d", snapshotDisk.getPath());
command.add("-n", snapshotName);
final String result = command.execute();
if (result != null) {
s_logger.debug("Failed to delete snapshot on primary: " + result);
// return new CopyCmdAnswer("Failed to backup snapshot: " + result);
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;
Domain vm = null;
if (vmName != null) {
try {
vm = resource.getDomain(conn, vmName);
state = vm.getInfo().state;
} catch (final LibvirtException e) {
s_logger.trace("Ignoring libvirt error.", e);
}
}
final KVMStoragePool primaryStorage = storagePoolMgr.getStoragePool(primaryStore.getPoolType(),
primaryStore.getUuid());
if (state == DomainInfo.DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) {
final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName);
snap.delete(0);
/*
* libvirt on RHEL6 doesn't handle resume event emitted from
* qemu
*/
vm = resource.getDomain(conn, vmName);
state = vm.getInfo().state;
if (state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) {
vm.resume();
}
} else {
if (primaryPool.getType() != StoragePoolType.RBD) {
final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger);
command.add("-d", snapshotDisk.getPath());
command.add("-n", snapshotName);
final String result = command.execute();
if (result != null) {
s_logger.debug("Failed to delete snapshot on primary: " + result);
// return new CopyCmdAnswer("Failed to backup snapshot: " + result);
}
}
}
} catch (final Exception ex) {
s_logger.debug("Failed to delete snapshots on primary", ex);
}
} catch (final Exception ex) {
s_logger.debug("Failed to delete snapshots on primary", ex);
}
try {

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
@ -104,7 +104,6 @@ class TestVmSnapshot(cloudstackTestCase):
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.dbclient = self.testClient.getDbConnection()
self.cleanup = []
if self.unsupportedHypervisor:
self.skipTest("Skipping test because unsupported hypervisor\
@ -112,11 +111,6 @@ class TestVmSnapshot(cloudstackTestCase):
return
def tearDown(self):
try:
# Clean up, terminate the created instance, volumes and snapshots
cleanup_resources(self.apiclient, self.cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
@ -150,18 +144,24 @@ class TestVmSnapshot(cloudstackTestCase):
time.sleep(self.services["sleep"])
#KVM VM Snapshot needs to set snapshot with memory
MemorySnapshot = False
if self.hypervisor.lower() in (KVM.lower()):
MemorySnapshot = True
vm_snapshot = VmSnapshot.create(
self.apiclient,
self.virtual_machine.id,
"false",
MemorySnapshot,
"TestSnapshot",
"Dsiplay Text"
"Display Text"
)
self.assertEqual(
vm_snapshot.state,
"Ready",
"Check the snapshot of vm is ready!"
)
return
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
@ -213,13 +213,21 @@ class TestVmSnapshot(cloudstackTestCase):
"Check the snapshot of vm is ready!"
)
self.virtual_machine.stop(self.apiclient)
#We don't need to stop the VM when taking a VM Snapshot on KVM
if self.hypervisor.lower() in (KVM.lower()):
pass
else:
self.virtual_machine.stop(self.apiclient)
VmSnapshot.revertToSnapshot(
self.apiclient,
list_snapshot_response[0].id)
self.virtual_machine.start(self.apiclient)
#We don't need to start the VM when taking a VM Snapshot on KVM
if self.hypervisor.lower() in (KVM.lower()):
pass
else:
self.virtual_machine.start(self.apiclient)
try:
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)

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?",
@ -2281,4 +2282,4 @@ var dictionary = {"ICMP.code":"ICMP Code",
"state.detached":"Detached",
"title.upload.volume":"Upload Volume",
"ui.listView.filters.all":"All",
"ui.listView.filters.mine":"Mine"};
"ui.listView.filters.mine":"Mine"};

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,7 +2330,350 @@
}
}
}
}
},
},
/**
* 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);