mirror of https://github.com/apache/cloudstack.git
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:
commit
7233ac37cd
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
|
|||
}
|
||||
|
||||
public static String getResultObjectName() {
|
||||
return "snapshot";
|
||||
return ApiConstants.SNAPSHOT;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
Loading…
Reference in New Issue