mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
CLOUDSTACK-8746: vm snapshot implementation for KVM
(1) add support to create/delete/revert vm snapshots on running vms with QCOW2 format (2) add new API to create volume snapshot from vm snapshot (3) delete metadata of vm snapshots before stopping/migrating and recover vm snapshots after starting/migrating (4) enable deleting of VM snapshot on stopped vm or vm snapshot is not listed in qcow2 image. (5) enable smoke tests for vmsnaphsots on KVM
This commit is contained in:
parent
9513053f42
commit
a2428508e2
@ -102,4 +102,6 @@ public interface VolumeApiService {
|
|||||||
boolean isDisplayResourceEnabled(Long id);
|
boolean isDisplayResourceEnabled(Long id);
|
||||||
|
|
||||||
void updateDisplay(Volume volume, Boolean displayVolume);
|
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 revertSnapshot(Long snapshotId);
|
||||||
|
|
||||||
|
Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId);
|
||||||
|
|
||||||
SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd);
|
SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public interface VMSnapshotService {
|
|||||||
|
|
||||||
VMSnapshot getVMSnapshotById(Long id);
|
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;
|
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 = "signature";
|
||||||
public static final String SIGNATURE_VERSION = "signatureversion";
|
public static final String SIGNATURE_VERSION = "signatureversion";
|
||||||
public static final String SIZE = "size";
|
public static final String SIZE = "size";
|
||||||
|
public static final String SNAPSHOT = "snapshot";
|
||||||
public static final String SNAPSHOT_ID = "snapshotid";
|
public static final String SNAPSHOT_ID = "snapshotid";
|
||||||
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
|
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
|
||||||
public static final String SNAPSHOT_TYPE = "snapshottype";
|
public static final String SNAPSHOT_TYPE = "snapshottype";
|
||||||
|
|||||||
@ -143,7 +143,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getResultObjectName() {
|
public static String getResultObjectName() {
|
||||||
return "snapshot";
|
return ApiConstants.SNAPSHOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
public void execute() {
|
public void execute() {
|
||||||
CallContext.current().setEventDetails("VM Id: " + getVmId());
|
CallContext.current().setEventDetails("VM Id: " + getVmId());
|
||||||
VMSnapshot result = _vmSnapshotService.creatVMSnapshot(getVmId(), getEntityId(), getQuiescevm());
|
VMSnapshot result = _vmSnapshotService.createVMSnapshot(getVmId(), getEntityId(), getQuiescevm());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
VMSnapshotResponse response = _responseGenerator.createVMSnapshotResponse(result);
|
VMSnapshotResponse response = _responseGenerator.createVMSnapshotResponse(result);
|
||||||
response.setResponseName(getCommandName());
|
response.setResponseName(getCommandName());
|
||||||
|
|||||||
63
core/src/com/cloud/agent/api/RestoreVMSnapshotAnswer.java
Normal file
63
core/src/com/cloud/agent/api/RestoreVMSnapshotAnswer.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
package com.cloud.agent.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||||
|
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
|
||||||
|
public class RestoreVMSnapshotAnswer extends Answer {
|
||||||
|
|
||||||
|
private List<VolumeObjectTO> volumeTOs;
|
||||||
|
private VirtualMachine.PowerState vmState;
|
||||||
|
|
||||||
|
public RestoreVMSnapshotAnswer(RestoreVMSnapshotCommand cmd, boolean result, String message) {
|
||||||
|
super(cmd, result, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestoreVMSnapshotAnswer() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestoreVMSnapshotAnswer(RestoreVMSnapshotCommand cmd, List<VolumeObjectTO> volumeTOs, VirtualMachine.PowerState vmState) {
|
||||||
|
super(cmd, true, "");
|
||||||
|
this.volumeTOs = volumeTOs;
|
||||||
|
this.vmState = vmState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualMachine.PowerState getVmState() {
|
||||||
|
return vmState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<VolumeObjectTO> getVolumeTOs() {
|
||||||
|
return volumeTOs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVolumeTOs(List<VolumeObjectTO> volumeTOs) {
|
||||||
|
this.volumeTOs = volumeTOs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmState(VirtualMachine.PowerState vmState) {
|
||||||
|
this.vmState = vmState;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
52
core/src/com/cloud/agent/api/RestoreVMSnapshotCommand.java
Normal file
52
core/src/com/cloud/agent/api/RestoreVMSnapshotCommand.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
package com.cloud.agent.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||||
|
|
||||||
|
public class RestoreVMSnapshotCommand extends VMSnapshotBaseCommand {
|
||||||
|
|
||||||
|
List<VMSnapshotTO> snapshots;
|
||||||
|
Map<Long, VMSnapshotTO> snapshotAndParents;
|
||||||
|
|
||||||
|
public RestoreVMSnapshotCommand(String vmName, VMSnapshotTO snapshot, List<VolumeObjectTO> volumeTOs, String guestOSType) {
|
||||||
|
super(vmName, snapshot, volumeTOs, guestOSType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<VMSnapshotTO> getSnapshots() {
|
||||||
|
return snapshots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshots(List<VMSnapshotTO> snapshots) {
|
||||||
|
this.snapshots = snapshots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Long, VMSnapshotTO> getSnapshotAndParents() {
|
||||||
|
return snapshotAndParents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotAndParents(Map<Long, VMSnapshotTO> snapshotAndParents) {
|
||||||
|
this.snapshotAndParents = snapshotAndParents;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -17,7 +17,11 @@
|
|||||||
|
|
||||||
package com.cloud.vm.snapshot;
|
package com.cloud.vm.snapshot;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.RestoreVMSnapshotCommand;
|
||||||
import com.cloud.utils.component.Manager;
|
import com.cloud.utils.component.Manager;
|
||||||
|
import com.cloud.vm.UserVmVO;
|
||||||
import com.cloud.vm.VMInstanceVO;
|
import com.cloud.vm.VMInstanceVO;
|
||||||
|
|
||||||
public interface VMSnapshotManager extends VMSnapshotService, Manager {
|
public interface VMSnapshotManager extends VMSnapshotService, Manager {
|
||||||
@ -42,4 +46,7 @@ public interface VMSnapshotManager extends VMSnapshotService, Manager {
|
|||||||
boolean syncVMSnapshot(VMInstanceVO vm, Long hostId);
|
boolean syncVMSnapshot(VMInstanceVO vm, Long hostId);
|
||||||
|
|
||||||
boolean hasActiveVMSnapshotTasks(Long vmId);
|
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.PrepareForMigrationCommand;
|
||||||
import com.cloud.agent.api.RebootAnswer;
|
import com.cloud.agent.api.RebootAnswer;
|
||||||
import com.cloud.agent.api.RebootCommand;
|
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.ScaleVmCommand;
|
||||||
import com.cloud.agent.api.StartAnswer;
|
import com.cloud.agent.api.StartAnswer;
|
||||||
import com.cloud.agent.api.StartCommand;
|
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.UserVmDetailsDao;
|
||||||
import com.cloud.vm.dao.VMInstanceDao;
|
import com.cloud.vm.dao.VMInstanceDao;
|
||||||
import com.cloud.vm.snapshot.VMSnapshotManager;
|
import com.cloud.vm.snapshot.VMSnapshotManager;
|
||||||
|
import com.cloud.vm.snapshot.VMSnapshotVO;
|
||||||
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
||||||
|
|
||||||
public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable {
|
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;
|
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,
|
private void orchestrateReboot(final String vmUuid, final Map<VirtualMachineProfile.Param, Object> params) throws InsufficientCapacityException, ConcurrentOperationException,
|
||||||
ResourceUnavailableException {
|
ResourceUnavailableException {
|
||||||
final VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
|
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 DataCenter dc = _entityMgr.findById(DataCenter.class, vm.getDataCenterId());
|
||||||
final Host host = _hostDao.findById(vm.getHostId());
|
final Host host = _hostDao.findById(vm.getHostId());
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
|
|||||||
@ -218,6 +218,12 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
|
|||||||
@Override
|
@Override
|
||||||
public boolean deleteSnapshot(Long snapshotId) {
|
public boolean deleteSnapshot(Long snapshotId) {
|
||||||
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
|
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
|
||||||
|
|
||||||
|
if (snapshotVO.getState() == Snapshot.State.Allocated) {
|
||||||
|
snapshotDao.remove(snapshotId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
|
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@ -44,6 +45,9 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
import javax.ejb.Local;
|
import javax.ejb.Local;
|
||||||
import javax.naming.ConfigurationException;
|
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 com.google.common.base.Strings;
|
||||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||||
@ -62,7 +66,14 @@ import org.libvirt.Domain;
|
|||||||
import org.libvirt.DomainBlockStats;
|
import org.libvirt.DomainBlockStats;
|
||||||
import org.libvirt.DomainInfo;
|
import org.libvirt.DomainInfo;
|
||||||
import org.libvirt.DomainInfo.DomainState;
|
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.DomainInterfaceStats;
|
||||||
|
import org.libvirt.DomainSnapshot;
|
||||||
import org.libvirt.LibvirtException;
|
import org.libvirt.LibvirtException;
|
||||||
import org.libvirt.MemoryStatistic;
|
import org.libvirt.MemoryStatistic;
|
||||||
import org.libvirt.NodeInfo;
|
import org.libvirt.NodeInfo;
|
||||||
@ -142,6 +153,7 @@ import com.cloud.utils.NumbersUtil;
|
|||||||
import com.cloud.utils.StringUtils;
|
import com.cloud.utils.StringUtils;
|
||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import com.cloud.utils.PropertiesUtil;
|
import com.cloud.utils.PropertiesUtil;
|
||||||
|
import com.cloud.utils.Ternary;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
import com.cloud.utils.net.NetUtils;
|
import com.cloud.utils.net.NetUtils;
|
||||||
import com.cloud.utils.script.OutputInterpreter;
|
import com.cloud.utils.script.OutputInterpreter;
|
||||||
@ -2776,6 +2788,22 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
DomainState state = null;
|
DomainState state = null;
|
||||||
Domain dm = 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");
|
s_logger.debug("Try to stop the vm at first");
|
||||||
String ret = stopVM(conn, vmName, false);
|
String ret = stopVM(conn, vmName, false);
|
||||||
if (ret == Script.ERR_TIMEOUT) {
|
if (ret == Script.ERR_TIMEOUT) {
|
||||||
@ -3481,4 +3509,77 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
}
|
}
|
||||||
return device;
|
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.hypervisor.kvm.resource.VifDriver;
|
||||||
import com.cloud.resource.CommandWrapper;
|
import com.cloud.resource.CommandWrapper;
|
||||||
import com.cloud.resource.ResourceWrapper;
|
import com.cloud.resource.ResourceWrapper;
|
||||||
|
import com.cloud.utils.Ternary;
|
||||||
|
|
||||||
@ResourceWrapper(handles = MigrateCommand.class)
|
@ResourceWrapper(handles = MigrateCommand.class)
|
||||||
public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, LibvirtComputingResource> {
|
public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, LibvirtComputingResource> {
|
||||||
@ -67,6 +68,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
Domain destDomain = null;
|
Domain destDomain = null;
|
||||||
Connect conn = null;
|
Connect conn = null;
|
||||||
String xmlDesc = null;
|
String xmlDesc = null;
|
||||||
|
List<Ternary<String, Boolean, String>> vmsnapshots = null;
|
||||||
try {
|
try {
|
||||||
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
||||||
|
|
||||||
@ -99,6 +101,9 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
xmlDesc = dm.getXMLDesc(xmlFlag);
|
xmlDesc = dm.getXMLDesc(xmlFlag);
|
||||||
xmlDesc = replaceIpForVNCInDescFile(xmlDesc, target);
|
xmlDesc = replaceIpForVNCInDescFile(xmlDesc, target);
|
||||||
|
|
||||||
|
// delete the metadata of vm snapshots before migration
|
||||||
|
vmsnapshots = libvirtComputingResource.cleanVMSnapshotMetadata(dm);
|
||||||
|
|
||||||
dconn = libvirtUtilitiesHelper.retrieveQemuConnection("qemu+tcp://" + command.getDestinationIp() + "/system");
|
dconn = libvirtUtilitiesHelper.retrieveQemuConnection("qemu+tcp://" + command.getDestinationIp() + "/system");
|
||||||
|
|
||||||
//run migration in thread so we can monitor it
|
//run migration in thread so we can monitor it
|
||||||
@ -149,6 +154,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
libvirtComputingResource.cleanupDisk(disk);
|
libvirtComputingResource.cleanupDisk(disk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (final LibvirtException e) {
|
} catch (final LibvirtException e) {
|
||||||
s_logger.debug("Can't migrate domain: " + e.getMessage());
|
s_logger.debug("Can't migrate domain: " + e.getMessage());
|
||||||
result = e.getMessage();
|
result = e.getMessage();
|
||||||
@ -163,6 +169,12 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
result = e.getMessage();
|
result = e.getMessage();
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
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 != null) {
|
||||||
if (dm.isPersistent() == 1) {
|
if (dm.isPersistent() == 1) {
|
||||||
dm.undefine();
|
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.Connect;
|
||||||
import org.libvirt.LibvirtException;
|
import org.libvirt.LibvirtException;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.VMSnapshotTO;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
|
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
|
||||||
import com.cloud.storage.StorageLayer;
|
import com.cloud.storage.StorageLayer;
|
||||||
@ -96,4 +97,16 @@ public class LibvirtUtilitiesHelper {
|
|||||||
final Script script = new Script(scriptPath, TIMEOUT);
|
final Script script = new Script(scriptPath, TIMEOUT);
|
||||||
return script;
|
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();
|
final String secondaryStoragePoolUrl = nfsImageStore.getUrl();
|
||||||
// NOTE: snapshot name is encoded in snapshot path
|
// NOTE: snapshot name is encoded in snapshot path
|
||||||
final int index = snapshot.getPath().lastIndexOf("/");
|
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);
|
final String snapshotName = snapshot.getPath().substring(index + 1);
|
||||||
|
String descName = snapshotName;
|
||||||
final String volumePath = snapshot.getVolume().getPath();
|
final String volumePath = snapshot.getVolume().getPath();
|
||||||
String snapshotDestPath = null;
|
String snapshotDestPath = null;
|
||||||
String snapshotRelPath = null;
|
String snapshotRelPath = null;
|
||||||
@ -768,20 +770,23 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
command.add("-b", snapshotDisk.getPath());
|
command.add("-b", snapshotDisk.getPath());
|
||||||
command.add("-n", snapshotName);
|
command.add("-n", snapshotName);
|
||||||
command.add("-p", snapshotDestPath);
|
command.add("-p", snapshotDestPath);
|
||||||
command.add("-t", snapshotName);
|
if (isCreatedFromVmSnapshot) {
|
||||||
|
descName = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
command.add("-t", descName);
|
||||||
final String result = command.execute();
|
final String result = command.execute();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
s_logger.debug("Failed to backup snaptshot: " + result);
|
s_logger.debug("Failed to backup snaptshot: " + result);
|
||||||
return new CopyCmdAnswer(result);
|
return new CopyCmdAnswer(result);
|
||||||
}
|
}
|
||||||
final File snapFile = new File(snapshotDestPath + "/" + snapshotName);
|
final File snapFile = new File(snapshotDestPath + "/" + descName);
|
||||||
if(snapFile.exists()){
|
if(snapFile.exists()){
|
||||||
size = snapFile.length();
|
size = snapFile.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
|
final SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
|
||||||
newSnapshot.setPath(snapshotRelPath + File.separator + snapshotName);
|
newSnapshot.setPath(snapshotRelPath + File.separator + descName);
|
||||||
newSnapshot.setPhysicalSize(size);
|
newSnapshot.setPhysicalSize(size);
|
||||||
return new CopyCmdAnswer(newSnapshot);
|
return new CopyCmdAnswer(newSnapshot);
|
||||||
} catch (final LibvirtException e) {
|
} catch (final LibvirtException e) {
|
||||||
@ -791,6 +796,9 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
s_logger.debug("Failed to backup snapshot: ", e);
|
s_logger.debug("Failed to backup snapshot: ", e);
|
||||||
return new CopyCmdAnswer(e.toString());
|
return new CopyCmdAnswer(e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
|
if (isCreatedFromVmSnapshot) {
|
||||||
|
s_logger.debug("Ignoring removal of vm snapshot on primary as this snapshot is created from vm snapshot");
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
/* Delete the snapshot on primary */
|
/* Delete the snapshot on primary */
|
||||||
DomainInfo.DomainState state = null;
|
DomainInfo.DomainState state = null;
|
||||||
@ -834,6 +842,7 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
} catch (final Exception ex) {
|
} catch (final Exception ex) {
|
||||||
s_logger.debug("Failed to delete snapshots on primary", ex);
|
s_logger.debug("Failed to delete snapshots on primary", ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (secondaryStoragePool != null) {
|
if (secondaryStoragePool != null) {
|
||||||
|
|||||||
@ -577,6 +577,7 @@ public class ApiResponseHelper implements ResponseGenerator {
|
|||||||
vmSnapshotResponse.setParentName(vmSnapshotParent.getDisplayName());
|
vmSnapshotResponse.setParentName(vmSnapshotParent.getDisplayName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
populateOwner(vmSnapshotResponse, vmSnapshot);
|
||||||
Project project = ApiDBUtils.findProjectByProjectAccountId(vmSnapshot.getAccountId());
|
Project project = ApiDBUtils.findProjectByProjectAccountId(vmSnapshot.getAccountId());
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
vmSnapshotResponse.setProjectId(project.getUuid());
|
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.RevokeSecurityGroupEgressCmd;
|
||||||
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd;
|
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.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.CreateSnapshotPolicyCmd;
|
||||||
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd;
|
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd;
|
||||||
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
|
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(RevokeSecurityGroupEgressCmd.class);
|
||||||
cmdList.add(RevokeSecurityGroupIngressCmd.class);
|
cmdList.add(RevokeSecurityGroupIngressCmd.class);
|
||||||
cmdList.add(CreateSnapshotCmd.class);
|
cmdList.add(CreateSnapshotCmd.class);
|
||||||
|
cmdList.add(CreateSnapshotFromVMSnapshotCmd.class);
|
||||||
cmdList.add(DeleteSnapshotCmd.class);
|
cmdList.add(DeleteSnapshotCmd.class);
|
||||||
cmdList.add(CreateSnapshotPolicyCmd.class);
|
cmdList.add(CreateSnapshotPolicyCmd.class);
|
||||||
cmdList.add(UpdateSnapshotPolicyCmd.class);
|
cmdList.add(UpdateSnapshotPolicyCmd.class);
|
||||||
|
|||||||
@ -2195,6 +2195,53 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||||||
return snapshotMgr.allocSnapshot(volumeId, policyId, snapshotName, locationType);
|
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
|
@Override
|
||||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_EXTRACT, eventDescription = "extracting volume", async = true)
|
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_EXTRACT, eventDescription = "extracting volume", async = true)
|
||||||
public String extractVolume(ExtractVolumeCmd cmd) {
|
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.DataStoreManager;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
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.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.SnapshotDataFactory;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
|
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
|
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) {
|
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.");
|
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);
|
SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image);
|
||||||
@ -363,6 +369,76 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
|
|||||||
return snapshotSrv.backupSnapshot(snapshot);
|
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
|
@Override
|
||||||
public SnapshotVO getParentSnapshot(VolumeInfo volume) {
|
public SnapshotVO getParentSnapshot(VolumeInfo volume) {
|
||||||
long preId = _snapshotDao.getLastSnapshot(volume.getId(), DataStoreRole.Primary);
|
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.GetVmStatsAnswer;
|
||||||
import com.cloud.agent.api.GetVmStatsCommand;
|
import com.cloud.agent.api.GetVmStatsCommand;
|
||||||
import com.cloud.agent.api.PvlanSetupCommand;
|
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.StartAnswer;
|
||||||
import com.cloud.agent.api.VmDiskStatsEntry;
|
import com.cloud.agent.api.VmDiskStatsEntry;
|
||||||
import com.cloud.agent.api.VmStatsEntry;
|
import com.cloud.agent.api.VmStatsEntry;
|
||||||
@ -3804,11 +3806,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalizeCommandsOnStart(cmds, profile);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean finalizeCommandsOnStart(Commands cmds, VirtualMachineProfile profile) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3893,6 +3901,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
return false;
|
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;
|
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.StorageStrategyFactory;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotOptions;
|
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.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.ConfigKey;
|
||||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
import org.apache.cloudstack.framework.jobs.AsyncJob;
|
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.OutcomeImpl;
|
||||||
import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO;
|
import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO;
|
||||||
import org.apache.cloudstack.jobs.JobInfo;
|
import org.apache.cloudstack.jobs.JobInfo;
|
||||||
|
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
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.api.query.MutualExclusiveIdsManagerBase;
|
||||||
import com.cloud.event.ActionEvent;
|
import com.cloud.event.ActionEvent;
|
||||||
import com.cloud.event.EventTypes;
|
import com.cloud.event.EventTypes;
|
||||||
@ -59,8 +64,10 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
|||||||
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
|
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
|
||||||
import com.cloud.projects.Project.ListProjectResourcesCriteria;
|
import com.cloud.projects.Project.ListProjectResourcesCriteria;
|
||||||
import com.cloud.service.dao.ServiceOfferingDetailsDao;
|
import com.cloud.service.dao.ServiceOfferingDetailsDao;
|
||||||
|
import com.cloud.storage.GuestOSVO;
|
||||||
import com.cloud.storage.Snapshot;
|
import com.cloud.storage.Snapshot;
|
||||||
import com.cloud.storage.SnapshotVO;
|
import com.cloud.storage.SnapshotVO;
|
||||||
|
import com.cloud.storage.Storage.ImageFormat;
|
||||||
import com.cloud.storage.Volume;
|
import com.cloud.storage.Volume;
|
||||||
import com.cloud.storage.Volume.Type;
|
import com.cloud.storage.Volume.Type;
|
||||||
import com.cloud.storage.VolumeVO;
|
import com.cloud.storage.VolumeVO;
|
||||||
@ -119,7 +126,8 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
|
|||||||
@Inject HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
|
@Inject HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
|
||||||
@Inject
|
@Inject
|
||||||
StorageStrategyFactory storageStrategyFactory;
|
StorageStrategyFactory storageStrategyFactory;
|
||||||
|
@Inject
|
||||||
|
VolumeDataFactory volumeDataFactory;
|
||||||
@Inject
|
@Inject
|
||||||
EntityManager _entityMgr;
|
EntityManager _entityMgr;
|
||||||
@Inject
|
@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");
|
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(
|
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");
|
"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");
|
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){
|
if(snapshotMemory && userVmVo.getState() != VirtualMachine.State.Running){
|
||||||
throw new InvalidParameterValueException("Can not snapshot memory when VM is in stopped state");
|
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
|
// 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) {
|
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.");
|
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
|
// check if there are other active VM snapshot tasks
|
||||||
@ -367,7 +378,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ActionEvent(eventType = EventTypes.EVENT_VM_SNAPSHOT_CREATE, eventDescription = "creating VM snapshot", async = true)
|
@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);
|
UserVmVO userVm = _userVMDao.findById(vmId);
|
||||||
if (userVm == null) {
|
if (userVm == null) {
|
||||||
throw new InvalidParameterValueException("Create vm to snapshot failed due to vm: " + vmId + " is not found");
|
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
|
@Override
|
||||||
public VMSnapshot getVMSnapshotById(Long id) {
|
public VMSnapshot getVMSnapshotById(Long id) {
|
||||||
VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(id);
|
VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(id);
|
||||||
@ -1050,4 +1095,42 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
|
|||||||
|
|
||||||
return workJob;
|
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.VolumeDataFactory;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
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.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
@ -89,6 +91,8 @@ public class SnapshotManagerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
VMSnapshotDao _vmSnapshotDao;
|
VMSnapshotDao _vmSnapshotDao;
|
||||||
@Mock
|
@Mock
|
||||||
|
VMSnapshotVO vmSnapshotMock;
|
||||||
|
@Mock
|
||||||
Account account;
|
Account account;
|
||||||
@Mock
|
@Mock
|
||||||
UserVmVO vmMock;
|
UserVmVO vmMock;
|
||||||
@ -118,11 +122,16 @@ public class SnapshotManagerTest {
|
|||||||
ResourceManager _resourceMgr;
|
ResourceManager _resourceMgr;
|
||||||
@Mock
|
@Mock
|
||||||
DataStore storeMock;
|
DataStore storeMock;
|
||||||
|
@Mock
|
||||||
|
SnapshotDataStoreDao _snapshotStoreDao;
|
||||||
|
@Mock
|
||||||
|
SnapshotDataStoreVO snapshotStoreMock;
|
||||||
|
|
||||||
private static final long TEST_SNAPSHOT_ID = 3L;
|
private static final long TEST_SNAPSHOT_ID = 3L;
|
||||||
private static final long TEST_VOLUME_ID = 4L;
|
private static final long TEST_VOLUME_ID = 4L;
|
||||||
private static final long TEST_VM_ID = 5L;
|
private static final long TEST_VM_ID = 5L;
|
||||||
private static final long TEST_STORAGE_POOL_ID = 6L;
|
private static final long TEST_STORAGE_POOL_ID = 6L;
|
||||||
|
private static final long TEST_VM_SNAPSHOT_ID = 6L;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws ResourceAllocationException {
|
public void setup() throws ResourceAllocationException {
|
||||||
@ -138,6 +147,7 @@ public class SnapshotManagerTest {
|
|||||||
_snapshotMgr._storagePoolDao = _storagePoolDao;
|
_snapshotMgr._storagePoolDao = _storagePoolDao;
|
||||||
_snapshotMgr._resourceMgr = _resourceMgr;
|
_snapshotMgr._resourceMgr = _resourceMgr;
|
||||||
_snapshotMgr._vmSnapshotDao = _vmSnapshotDao;
|
_snapshotMgr._vmSnapshotDao = _vmSnapshotDao;
|
||||||
|
_snapshotMgr._snapshotStoreDao = _snapshotStoreDao;
|
||||||
|
|
||||||
when(_snapshotDao.findById(anyLong())).thenReturn(snapshotMock);
|
when(_snapshotDao.findById(anyLong())).thenReturn(snapshotMock);
|
||||||
when(snapshotMock.getVolumeId()).thenReturn(TEST_VOLUME_ID);
|
when(snapshotMock.getVolumeId()).thenReturn(TEST_VOLUME_ID);
|
||||||
@ -147,9 +157,11 @@ public class SnapshotManagerTest {
|
|||||||
when(volumeMock.getState()).thenReturn(Volume.State.Ready);
|
when(volumeMock.getState()).thenReturn(Volume.State.Ready);
|
||||||
when(volumeFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
|
when(volumeFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
|
||||||
when(volumeInfoMock.getDataStore()).thenReturn(storeMock);
|
when(volumeInfoMock.getDataStore()).thenReturn(storeMock);
|
||||||
|
when(volumeInfoMock.getState()).thenReturn(Volume.State.Ready);
|
||||||
when(storeMock.getId()).thenReturn(TEST_STORAGE_POOL_ID);
|
when(storeMock.getId()).thenReturn(TEST_STORAGE_POOL_ID);
|
||||||
|
|
||||||
when(snapshotFactory.getSnapshot(anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(snapshotInfoMock);
|
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.REVERT))).thenReturn(snapshotStrategy);
|
||||||
when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.DELETE))).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);
|
Snapshot snapshot = _snapshotMgr.revertSnapshot(TEST_SNAPSHOT_ID);
|
||||||
Assert.assertNotNull(snapshot);
|
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`;
|
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)';
|
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._cleanup = []
|
||||||
cls.unsupportedHypervisor = False
|
cls.unsupportedHypervisor = False
|
||||||
cls.hypervisor = testClient.getHypervisorInfo()
|
cls.hypervisor = testClient.getHypervisorInfo()
|
||||||
if cls.hypervisor.lower() in (KVM.lower(), "hyperv", "lxc"):
|
if cls.hypervisor.lower() in ("hyperv", "lxc"):
|
||||||
cls.unsupportedHypervisor = True
|
cls.unsupportedHypervisor = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@ -2830,6 +2830,10 @@ div.detail-group.actions td {
|
|||||||
background-position: -82px -686px;
|
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 {
|
#navigation ul li.affinityGroups span.icon {
|
||||||
background-position: -73px -87px;
|
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,
|
.revertSnapshot .icon,
|
||||||
|
.revertToVMSnapshot .icon,
|
||||||
.restoreVM .icon,
|
.restoreVM .icon,
|
||||||
.restore .icon,
|
.restore .icon,
|
||||||
.recover .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,
|
.revertSnapshot:hover .icon,
|
||||||
|
.revertToVMSnapshot:hover .icon,
|
||||||
.restoreVM:hover .icon,
|
.restoreVM:hover .icon,
|
||||||
.restore:hover .icon {
|
.restore:hover .icon {
|
||||||
background-position: -168px -613px;
|
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.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.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.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.delete":"Please confirm that you want to delete this VM snapshot.",
|
||||||
"message.action.vmsnapshot.revert":"Revert VM snapshot",
|
"message.action.vmsnapshot.revert":"Revert VM snapshot",
|
||||||
"message.activate.project":"Are you sure you want to activate this project?",
|
"message.activate.project":"Are you sure you want to activate this project?",
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
addRow: 'false',
|
addRow: 'false',
|
||||||
createForm: {
|
createForm: {
|
||||||
title: 'label.action.vmsnapshot.create',
|
title: 'label.action.vmsnapshot.create',
|
||||||
|
desc: 'message.action.vmsnapshot.create',
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
label: 'label.name',
|
label: 'label.name',
|
||||||
@ -423,7 +424,7 @@
|
|||||||
path: 'storage.volumes',
|
path: 'storage.volumes',
|
||||||
label: 'label.volumes'
|
label: 'label.volumes'
|
||||||
}, {
|
}, {
|
||||||
path: 'vmsnapshots',
|
path: 'storage.vmsnapshots',
|
||||||
label: 'label.snapshots'
|
label: 'label.snapshots'
|
||||||
}, {
|
}, {
|
||||||
path: 'affinityGroups',
|
path: 'affinityGroups',
|
||||||
@ -2701,8 +2702,7 @@
|
|||||||
allowedActions.push("stop");
|
allowedActions.push("stop");
|
||||||
allowedActions.push("restart");
|
allowedActions.push("restart");
|
||||||
|
|
||||||
if ((jsonObj.hypervisor != 'KVM' || g_kvmsnapshotenabled == true)
|
if (jsonObj.hypervisor != 'LXC') {
|
||||||
&& (jsonObj.hypervisor != 'LXC')) {
|
|
||||||
allowedActions.push("snapshot");
|
allowedActions.push("snapshot");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2738,8 +2738,7 @@
|
|||||||
allowedActions.push("destroy");
|
allowedActions.push("destroy");
|
||||||
allowedActions.push("reinstall");
|
allowedActions.push("reinstall");
|
||||||
|
|
||||||
if ((jsonObj.hypervisor != 'KVM' || g_kvmsnapshotenabled == true)
|
if (jsonObj.hypervisor != 'KVM' && jsonObj.hypervisor != 'LXC') {
|
||||||
&& (jsonObj.hypervisor != 'LXC')) {
|
|
||||||
allowedActions.push("snapshot");
|
allowedActions.push("snapshot");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2330,6 +2330,349 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VM Snapshots
|
||||||
|
*/
|
||||||
|
vmsnapshots: {
|
||||||
|
type: 'select',
|
||||||
|
title: 'label.vmsnapshot',
|
||||||
|
listView: {
|
||||||
|
id: 'vmsnapshots',
|
||||||
|
isMaximized: true,
|
||||||
|
fields: {
|
||||||
|
displayname: {
|
||||||
|
label: 'label.name'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
label: 'label.state',
|
||||||
|
indicator: {
|
||||||
|
'Ready': 'on',
|
||||||
|
'Error': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
label: 'label.vmsnapshot.type'
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
label: 'label.vmsnapshot.current',
|
||||||
|
converter: cloudStack.converters.toBooleanText
|
||||||
|
},
|
||||||
|
parentName: {
|
||||||
|
label: 'label.vmsnapshot.parentname'
|
||||||
|
},
|
||||||
|
created: {
|
||||||
|
label: 'label.date',
|
||||||
|
converter: cloudStack.converters.toLocalDate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
advSearchFields: {
|
||||||
|
name: {
|
||||||
|
label: 'label.name'
|
||||||
|
},
|
||||||
|
|
||||||
|
domainid: {
|
||||||
|
label: 'label.domain',
|
||||||
|
select: function(args) {
|
||||||
|
if (isAdmin() || isDomainAdmin()) {
|
||||||
|
$.ajax({
|
||||||
|
url: createURL('listDomains'),
|
||||||
|
data: {
|
||||||
|
listAll: true,
|
||||||
|
details: 'min'
|
||||||
|
},
|
||||||
|
success: function(json) {
|
||||||
|
var array1 = [{
|
||||||
|
id: '',
|
||||||
|
description: ''
|
||||||
|
}];
|
||||||
|
var domains = json.listdomainsresponse.domain;
|
||||||
|
if (domains != null && domains.length > 0) {
|
||||||
|
for (var i = 0; i < domains.length; i++) {
|
||||||
|
array1.push({
|
||||||
|
id: domains[i].id,
|
||||||
|
description: domains[i].path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
array1.sort(function(a, b) {
|
||||||
|
return a.description.localeCompare(b.description);
|
||||||
|
});
|
||||||
|
args.response.success({
|
||||||
|
data: array1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
args.response.success({
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isHidden: function(args) {
|
||||||
|
if (isAdmin() || isDomainAdmin())
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
account: {
|
||||||
|
label: 'label.account',
|
||||||
|
isHidden: function(args) {
|
||||||
|
if (isAdmin() || isDomainAdmin())
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tagKey: {
|
||||||
|
label: 'label.tag.key'
|
||||||
|
},
|
||||||
|
tagValue: {
|
||||||
|
label: 'label.tag.value'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dataProvider: function(args) {
|
||||||
|
var data = {};
|
||||||
|
listViewDataProvider(args, data);
|
||||||
|
|
||||||
|
if (args.context != null) {
|
||||||
|
if ("instances" in args.context) {
|
||||||
|
$.extend(data, {
|
||||||
|
virtualMachineId: args.context.instances[0].id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: createURL('listVMSnapshot&listAll=true'),
|
||||||
|
data: data,
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var jsonObj;
|
||||||
|
jsonObj = json.listvmsnapshotresponse.vmSnapshot;
|
||||||
|
args.response.success({
|
||||||
|
actionFilter: vmSnapshotActionfilter,
|
||||||
|
data: jsonObj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//dataProvider end
|
||||||
|
detailView: {
|
||||||
|
tabs: {
|
||||||
|
details: {
|
||||||
|
title: 'label.details',
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
label: 'label.id'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
label: 'label.name'
|
||||||
|
},
|
||||||
|
displayname: {
|
||||||
|
label: 'label.display.name'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
label: 'label.vmsnapshot.type'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
label: 'label.description'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
label: 'label.state',
|
||||||
|
indicator: {
|
||||||
|
'Ready': 'on',
|
||||||
|
'Error': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
label: 'label.vmsnapshot.current',
|
||||||
|
converter: cloudStack.converters.toBooleanText
|
||||||
|
},
|
||||||
|
parentName: {
|
||||||
|
label: 'label.vmsnapshot.parentname'
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
label: 'label.domain'
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
label: 'label.account'
|
||||||
|
},
|
||||||
|
virtualmachineid: {
|
||||||
|
label: 'label.vm.id'
|
||||||
|
},
|
||||||
|
created: {
|
||||||
|
label: 'label.date',
|
||||||
|
converter: cloudStack.converters.toLocalDate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataProvider: function(args) {
|
||||||
|
$.ajax({
|
||||||
|
url: createURL("listVMSnapshot&listAll=true&vmsnapshotid=" + args.context.vmsnapshots[0].id),
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var jsonObj;
|
||||||
|
jsonObj = json.listvmsnapshotresponse.vmSnapshot[0];
|
||||||
|
args.response.success({
|
||||||
|
actionFilter: vmSnapshotActionfilter,
|
||||||
|
data: jsonObj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tags: cloudStack.api.tags({
|
||||||
|
resourceType: 'VMSnapshot',
|
||||||
|
contextId: 'vmsnapshots'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
//delete a snapshot
|
||||||
|
remove: {
|
||||||
|
label: 'label.action.vmsnapshot.delete',
|
||||||
|
messages: {
|
||||||
|
confirm: function(args) {
|
||||||
|
return 'message.action.vmsnapshot.delete';
|
||||||
|
},
|
||||||
|
notification: function(args) {
|
||||||
|
return 'label.action.vmsnapshot.delete';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action: function(args) {
|
||||||
|
$.ajax({
|
||||||
|
url: createURL("deleteVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id),
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var jid = json.deletevmsnapshotresponse.jobid;
|
||||||
|
args.response.success({
|
||||||
|
_custom: {
|
||||||
|
jobId: jid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
poll: pollAsyncJobResult
|
||||||
|
}
|
||||||
|
},
|
||||||
|
revertToVMSnapshot: {
|
||||||
|
label: 'label.action.vmsnapshot.revert',
|
||||||
|
messages: {
|
||||||
|
confirm: function(args) {
|
||||||
|
return 'label.action.vmsnapshot.revert';
|
||||||
|
},
|
||||||
|
notification: function(args) {
|
||||||
|
return 'message.action.vmsnapshot.revert';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action: function(args) {
|
||||||
|
$.ajax({
|
||||||
|
url: createURL("revertToVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id),
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var jid = json.reverttovmsnapshotresponse.jobid;
|
||||||
|
args.response.success({
|
||||||
|
_custom: {
|
||||||
|
jobId: jid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
poll: pollAsyncJobResult
|
||||||
|
}
|
||||||
|
},
|
||||||
|
takeSnapshot: {
|
||||||
|
label: 'Create Snapshot From VM Snapshot',
|
||||||
|
messages: {
|
||||||
|
confirm: function(args) {
|
||||||
|
return 'Please confirm that you want to create a volume snapshot from the vm snapshot.';
|
||||||
|
},
|
||||||
|
notification: function(args) {
|
||||||
|
return 'Volume snapshot is created from vm snapshot';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createForm: {
|
||||||
|
title: 'label.action.take.snapshot',
|
||||||
|
desc: 'message.action.take.snapshot',
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
label: 'label.name',
|
||||||
|
},
|
||||||
|
volume: {
|
||||||
|
label: 'label.volume',
|
||||||
|
validation: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
select: function(args) {
|
||||||
|
$.ajax({
|
||||||
|
url: createURL("listVolumes&virtualMachineId=" + args.context.vmsnapshots[0].virtualmachineid),
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var volumes = json.listvolumesresponse.volume;
|
||||||
|
var items = [];
|
||||||
|
$(volumes).each(function() {
|
||||||
|
items.push({
|
||||||
|
id: this.id,
|
||||||
|
description: this.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
args.response.success({
|
||||||
|
data: items
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action: function(args) {
|
||||||
|
var data = {
|
||||||
|
volumeid: args.data.volume,
|
||||||
|
vmsnapshotid: args.context.vmsnapshots[0].id
|
||||||
|
};
|
||||||
|
if (args.data.name != null && args.data.name.length > 0) {
|
||||||
|
$.extend(data, {
|
||||||
|
name: args.data.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: createURL("createSnapshotFromVMSnapshot"),
|
||||||
|
data: data,
|
||||||
|
dataType: "json",
|
||||||
|
async: true,
|
||||||
|
success: function(json) {
|
||||||
|
var jid = json.createsnapshotfromvmsnapshotresponse.jobid;
|
||||||
|
args.response.success({
|
||||||
|
_custom: {
|
||||||
|
jobId: jid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
poll: pollAsyncJobResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//detailview end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2422,6 +2765,23 @@
|
|||||||
}
|
}
|
||||||
allowedActions.push("remove");
|
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;
|
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…
x
Reference in New Issue
Block a user