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
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/a2428508
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/a2428508
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/a2428508
Branch: refs/heads/master
Commit: a2428508e2969e89577ba29e4cf43ce28ba11704
Parents: 9513053
Author: Wei Zhou <w.zhou@tech.leaseweb.com>
Authored: Mon Dec 14 11:17:48 2015 +0100
Committer: Wei Zhou <w.zhou@tech.leaseweb.com>
Committed: Tue Jan 24 21:47:30 2017 +0100
----------------------------------------------------------------------
api/src/com/cloud/storage/VolumeApiService.java | 2 +
.../storage/snapshot/SnapshotApiService.java | 2 +
.../cloud/vm/snapshot/VMSnapshotService.java | 2 +-
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
.../user/snapshot/CreateSnapshotCmd.java | 2 +-
.../CreateSnapshotFromVMSnapshotCmd.java | 219 +++++++++++
.../user/vmsnapshot/CreateVMSnapshotCmd.java | 2 +-
.../agent/api/RestoreVMSnapshotAnswer.java | 63 ++++
.../agent/api/RestoreVMSnapshotCommand.java | 52 +++
.../cloud/vm/snapshot/VMSnapshotManager.java | 7 +
.../com/cloud/vm/VirtualMachineManagerImpl.java | 21 +-
.../snapshot/XenserverSnapshotStrategy.java | 6 +
.../kvm/resource/LibvirtComputingResource.java | 101 ++++++
.../LibvirtCreateVMSnapshotCommandWrapper.java | 82 +++++
.../LibvirtDeleteVMSnapshotCommandWrapper.java | 110 ++++++
.../wrapper/LibvirtMigrateCommandWrapper.java | 12 +
.../LibvirtRestoreVMSnapshotCommandWrapper.java | 96 +++++
...LibvirtRevertToVMSnapshotCommandWrapper.java | 95 +++++
.../wrapper/LibvirtUtilitiesHelper.java | 13 +
.../kvm/storage/KVMStorageProcessor.java | 87 +++--
server/src/com/cloud/api/ApiResponseHelper.java | 1 +
.../com/cloud/server/ManagementServerImpl.java | 2 +
.../com/cloud/storage/VolumeApiServiceImpl.java | 47 +++
.../storage/snapshot/SnapshotManagerImpl.java | 76 ++++
server/src/com/cloud/vm/UserVmManagerImpl.java | 16 +
.../vm/snapshot/VMSnapshotManagerImpl.java | 93 ++++-
.../storage/snapshot/SnapshotManagerTest.java | 54 +++
setup/db/db/schema-4920to41000.sql | 9 +
test/integration/smoke/test_vm_snapshots.py | 2 +-
ui/css/cloudstack3.css | 6 +
ui/l10n/en.js | 3 +-
ui/scripts/instances.js | 9 +-
ui/scripts/storage.js | 362 ++++++++++++++++++-
ui/scripts/vm_snapshots.js | 198 ----------
34 files changed, 1599 insertions(+), 254 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/com/cloud/storage/VolumeApiService.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/storage/VolumeApiService.java b/api/src/com/cloud/storage/VolumeApiService.java
index f562ce2..673fffc 100644
--- a/api/src/com/cloud/storage/VolumeApiService.java
+++ b/api/src/com/cloud/storage/VolumeApiService.java
@@ -102,4 +102,6 @@ public interface VolumeApiService {
boolean isDisplayResourceEnabled(Long id);
void updateDisplay(Volume volume, Boolean displayVolume);
+
+ Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/com/cloud/storage/snapshot/SnapshotApiService.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/storage/snapshot/SnapshotApiService.java b/api/src/com/cloud/storage/snapshot/SnapshotApiService.java
index 013704d..eb13935 100644
--- a/api/src/com/cloud/storage/snapshot/SnapshotApiService.java
+++ b/api/src/com/cloud/storage/snapshot/SnapshotApiService.java
@@ -108,5 +108,7 @@ public interface SnapshotApiService {
Snapshot revertSnapshot(Long snapshotId);
+ Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId);
+
SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd);
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/com/cloud/vm/snapshot/VMSnapshotService.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/vm/snapshot/VMSnapshotService.java b/api/src/com/cloud/vm/snapshot/VMSnapshotService.java
index 12767b3..0d4d22c 100644
--- a/api/src/com/cloud/vm/snapshot/VMSnapshotService.java
+++ b/api/src/com/cloud/vm/snapshot/VMSnapshotService.java
@@ -36,7 +36,7 @@ public interface VMSnapshotService {
VMSnapshot getVMSnapshotById(Long id);
- VMSnapshot creatVMSnapshot(Long vmId, Long vmSnapshotId, Boolean quiescevm);
+ VMSnapshot createVMSnapshot(Long vmId, Long vmSnapshotId, Boolean quiescevm);
VMSnapshot allocVMSnapshot(Long vmId, String vsDisplayName, String vsDescription, Boolean snapshotMemory) throws ResourceAllocationException;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index 00e9d38..708b0d1 100644
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -240,6 +240,7 @@ public class ApiConstants {
public static final String SIGNATURE = "signature";
public static final String SIGNATURE_VERSION = "signatureversion";
public static final String SIZE = "size";
+ public static final String SNAPSHOT = "snapshot";
public static final String SNAPSHOT_ID = "snapshotid";
public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid";
public static final String SNAPSHOT_TYPE = "snapshottype";
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
index 4523889..e79feb7 100644
--- a/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
+++ b/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java
@@ -143,7 +143,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
}
public static String getResultObjectName() {
- return "snapshot";
+ return ApiConstants.SNAPSHOT;
}
@Override
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java
new file mode 100644
index 0000000..7a35d34
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java
@@ -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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java
index f18793a..3e37bbe 100644
--- a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java
+++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java
@@ -110,7 +110,7 @@ public class CreateVMSnapshotCmd extends BaseAsyncCreateCmd {
@Override
public void execute() {
CallContext.current().setEventDetails("VM Id: " + getVmId());
- VMSnapshot result = _vmSnapshotService.creatVMSnapshot(getVmId(), getEntityId(), getQuiescevm());
+ VMSnapshot result = _vmSnapshotService.createVMSnapshot(getVmId(), getEntityId(), getQuiescevm());
if (result != null) {
VMSnapshotResponse response = _responseGenerator.createVMSnapshotResponse(result);
response.setResponseName(getCommandName());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/core/src/com/cloud/agent/api/RestoreVMSnapshotAnswer.java
----------------------------------------------------------------------
diff --git a/core/src/com/cloud/agent/api/RestoreVMSnapshotAnswer.java b/core/src/com/cloud/agent/api/RestoreVMSnapshotAnswer.java
new file mode 100644
index 0000000..390f49a
--- /dev/null
+++ b/core/src/com/cloud/agent/api/RestoreVMSnapshotAnswer.java
@@ -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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/core/src/com/cloud/agent/api/RestoreVMSnapshotCommand.java
----------------------------------------------------------------------
diff --git a/core/src/com/cloud/agent/api/RestoreVMSnapshotCommand.java b/core/src/com/cloud/agent/api/RestoreVMSnapshotCommand.java
new file mode 100644
index 0000000..2769475
--- /dev/null
+++ b/core/src/com/cloud/agent/api/RestoreVMSnapshotCommand.java
@@ -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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/engine/components-api/src/com/cloud/vm/snapshot/VMSnapshotManager.java
----------------------------------------------------------------------
diff --git a/engine/components-api/src/com/cloud/vm/snapshot/VMSnapshotManager.java b/engine/components-api/src/com/cloud/vm/snapshot/VMSnapshotManager.java
index e7e3372..ce8a818 100644
--- a/engine/components-api/src/com/cloud/vm/snapshot/VMSnapshotManager.java
+++ b/engine/components-api/src/com/cloud/vm/snapshot/VMSnapshotManager.java
@@ -17,7 +17,11 @@
package com.cloud.vm.snapshot;
+import java.util.List;
+
+import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.utils.component.Manager;
+import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
public interface VMSnapshotManager extends VMSnapshotService, Manager {
@@ -42,4 +46,7 @@ public interface VMSnapshotManager extends VMSnapshotService, Manager {
boolean syncVMSnapshot(VMInstanceVO vm, Long hostId);
boolean hasActiveVMSnapshotTasks(Long vmId);
+
+ RestoreVMSnapshotCommand createRestoreCommand(UserVmVO userVm, List<VMSnapshotVO> vmSnapshotVOs);
+
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
----------------------------------------------------------------------
diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
index f982de8..b1c69b2 100644
--- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -85,6 +85,8 @@ import com.cloud.agent.api.PlugNicCommand;
import com.cloud.agent.api.PrepareForMigrationCommand;
import com.cloud.agent.api.RebootAnswer;
import com.cloud.agent.api.RebootCommand;
+import com.cloud.agent.api.RestoreVMSnapshotAnswer;
+import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.agent.api.ScaleVmCommand;
import com.cloud.agent.api.StartAnswer;
import com.cloud.agent.api.StartCommand;
@@ -201,6 +203,7 @@ import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.VMSnapshotManager;
+import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable {
@@ -1721,6 +1724,18 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
}
+ UserVmVO userVm = _userVmDao.findById(vm.getId());
+ if (userVm != null) {
+ List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vm.getId());
+ RestoreVMSnapshotCommand command = _vmSnapshotMgr.createRestoreCommand(userVm, vmSnapshots);
+ if (command != null) {
+ RestoreVMSnapshotAnswer restoreVMSnapshotAnswer = (RestoreVMSnapshotAnswer) _agentMgr.send(hostId, command);
+ if (restoreVMSnapshotAnswer == null || !restoreVMSnapshotAnswer.getResult()) {
+ s_logger.warn("Unable to restore the vm snapshot from image file after live migration of vm with vmsnapshots: " + restoreVMSnapshotAnswer.getDetails());
+ }
+ }
+ }
+
return true;
}
@@ -2603,7 +2618,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
private void orchestrateReboot(final String vmUuid, final Map<VirtualMachineProfile.Param, Object> params) throws InsufficientCapacityException, ConcurrentOperationException,
ResourceUnavailableException {
final VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
-
+ // if there are active vm snapshots task, state change is not allowed
+ if(_vmSnapshotMgr.hasActiveVMSnapshotTasks(vm.getId())){
+ s_logger.error("Unable to reboot VM " + vm + " due to: " + vm.getInstanceName() + " has active VM snapshots tasks");
+ throw new CloudRuntimeException("Unable to reboot VM " + vm + " due to: " + vm.getInstanceName() + " has active VM snapshots tasks");
+ }
final DataCenter dc = _entityMgr.findById(DataCenter.class, vm.getDataCenterId());
final Host host = _hostDao.findById(vm.getHostId());
if (host == null) {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java
----------------------------------------------------------------------
diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java
index a9a3f4c..16a47c6 100644
--- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java
@@ -218,6 +218,12 @@ public class XenserverSnapshotStrategy extends SnapshotStrategyBase {
@Override
public boolean deleteSnapshot(Long snapshotId) {
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
+
+ if (snapshotVO.getState() == Snapshot.State.Allocated) {
+ snapshotDao.remove(snapshotId);
+ return true;
+ }
+
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
return true;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index 38dabda..42d80b3 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -22,6 +22,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
+import java.io.StringReader;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
@@ -44,6 +45,9 @@ import java.util.regex.Pattern;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import com.google.common.base.Strings;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
@@ -62,7 +66,14 @@ import org.libvirt.Domain;
import org.libvirt.DomainBlockStats;
import org.libvirt.DomainInfo;
import org.libvirt.DomainInfo.DomainState;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
import org.libvirt.DomainInterfaceStats;
+import org.libvirt.DomainSnapshot;
import org.libvirt.LibvirtException;
import org.libvirt.MemoryStatistic;
import org.libvirt.NodeInfo;
@@ -142,6 +153,7 @@ import com.cloud.utils.NumbersUtil;
import com.cloud.utils.StringUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.OutputInterpreter;
@@ -2776,6 +2788,22 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
DomainState state = null;
Domain dm = null;
+ // delete the metadata of vm snapshots before stopping
+ try {
+ dm = conn.domainLookupByName(vmName);
+ cleanVMSnapshotMetadata(dm);
+ } catch (LibvirtException e) {
+ s_logger.debug("Failed to get vm :" + e.getMessage());
+ } finally {
+ try {
+ if (dm != null) {
+ dm.free();
+ }
+ } catch (LibvirtException l) {
+ s_logger.trace("Ignoring libvirt error.", l);
+ }
+ }
+
s_logger.debug("Try to stop the vm at first");
String ret = stopVM(conn, vmName, false);
if (ret == Script.ERR_TIMEOUT) {
@@ -3481,4 +3509,77 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
}
return device;
}
+
+ public List<Ternary<String, Boolean, String>> cleanVMSnapshotMetadata(Domain dm) throws LibvirtException {
+ s_logger.debug("Cleaning the metadata of vm snapshots of vm " + dm.getName());
+ List<Ternary<String, Boolean, String>> vmsnapshots = new ArrayList<Ternary<String, Boolean, String>>();
+ if (dm.snapshotNum() == 0) {
+ return vmsnapshots;
+ }
+ String currentSnapshotName = null;
+ try {
+ DomainSnapshot snapshotCurrent = dm.snapshotCurrent();
+ String snapshotXML = snapshotCurrent.getXMLDesc();
+ snapshotCurrent.free();
+ DocumentBuilder builder;
+ try {
+ builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(snapshotXML));
+ Document doc = builder.parse(is);
+ Element rootElement = doc.getDocumentElement();
+
+ currentSnapshotName = getTagValue("name", rootElement);
+ } catch (ParserConfigurationException e) {
+ s_logger.debug(e.toString());
+ } catch (SAXException e) {
+ s_logger.debug(e.toString());
+ } catch (IOException e) {
+ s_logger.debug(e.toString());
+ }
+ } catch (LibvirtException e) {
+ s_logger.debug("Fail to get the current vm snapshot for vm: " + dm.getName() + ", continue");
+ }
+ int flags = 2; // VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = 2
+ String[] snapshotNames = dm.snapshotListNames();
+ Arrays.sort(snapshotNames);
+ for (String snapshotName: snapshotNames) {
+ DomainSnapshot snapshot = dm.snapshotLookupByName(snapshotName);
+ Boolean isCurrent = (currentSnapshotName != null && currentSnapshotName.equals(snapshotName)) ? true: false;
+ vmsnapshots.add(new Ternary<String, Boolean, String>(snapshotName, isCurrent, snapshot.getXMLDesc()));
+ }
+ for (String snapshotName: snapshotNames) {
+ DomainSnapshot snapshot = dm.snapshotLookupByName(snapshotName);
+ snapshot.delete(flags); // clean metadata of vm snapshot
+ }
+ return vmsnapshots;
+ }
+
+ private static String getTagValue(String tag, Element eElement) {
+ NodeList nlList = eElement.getElementsByTagName(tag).item(0).getChildNodes();
+ Node nValue = nlList.item(0);
+
+ return nValue.getNodeValue();
+ }
+
+ public void restoreVMSnapshotMetadata(Domain dm, String vmName, List<Ternary<String, Boolean, String>> vmsnapshots) {
+ s_logger.debug("Restoring the metadata of vm snapshots of vm " + vmName);
+ for (Ternary<String, Boolean, String> vmsnapshot: vmsnapshots) {
+ String snapshotName = vmsnapshot.first();
+ Boolean isCurrent = vmsnapshot.second();
+ String snapshotXML = vmsnapshot.third();
+ s_logger.debug("Restoring vm snapshot " + snapshotName + " on " + vmName + " with XML:\n " + snapshotXML);
+ try {
+ int flags = 1; // VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE = 1
+ if (isCurrent) {
+ flags += 2; // VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT = 2
+ }
+ dm.snapshotCreateXML(snapshotXML, flags);
+ } catch (LibvirtException e) {
+ s_logger.debug("Failed to restore vm snapshot " + snapshotName + ", continue");
+ continue;
+ }
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateVMSnapshotCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateVMSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateVMSnapshotCommandWrapper.java
new file mode 100644
index 0000000..c7941e7
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateVMSnapshotCommandWrapper.java
@@ -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);
+ };
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
new file mode 100644
index 0000000..9efec95
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
@@ -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);
+ };
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
index 6736c51..9e7b78e 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java
@@ -44,6 +44,7 @@ import com.cloud.hypervisor.kvm.resource.MigrateKVMAsync;
import com.cloud.hypervisor.kvm.resource.VifDriver;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.Ternary;
@ResourceWrapper(handles = MigrateCommand.class)
public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCommand, Answer, LibvirtComputingResource> {
@@ -67,6 +68,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
Domain destDomain = null;
Connect conn = null;
String xmlDesc = null;
+ List<Ternary<String, Boolean, String>> vmsnapshots = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
@@ -99,6 +101,9 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
xmlDesc = dm.getXMLDesc(xmlFlag);
xmlDesc = replaceIpForVNCInDescFile(xmlDesc, target);
+ // delete the metadata of vm snapshots before migration
+ vmsnapshots = libvirtComputingResource.cleanVMSnapshotMetadata(dm);
+
dconn = libvirtUtilitiesHelper.retrieveQemuConnection("qemu+tcp://" + command.getDestinationIp() + "/system");
//run migration in thread so we can monitor it
@@ -149,6 +154,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
libvirtComputingResource.cleanupDisk(disk);
}
}
+
} catch (final LibvirtException e) {
s_logger.debug("Can't migrate domain: " + e.getMessage());
result = e.getMessage();
@@ -163,6 +169,12 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
result = e.getMessage();
} finally {
try {
+ if (dm != null && result != null) {
+ // restore vm snapshots in case of failed migration
+ if (vmsnapshots != null) {
+ libvirtComputingResource.restoreVMSnapshotMetadata(dm, vmName, vmsnapshots);
+ }
+ }
if (dm != null) {
if (dm.isPersistent() == 1) {
dm.undefine();
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreVMSnapshotCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreVMSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreVMSnapshotCommandWrapper.java
new file mode 100644
index 0000000..ce8c209
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreVMSnapshotCommandWrapper.java
@@ -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);
+ };
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertToVMSnapshotCommandWrapper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertToVMSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertToVMSnapshotCommandWrapper.java
new file mode 100644
index 0000000..086d6ef
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertToVMSnapshotCommandWrapper.java
@@ -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);
+ };
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelper.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelper.java
index 7a93e1f..2881ed0 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelper.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelper.java
@@ -25,6 +25,7 @@ import javax.naming.ConfigurationException;
import org.libvirt.Connect;
import org.libvirt.LibvirtException;
+import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.storage.StorageLayer;
@@ -96,4 +97,16 @@ public class LibvirtUtilitiesHelper {
final Script script = new Script(scriptPath, TIMEOUT);
return script;
}
+
+ public String generateVMSnapshotXML(VMSnapshotTO snapshot, VMSnapshotTO parent, String domainXmlDesc) {
+ String parentName = (parent == null)? "": (" <parent><name>" + parent.getSnapshotName() + "</name></parent>\n");
+ String vmSnapshotXML = "<domainsnapshot>\n"
+ + " <name>" + snapshot.getSnapshotName() + "</name>\n"
+ + " <state>running</state>\n"
+ + parentName
+ + " <creationTime>" + (int) Math.rint(snapshot.getCreateTime()/1000) + "</creationTime>\n"
+ + domainXmlDesc
+ + "</domainsnapshot>";
+ return vmSnapshotXML;
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index f11cb21..55b458a 100644
--- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -695,8 +695,10 @@ public class KVMStorageProcessor implements StorageProcessor {
final String secondaryStoragePoolUrl = nfsImageStore.getUrl();
// NOTE: snapshot name is encoded in snapshot path
final int index = snapshot.getPath().lastIndexOf("/");
+ final boolean isCreatedFromVmSnapshot = (index == -1) ? true: false; // -1 means the snapshot is created from existing vm snapshot
final String snapshotName = snapshot.getPath().substring(index + 1);
+ String descName = snapshotName;
final String volumePath = snapshot.getVolume().getPath();
String snapshotDestPath = null;
String snapshotRelPath = null;
@@ -768,20 +770,23 @@ public class KVMStorageProcessor implements StorageProcessor {
command.add("-b", snapshotDisk.getPath());
command.add("-n", snapshotName);
command.add("-p", snapshotDestPath);
- command.add("-t", snapshotName);
+ if (isCreatedFromVmSnapshot) {
+ descName = UUID.randomUUID().toString();
+ }
+ command.add("-t", descName);
final String result = command.execute();
if (result != null) {
s_logger.debug("Failed to backup snaptshot: " + result);
return new CopyCmdAnswer(result);
}
- final File snapFile = new File(snapshotDestPath + "/" + snapshotName);
+ final File snapFile = new File(snapshotDestPath + "/" + descName);
if(snapFile.exists()){
size = snapFile.length();
}
}
final SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
- newSnapshot.setPath(snapshotRelPath + File.separator + snapshotName);
+ newSnapshot.setPath(snapshotRelPath + File.separator + descName);
newSnapshot.setPhysicalSize(size);
return new CopyCmdAnswer(newSnapshot);
} catch (final LibvirtException e) {
@@ -791,48 +796,52 @@ public class KVMStorageProcessor implements StorageProcessor {
s_logger.debug("Failed to backup snapshot: ", e);
return new CopyCmdAnswer(e.toString());
} finally {
- try {
- /* Delete the snapshot on primary */
- DomainInfo.DomainState state = null;
- Domain vm = null;
- if (vmName != null) {
- try {
- vm = resource.getDomain(conn, vmName);
- state = vm.getInfo().state;
- } catch (final LibvirtException e) {
- s_logger.trace("Ignoring libvirt error.", e);
+ if (isCreatedFromVmSnapshot) {
+ s_logger.debug("Ignoring removal of vm snapshot on primary as this snapshot is created from vm snapshot");
+ } else {
+ try {
+ /* Delete the snapshot on primary */
+ DomainInfo.DomainState state = null;
+ Domain vm = null;
+ if (vmName != null) {
+ try {
+ vm = resource.getDomain(conn, vmName);
+ state = vm.getInfo().state;
+ } catch (final LibvirtException e) {
+ s_logger.trace("Ignoring libvirt error.", e);
+ }
}
- }
- final KVMStoragePool primaryStorage = storagePoolMgr.getStoragePool(primaryStore.getPoolType(),
- primaryStore.getUuid());
- if (state == DomainInfo.DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) {
- final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName);
- snap.delete(0);
+ final KVMStoragePool primaryStorage = storagePoolMgr.getStoragePool(primaryStore.getPoolType(),
+ primaryStore.getUuid());
+ if (state == DomainInfo.DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) {
+ final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName);
+ snap.delete(0);
- /*
- * libvirt on RHEL6 doesn't handle resume event emitted from
- * qemu
- */
- vm = resource.getDomain(conn, vmName);
- state = vm.getInfo().state;
- if (state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) {
- vm.resume();
- }
- } else {
- if (primaryPool.getType() != StoragePoolType.RBD) {
- final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger);
- command.add("-d", snapshotDisk.getPath());
- command.add("-n", snapshotName);
- final String result = command.execute();
- if (result != null) {
- s_logger.debug("Failed to delete snapshot on primary: " + result);
- // return new CopyCmdAnswer("Failed to backup snapshot: " + result);
+ /*
+ * libvirt on RHEL6 doesn't handle resume event emitted from
+ * qemu
+ */
+ vm = resource.getDomain(conn, vmName);
+ state = vm.getInfo().state;
+ if (state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) {
+ vm.resume();
+ }
+ } else {
+ if (primaryPool.getType() != StoragePoolType.RBD) {
+ final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger);
+ command.add("-d", snapshotDisk.getPath());
+ command.add("-n", snapshotName);
+ final String result = command.execute();
+ if (result != null) {
+ s_logger.debug("Failed to delete snapshot on primary: " + result);
+ // return new CopyCmdAnswer("Failed to backup snapshot: " + result);
+ }
}
}
+ } catch (final Exception ex) {
+ s_logger.debug("Failed to delete snapshots on primary", ex);
}
- } catch (final Exception ex) {
- s_logger.debug("Failed to delete snapshots on primary", ex);
}
try {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/server/src/com/cloud/api/ApiResponseHelper.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java
index 6815797..0b47026 100644
--- a/server/src/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/com/cloud/api/ApiResponseHelper.java
@@ -577,6 +577,7 @@ public class ApiResponseHelper implements ResponseGenerator {
vmSnapshotResponse.setParentName(vmSnapshotParent.getDisplayName());
}
}
+ populateOwner(vmSnapshotResponse, vmSnapshot);
Project project = ApiDBUtils.findProjectByProjectAccountId(vmSnapshot.getAccountId());
if (project != null) {
vmSnapshotResponse.setProjectId(project.getUuid());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/server/src/com/cloud/server/ManagementServerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java
index b6a2637..5ab7c36 100644
--- a/server/src/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/com/cloud/server/ManagementServerImpl.java
@@ -409,6 +409,7 @@ import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCm
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupEgressCmd;
import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
+import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
@@ -2837,6 +2838,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(RevokeSecurityGroupEgressCmd.class);
cmdList.add(RevokeSecurityGroupIngressCmd.class);
cmdList.add(CreateSnapshotCmd.class);
+ cmdList.add(CreateSnapshotFromVMSnapshotCmd.class);
cmdList.add(DeleteSnapshotCmd.class);
cmdList.add(CreateSnapshotPolicyCmd.class);
cmdList.add(UpdateSnapshotPolicyCmd.class);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/server/src/com/cloud/storage/VolumeApiServiceImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java
index 759c7b4..89e1fd9 100644
--- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java
@@ -2196,6 +2196,53 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
@Override
+ public Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException {
+ Account caller = CallContext.current().getCallingAccount();
+ VMInstanceVO vm = _vmInstanceDao.findById(vmId);
+ if (vm == null) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to vm:" + vmId + " doesn't exist");
+ }
+ _accountMgr.checkAccess(caller, null, true, vm);
+
+ VolumeInfo volume = volFactory.getVolume(volumeId);
+ if (volume == null) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
+ }
+ _accountMgr.checkAccess(caller, null, true, volume);
+ VirtualMachine attachVM = volume.getAttachedVM();
+ if (attachVM == null || attachVM.getId() != vm.getId()) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't attach to vm :" + vm);
+ }
+
+ DataCenter zone = _dcDao.findById(volume.getDataCenterId());
+ if (zone == null) {
+ throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId());
+ }
+
+ if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
+ throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getName());
+ }
+
+ if (volume.getState() != Volume.State.Ready) {
+ throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
+ }
+
+ if ( volume.getTemplateId() != null ) {
+ VMTemplateVO template = _templateDao.findById(volume.getTemplateId());
+ if( template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM ) {
+ throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported");
+ }
+ }
+
+ StoragePool storagePool = (StoragePool)volume.getDataStore();
+ if (storagePool == null) {
+ throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it");
+ }
+
+ return snapshotMgr.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, snapshotName, null);
+ }
+
+ @Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_EXTRACT, eventDescription = "extracting volume", async = true)
public String extractVolume(ExtractVolumeCmd cmd) {
Long volumeId = cmd.getId();
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java
index bb0fe37..d9a93c3 100644
--- a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java
+++ b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java
@@ -101,6 +101,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
@@ -262,6 +263,11 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
if (vm.getState() != State.Stopped && vm.getState() != State.Shutdowned) {
throw new InvalidParameterValueException("The VM the specified disk is attached to is not in the shutdown state.");
}
+ // If target VM has associated VM snapshots then don't allow to revert from snapshot
+ List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(instanceId);
+ if (vmSnapshots.size() > 0) {
+ throw new InvalidParameterValueException("Unable to revert snapshot for VM, please remove VM snapshots before reverting VM from snapshot");
+ }
}
SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image);
@@ -364,6 +370,76 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
}
@Override
+ public Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId) {
+ VMInstanceVO vm = _vmDao.findById(vmId);
+ if (vm == null) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to vm:" + vmId + " doesn't exist");
+ }
+ if (! HypervisorType.KVM.equals(vm.getHypervisorType())) {
+ throw new InvalidParameterValueException("Unsupported hypervisor type " + vm.getHypervisorType() + ". This supports KVM only");
+ }
+
+ VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(vmSnapshotId);
+ if (vmSnapshot == null) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to vmSnapshot:" + vmSnapshotId + " doesn't exist");
+ }
+ // check vmsnapshot permissions
+ Account caller = CallContext.current().getCallingAccount();
+ _accountMgr.checkAccess(caller, null, true, vmSnapshot);
+
+ SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
+ if (snapshot == null) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to snapshot:" + snapshotId + " doesn't exist");
+ }
+
+ VolumeInfo volume = volFactory.getVolume(volumeId);
+ if (volume == null) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
+ }
+
+ if (volume.getState() != Volume.State.Ready) {
+ throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
+ }
+
+ DataStore store = volume.getDataStore();
+ SnapshotDataStoreVO parentSnapshotDataStoreVO = _snapshotStoreDao.findParent(store.getRole(), store.getId(), volumeId);
+ if (parentSnapshotDataStoreVO != null) {
+ //Double check the snapshot is removed or not
+ SnapshotVO parentSnap = _snapshotDao.findById(parentSnapshotDataStoreVO.getSnapshotId());
+ if (parentSnap != null && parentSnapshotDataStoreVO.getInstallPath() != null && parentSnapshotDataStoreVO.getInstallPath().equals(vmSnapshot.getName())) {
+ throw new InvalidParameterValueException("Creating snapshot failed due to snapshot : " + parentSnap.getUuid() + " is created from the same vm snapshot");
+ }
+ }
+ SnapshotInfo snapshotInfo = this.snapshotFactory.getSnapshot(snapshotId, store);
+ snapshotInfo = (SnapshotInfo) store.create(snapshotInfo);
+ SnapshotDataStoreVO snapshotOnPrimaryStore = this._snapshotStoreDao.findBySnapshot(snapshot.getId(), store.getRole());
+ snapshotOnPrimaryStore.setState(ObjectInDataStoreStateMachine.State.Ready);
+ snapshotOnPrimaryStore.setInstallPath(vmSnapshot.getName());
+ _snapshotStoreDao.update(snapshotOnPrimaryStore.getId(), snapshotOnPrimaryStore);
+ snapshot.setState(Snapshot.State.CreatedOnPrimary);
+ _snapshotDao.update(snapshot.getId(), snapshot);
+
+ snapshotInfo = this.snapshotFactory.getSnapshot(snapshotId, store);
+
+ Long snapshotOwnerId = vm.getAccountId();
+
+ try {
+ SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.BACKUP);
+ if (snapshotStrategy == null) {
+ throw new CloudRuntimeException("Unable to find snaphot strategy to handle snapshot with id '" + snapshotId + "'");
+ }
+ snapshotInfo = snapshotStrategy.backupSnapshot(snapshotInfo);
+
+ } catch(Exception e) {
+ s_logger.debug("Failed to backup snapshot from vm snapshot", e);
+ _resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.snapshot);
+ _resourceLimitMgr.decrementResourceCount(snapshotOwnerId, ResourceType.secondary_storage, new Long(volume.getSize()));
+ throw new CloudRuntimeException("Failed to backup snapshot from vm snapshot", e);
+ }
+ return snapshotInfo;
+ }
+
+ @Override
public SnapshotVO getParentSnapshot(VolumeInfo volume) {
long preId = _snapshotDao.getLastSnapshot(volume.getId(), DataStoreRole.Primary);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a2428508/server/src/com/cloud/vm/UserVmManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java
index cf58679..ec7a6f6 100644
--- a/server/src/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/com/cloud/vm/UserVmManagerImpl.java
@@ -102,6 +102,8 @@ import com.cloud.agent.api.GetVmIpAddressCommand;
import com.cloud.agent.api.GetVmStatsAnswer;
import com.cloud.agent.api.GetVmStatsCommand;
import com.cloud.agent.api.PvlanSetupCommand;
+import com.cloud.agent.api.RestoreVMSnapshotAnswer;
+import com.cloud.agent.api.RestoreVMSnapshotCommand;
import com.cloud.agent.api.StartAnswer;
import com.cloud.agent.api.VmDiskStatsEntry;
import com.cloud.agent.api.VmStatsEntry;
@@ -3804,11 +3806,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
+ finalizeCommandsOnStart(cmds, profile);
return true;
}
@Override
public boolean finalizeCommandsOnStart(Commands cmds, VirtualMachineProfile profile) {
+ UserVmVO vm = _vmDao.findById(profile.getId());
+ List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vm.getId());
+ RestoreVMSnapshotCommand command = _vmSnapshotMgr.createRestoreCommand(vm, vmSnapshots);
+ if (command != null)
+ cmds.addCommand("restoreVMSnapshot", command);
return true;
}
@@ -3893,6 +3901,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return false;
}
+ Answer answer = cmds.getAnswer("restoreVMSnapshot");
+ if (answer != null && answer instanceof RestoreVMSnapshotAnswer) {
+ RestoreVMSnapshotAnswer restoreVMSnapshotAnswer = (RestoreVMSnapshotAnswer) answer;
+ if (restoreVMSnapshotAnswer == null || !restoreVMSnapshotAnswer.getResult()) {
+ s_logger.warn("Unable to restore the vm snapshot from image file to the VM: " + restoreVMSnapshotAnswer.getDetails());
+ }
+ }
+
return true;
}
|