From bb7cd90b6129b3ed5adef1974c38d0706dd42441 Mon Sep 17 00:00:00 2001 From: Mice Xia Date: Wed, 30 Jan 2013 10:13:05 +0800 Subject: [PATCH] CLOUDSTACK-684 Support VM Snapshot --- .../agent/api/CreateVMSnapshotAnswer.java | 62 ++ .../agent/api/CreateVMSnapshotCommand.java | 42 + .../api/CreateVolumeFromVMSnapshotAnswer.java | 54 ++ .../CreateVolumeFromVMSnapshotCommand.java | 88 ++ .../agent/api/DeleteVMSnapshotAnswer.java | 49 ++ .../agent/api/DeleteVMSnapshotCommand.java | 28 + .../agent/api/RevertToVMSnapshotAnswer.java | 63 ++ .../agent/api/RevertToVMSnapshotCommand.java | 29 + .../agent/api/VMSnapshotBaseCommand.java | 74 ++ api/src/com/cloud/agent/api/VMSnapshotTO.java | 90 ++ api/src/com/cloud/agent/api/to/VolumeTO.java | 4 + api/src/com/cloud/event/EventTypes.java | 5 + api/src/com/cloud/server/ResourceTag.java | 3 +- api/src/com/cloud/vm/VirtualMachine.java | 28 +- api/src/com/cloud/vm/snapshot/VMSnapshot.java | 109 +++ .../cloud/vm/snapshot/VMSnapshotService.java | 51 ++ .../apache/cloudstack/api/ApiConstants.java | 5 + .../org/apache/cloudstack/api/BaseCmd.java | 3 + .../cloudstack/api/ResponseGenerator.java | 7 +- .../user/vmsnapshot/CreateVMSnapshotCmd.java | 125 +++ .../user/vmsnapshot/DeleteVMSnapshotCmd.java | 99 +++ .../user/vmsnapshot/ListVMSnapshotCmd.java | 105 +++ .../user/vmsnapshot/RevertToSnapshotCmd.java | 98 +++ .../api/response/VMSnapshotResponse.java | 220 +++++ .../classes/resources/messages.properties | 14 + client/tomcatconf/commands.properties.in | 6 + .../com/cloud/vm/snapshot/VMSnapshotVO.java | 224 +++++ .../vmware/manager/VmwareStorageManager.java | 16 +- .../manager/VmwareStorageManagerImpl.java | 332 ++++++- .../vmware/resource/VmwareResource.java | 52 +- .../xen/resource/CitrixResourceBase.java | 302 +++++++ scripts/vm/hypervisor/xenserver/vmopsSnapshot | 31 +- server/src/com/cloud/api/ApiDBUtils.java | 12 +- .../src/com/cloud/api/ApiResponseHelper.java | 32 +- .../cloud/capacity/CapacityManagerImpl.java | 43 +- .../src/com/cloud/configuration/Config.java | 12 +- .../DefaultComponentLibrary.java | 7 +- .../cloud/resource/ResourceManagerImpl.java | 2 +- .../storage/snapshot/SnapshotManagerImpl.java | 13 +- .../cloud/tags/TaggedResourceManagerImpl.java | 4 + .../src/com/cloud/vm/UserVmManagerImpl.java | 53 +- .../cloud/vm/VirtualMachineManagerImpl.java | 68 +- .../cloud/vm/snapshot/VMSnapshotManager.java | 46 + .../vm/snapshot/VMSnapshotManagerImpl.java | 828 ++++++++++++++++++ .../cloud/vm/snapshot/dao/VMSnapshotDao.java | 39 + .../vm/snapshot/dao/VMSnapshotDaoImpl.java | 160 ++++ .../vm/snapshot/VMSnapshotManagerTest.java | 182 ++++ setup/db/create-schema.sql | 30 + ui/css/cloudstack3.css | 2 +- ui/dictionary.jsp | 12 +- ui/index.jsp | 2 + ui/scripts/instances.js | 267 +++++- ui/scripts/ui/widgets/detailView.js | 13 +- ui/scripts/vm_snapshots.js | 196 +++++ .../cloud/hypervisor/vmware/mo/HostMO.java | 18 + .../vmware/mo/VirtualMachineMO.java | 20 + 56 files changed, 4393 insertions(+), 86 deletions(-) create mode 100644 api/src/com/cloud/agent/api/CreateVMSnapshotAnswer.java create mode 100644 api/src/com/cloud/agent/api/CreateVMSnapshotCommand.java create mode 100644 api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotAnswer.java create mode 100644 api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotCommand.java create mode 100644 api/src/com/cloud/agent/api/DeleteVMSnapshotAnswer.java create mode 100644 api/src/com/cloud/agent/api/DeleteVMSnapshotCommand.java create mode 100644 api/src/com/cloud/agent/api/RevertToVMSnapshotAnswer.java create mode 100644 api/src/com/cloud/agent/api/RevertToVMSnapshotCommand.java create mode 100644 api/src/com/cloud/agent/api/VMSnapshotBaseCommand.java create mode 100644 api/src/com/cloud/agent/api/VMSnapshotTO.java create mode 100644 api/src/com/cloud/vm/snapshot/VMSnapshot.java create mode 100644 api/src/com/cloud/vm/snapshot/VMSnapshotService.java create mode 100644 api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java create mode 100644 api/src/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java create mode 100644 api/src/org/apache/cloudstack/api/command/user/vmsnapshot/ListVMSnapshotCmd.java create mode 100644 api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java create mode 100644 api/src/org/apache/cloudstack/api/response/VMSnapshotResponse.java create mode 100644 core/src/com/cloud/vm/snapshot/VMSnapshotVO.java create mode 100644 server/src/com/cloud/vm/snapshot/VMSnapshotManager.java create mode 100644 server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java create mode 100644 server/src/com/cloud/vm/snapshot/dao/VMSnapshotDao.java create mode 100644 server/src/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java create mode 100644 server/test/com/cloud/vm/snapshot/VMSnapshotManagerTest.java create mode 100644 ui/scripts/vm_snapshots.js diff --git a/api/src/com/cloud/agent/api/CreateVMSnapshotAnswer.java b/api/src/com/cloud/agent/api/CreateVMSnapshotAnswer.java new file mode 100644 index 00000000000..f9fb1642b3f --- /dev/null +++ b/api/src/com/cloud/agent/api/CreateVMSnapshotAnswer.java @@ -0,0 +1,62 @@ +// 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 com.cloud.agent.api.to.VolumeTO; + +public class CreateVMSnapshotAnswer extends Answer { + + private List volumeTOs; + private VMSnapshotTO vmSnapshotTo; + + + public List getVolumeTOs() { + return volumeTOs; + } + + public void setVolumeTOs(List volumeTOs) { + this.volumeTOs = volumeTOs; + } + + public VMSnapshotTO getVmSnapshotTo() { + return vmSnapshotTo; + } + + public void setVmSnapshotTo(VMSnapshotTO vmSnapshotTo) { + this.vmSnapshotTo = vmSnapshotTo; + } + + public CreateVMSnapshotAnswer() { + + } + + public CreateVMSnapshotAnswer(CreateVMSnapshotCommand cmd, boolean success, + String result) { + super(cmd, success, result); + } + + public CreateVMSnapshotAnswer(CreateVMSnapshotCommand cmd, + VMSnapshotTO vmSnapshotTo, List volumeTOs) { + super(cmd, true, ""); + this.vmSnapshotTo = vmSnapshotTo; + this.volumeTOs = volumeTOs; + } + +} diff --git a/api/src/com/cloud/agent/api/CreateVMSnapshotCommand.java b/api/src/com/cloud/agent/api/CreateVMSnapshotCommand.java new file mode 100644 index 00000000000..478987d993b --- /dev/null +++ b/api/src/com/cloud/agent/api/CreateVMSnapshotCommand.java @@ -0,0 +1,42 @@ +// 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 com.cloud.agent.api.to.VolumeTO; +import com.cloud.vm.VirtualMachine; + +public class CreateVMSnapshotCommand extends VMSnapshotBaseCommand { + + public CreateVMSnapshotCommand(String vmName, VMSnapshotTO snapshot, List volumeTOs, String guestOSType, VirtualMachine.State vmState) { + super(vmName, snapshot, volumeTOs, guestOSType); + this.vmState = vmState; + } + + private VirtualMachine.State vmState; + + + public VirtualMachine.State getVmState() { + return vmState; + } + + public void setVmState(VirtualMachine.State vmState) { + this.vmState = vmState; + } + +} diff --git a/api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotAnswer.java b/api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotAnswer.java new file mode 100644 index 00000000000..ed3bc62ccba --- /dev/null +++ b/api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotAnswer.java @@ -0,0 +1,54 @@ +// 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 com.cloud.agent.api.to.VolumeTO; + +public class CreateVolumeFromVMSnapshotAnswer extends Answer { + private String path; + private VolumeTO volumeTo; + + public VolumeTO getVolumeTo() { + return volumeTo; + } + + public CreateVolumeFromVMSnapshotAnswer( + CreateVolumeFromVMSnapshotCommand cmd, VolumeTO volumeTo) { + super(cmd, true, ""); + this.volumeTo = volumeTo; + } + + public String getPath() { + return path; + } + + protected CreateVolumeFromVMSnapshotAnswer() { + + } + + public CreateVolumeFromVMSnapshotAnswer( + CreateVolumeFromVMSnapshotCommand cmd, String path) { + super(cmd, true, ""); + this.path = path; + } + + public CreateVolumeFromVMSnapshotAnswer( + CreateVolumeFromVMSnapshotCommand cmd, boolean result, String string) { + super(cmd, result, string); + } +} diff --git a/api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotCommand.java b/api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotCommand.java new file mode 100644 index 00000000000..634e15c9f04 --- /dev/null +++ b/api/src/com/cloud/agent/api/CreateVolumeFromVMSnapshotCommand.java @@ -0,0 +1,88 @@ +// 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 com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.vm.DiskProfile; + +public class CreateVolumeFromVMSnapshotCommand extends Command { + + protected String path; + protected String name; + protected Boolean fullClone; + protected String storagePoolUuid; + private StorageFilerTO pool; + private DiskProfile diskProfile; + private Long volumeId; + + public DiskProfile getDskch() { + return diskProfile; + } + + public String getPath() { + return path; + } + + public Long getVolumeId() { + return volumeId; + } + + protected CreateVolumeFromVMSnapshotCommand() { + + } + + public CreateVolumeFromVMSnapshotCommand(String path, String name, + Boolean fullClone, String storagePoolUuid) { + this.path = path; + this.name = name; + this.fullClone = fullClone; + this.storagePoolUuid = storagePoolUuid; + } + + public CreateVolumeFromVMSnapshotCommand(String path, String name, + Boolean fullClone, String storagePoolUuid, StorageFilerTO pool, + DiskProfile diskProfile, Long volumeId) { + this.path = path; + this.name = name; + this.fullClone = fullClone; + this.storagePoolUuid = storagePoolUuid; + this.pool = pool; + this.diskProfile = diskProfile; + this.volumeId = volumeId; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getName() { + return name; + } + + public Boolean getFullClone() { + return fullClone; + } + + public String getStoragePoolUuid() { + return storagePoolUuid; + } + + public StorageFilerTO getPool() { + return pool; + } +} diff --git a/api/src/com/cloud/agent/api/DeleteVMSnapshotAnswer.java b/api/src/com/cloud/agent/api/DeleteVMSnapshotAnswer.java new file mode 100644 index 00000000000..8f4ecad3d80 --- /dev/null +++ b/api/src/com/cloud/agent/api/DeleteVMSnapshotAnswer.java @@ -0,0 +1,49 @@ +// 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 com.cloud.agent.api.to.VolumeTO; + +public class DeleteVMSnapshotAnswer extends Answer { + private List volumeTOs; + + public DeleteVMSnapshotAnswer() { + } + + public DeleteVMSnapshotAnswer(DeleteVMSnapshotCommand cmd, boolean result, + String message) { + super(cmd, result, message); + } + + public DeleteVMSnapshotAnswer(DeleteVMSnapshotCommand cmd, + List volumeTOs) { + super(cmd, true, ""); + this.volumeTOs = volumeTOs; + } + + public List getVolumeTOs() { + return volumeTOs; + } + + public void setVolumeTOs(List volumeTOs) { + this.volumeTOs = volumeTOs; + } + + +} diff --git a/api/src/com/cloud/agent/api/DeleteVMSnapshotCommand.java b/api/src/com/cloud/agent/api/DeleteVMSnapshotCommand.java new file mode 100644 index 00000000000..c213448bf9c --- /dev/null +++ b/api/src/com/cloud/agent/api/DeleteVMSnapshotCommand.java @@ -0,0 +1,28 @@ +// 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 com.cloud.agent.api.to.VolumeTO; + + +public class DeleteVMSnapshotCommand extends VMSnapshotBaseCommand { + public DeleteVMSnapshotCommand(String vmName, VMSnapshotTO snapshot, List volumeTOs, String guestOSType) { + super( vmName, snapshot, volumeTOs, guestOSType); + } +} diff --git a/api/src/com/cloud/agent/api/RevertToVMSnapshotAnswer.java b/api/src/com/cloud/agent/api/RevertToVMSnapshotAnswer.java new file mode 100644 index 00000000000..848ffc0ebf8 --- /dev/null +++ b/api/src/com/cloud/agent/api/RevertToVMSnapshotAnswer.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 com.cloud.agent.api.to.VolumeTO; +import com.cloud.vm.VirtualMachine; + +public class RevertToVMSnapshotAnswer extends Answer { + + private List volumeTOs; + private VirtualMachine.State vmState; + + public RevertToVMSnapshotAnswer(RevertToVMSnapshotCommand cmd, boolean result, + String message) { + super(cmd, result, message); + } + + public RevertToVMSnapshotAnswer() { + super(); + } + + public RevertToVMSnapshotAnswer(RevertToVMSnapshotCommand cmd, + List volumeTOs, + VirtualMachine.State vmState) { + super(cmd, true, ""); + this.volumeTOs = volumeTOs; + this.vmState = vmState; + } + + public VirtualMachine.State getVmState() { + return vmState; + } + + public List getVolumeTOs() { + return volumeTOs; + } + + public void setVolumeTOs(List volumeTOs) { + this.volumeTOs = volumeTOs; + } + + public void setVmState(VirtualMachine.State vmState) { + this.vmState = vmState; + } + +} diff --git a/api/src/com/cloud/agent/api/RevertToVMSnapshotCommand.java b/api/src/com/cloud/agent/api/RevertToVMSnapshotCommand.java new file mode 100644 index 00000000000..429a186e0dc --- /dev/null +++ b/api/src/com/cloud/agent/api/RevertToVMSnapshotCommand.java @@ -0,0 +1,29 @@ +// 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 com.cloud.agent.api.to.VolumeTO; + +public class RevertToVMSnapshotCommand extends VMSnapshotBaseCommand { + + public RevertToVMSnapshotCommand(String vmName, VMSnapshotTO snapshot, List volumeTOs, String guestOSType) { + super(vmName, snapshot, volumeTOs, guestOSType); + } + +} diff --git a/api/src/com/cloud/agent/api/VMSnapshotBaseCommand.java b/api/src/com/cloud/agent/api/VMSnapshotBaseCommand.java new file mode 100644 index 00000000000..2120f2f73b1 --- /dev/null +++ b/api/src/com/cloud/agent/api/VMSnapshotBaseCommand.java @@ -0,0 +1,74 @@ +// 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 com.cloud.agent.api.to.VolumeTO; + +public class VMSnapshotBaseCommand extends Command{ + protected List volumeTOs; + protected VMSnapshotTO target; + protected String vmName; + protected String guestOSType; + + + public VMSnapshotBaseCommand(String vmName, VMSnapshotTO snapshot, List volumeTOs, String guestOSType) { + this.vmName = vmName; + this.target = snapshot; + this.volumeTOs = volumeTOs; + this.guestOSType = guestOSType; + } + + public List getVolumeTOs() { + return volumeTOs; + } + + public void setVolumeTOs(List volumeTOs) { + this.volumeTOs = volumeTOs; + } + + public VMSnapshotTO getTarget() { + return target; + } + + public void setTarget(VMSnapshotTO target) { + this.target = target; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getGuestOSType() { + return guestOSType; + } + + public void setGuestOSType(String guestOSType) { + this.guestOSType = guestOSType; + } +} diff --git a/api/src/com/cloud/agent/api/VMSnapshotTO.java b/api/src/com/cloud/agent/api/VMSnapshotTO.java new file mode 100644 index 00000000000..c7b42d25bc9 --- /dev/null +++ b/api/src/com/cloud/agent/api/VMSnapshotTO.java @@ -0,0 +1,90 @@ +// 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 com.cloud.vm.snapshot.VMSnapshot; + +public class VMSnapshotTO { + private Long id; + private String snapshotName; + private VMSnapshot.Type type; + private Long createTime; + private Boolean current; + private String description; + private VMSnapshotTO parent; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public VMSnapshotTO(Long id, String snapshotName, + VMSnapshot.Type type, Long createTime, + String description, Boolean current, VMSnapshotTO parent) { + super(); + this.id = id; + this.snapshotName = snapshotName; + this.type = type; + this.createTime = createTime; + this.current = current; + this.description = description; + this.parent = parent; + } + public VMSnapshotTO() { + + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public Boolean getCurrent() { + return current; + } + public void setCurrent(Boolean current) { + this.current = current; + } + public Long getCreateTime() { + return createTime; + } + public void setCreateTime(Long createTime) { + this.createTime = createTime; + } + + public VMSnapshot.Type getType() { + return type; + } + public void setType(VMSnapshot.Type type) { + this.type = type; + } + + public String getSnapshotName() { + return snapshotName; + } + public void setSnapshotName(String snapshotName) { + this.snapshotName = snapshotName; + } + public VMSnapshotTO getParent() { + return parent; + } + public void setParent(VMSnapshotTO parent) { + this.parent = parent; + } + +} diff --git a/api/src/com/cloud/agent/api/to/VolumeTO.java b/api/src/com/cloud/agent/api/to/VolumeTO.java index ff739c58f80..9c6e10b171c 100644 --- a/api/src/com/cloud/agent/api/to/VolumeTO.java +++ b/api/src/com/cloud/agent/api/to/VolumeTO.java @@ -123,6 +123,10 @@ public class VolumeTO implements InternalIdentity { public String getOsType() { return guestOsType; } + + public void setPath(String path){ + this.path = path; + } @Override public String toString() { diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 63b7cd0b32d..ffd0dd494e5 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -291,6 +291,11 @@ public class EventTypes { // tag related events public static final String EVENT_TAGS_CREATE = "CREATE_TAGS"; public static final String EVENT_TAGS_DELETE = "DELETE_TAGS"; + + // vm snapshot events + public static final String EVENT_VM_SNAPSHOT_CREATE = "VMSNAPSHOT.CREATE"; + public static final String EVENT_VM_SNAPSHOT_DELETE = "VMSNAPSHOT.DELETE"; + public static final String EVENT_VM_SNAPSHOT_REVERT = "VMSNAPSHOT.REVERT"; // external network device events public static final String EVENT_EXTERNAL_NVP_CONTROLLER_ADD = "PHYSICAL.NVPCONTROLLER.ADD"; diff --git a/api/src/com/cloud/server/ResourceTag.java b/api/src/com/cloud/server/ResourceTag.java index 5ec9f0171cc..ee56748640c 100644 --- a/api/src/com/cloud/server/ResourceTag.java +++ b/api/src/com/cloud/server/ResourceTag.java @@ -37,7 +37,8 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit Project, Vpc, NetworkACL, - StaticRoute + StaticRoute, + VMSnapshot } /** diff --git a/api/src/com/cloud/vm/VirtualMachine.java b/api/src/com/cloud/vm/VirtualMachine.java index cd305795478..d7df268b994 100755 --- a/api/src/com/cloud/vm/VirtualMachine.java +++ b/api/src/com/cloud/vm/VirtualMachine.java @@ -42,7 +42,11 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, Identity, I Migrating(true, "VM is being migrated. host id holds to from host"), Error(false, "VM is in error"), Unknown(false, "VM state is unknown."), - Shutdowned(false, "VM is shutdowned from inside"); + Shutdowned(false, "VM is shutdowned from inside"), + RunningSnapshotting(true, "VM is taking a snapshot in running state"), + StoppedSnapshotting(true, "VM is taking a snapshot in stopped state"), + RevertingToRunning(true, "VM is reverting to snapshot"), + RevertingToStopped(true, "VM is reverting to snapshot"); private final boolean _transitional; String _description; @@ -110,8 +114,24 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, Identity, I s_fsm.addTransition(State.Expunging, VirtualMachine.Event.ExpungeOperation, State.Expunging); s_fsm.addTransition(State.Error, VirtualMachine.Event.DestroyRequested, State.Expunging); s_fsm.addTransition(State.Error, VirtualMachine.Event.ExpungeOperation, State.Expunging); - } + + s_fsm.addTransition(State.Running, VirtualMachine.Event.SnapshotRequested, State.RunningSnapshotting); + s_fsm.addTransition(State.Stopped, VirtualMachine.Event.SnapshotRequested, State.StoppedSnapshotting); + s_fsm.addTransition(State.RunningSnapshotting, VirtualMachine.Event.OperationSucceeded, State.Running); + s_fsm.addTransition(State.StoppedSnapshotting, VirtualMachine.Event.OperationSucceeded, State.Stopped); + s_fsm.addTransition(State.RunningSnapshotting, VirtualMachine.Event.OperationFailed, State.Running); + s_fsm.addTransition(State.StoppedSnapshotting, VirtualMachine.Event.OperationFailed, State.Stopped); + + s_fsm.addTransition(State.Running, VirtualMachine.Event.RevertRequested, State.RevertingToRunning); + s_fsm.addTransition(State.Stopped, VirtualMachine.Event.RevertRequested, State.RevertingToStopped); + s_fsm.addTransition(State.RevertingToRunning, VirtualMachine.Event.OperationFailed, State.Running); + s_fsm.addTransition(State.RevertingToStopped, VirtualMachine.Event.OperationFailed, State.Stopped); + s_fsm.addTransition(State.RevertingToRunning, VirtualMachine.Event.OperationSucceeded, State.Running); + s_fsm.addTransition(State.RevertingToStopped, VirtualMachine.Event.OperationSucceeded, State.Stopped); + + } + public static boolean isVmStarted(State oldState, Event e, State newState) { if (oldState == State.Starting && newState == State.Running) { return true; @@ -173,7 +193,9 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, Identity, I OperationFailedToError, OperationRetry, AgentReportShutdowned, - AgentReportMigrated + AgentReportMigrated, + RevertRequested, + SnapshotRequested }; public enum Type { diff --git a/api/src/com/cloud/vm/snapshot/VMSnapshot.java b/api/src/com/cloud/vm/snapshot/VMSnapshot.java new file mode 100644 index 00000000000..7d08a2e7c73 --- /dev/null +++ b/api/src/com/cloud/vm/snapshot/VMSnapshot.java @@ -0,0 +1,109 @@ +// 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.vm.snapshot; + +import java.util.Date; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import org.apache.cloudstack.acl.ControlledEntity; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.utils.fsm.StateObject; + +public interface VMSnapshot extends ControlledEntity, Identity, InternalIdentity,StateObject { + + enum State { + Allocated("The VM snapshot is allocated but has not been created yet."), + Creating("The VM snapshot is being created."), + Ready("The VM snapshot is ready to be used."), + Reverting("The VM snapshot is being used to revert"), + Expunging("The volume is being expunging"), + Removed("The volume is destroyed, and can't be recovered."), + Error ("The volume is in error state, and can't be recovered"); + + String _description; + + private State(String description) { + _description = description; + } + + public static StateMachine2 getStateMachine() { + return s_fsm; + } + + public String getDescription() { + return _description; + } + + private final static StateMachine2 s_fsm = new StateMachine2(); + static { + s_fsm.addTransition(Allocated, Event.CreateRequested, Creating); + s_fsm.addTransition(Creating, Event.OperationSucceeded, Ready); + s_fsm.addTransition(Creating, Event.OperationFailed, Error); + s_fsm.addTransition(Ready, Event.RevertRequested, Reverting); + s_fsm.addTransition(Reverting, Event.OperationSucceeded, Ready); + s_fsm.addTransition(Reverting, Event.OperationFailed, Ready); + s_fsm.addTransition(Ready, Event.ExpungeRequested, Expunging); + s_fsm.addTransition(Error, Event.ExpungeRequested, Expunging); + s_fsm.addTransition(Expunging, Event.OperationSucceeded, Removed); + } + } + + enum Type{ + Disk, DiskAndMemory + } + + enum Event { + CreateRequested, + OperationFailed, + OperationSucceeded, + RevertRequested, + ExpungeRequested, + } + + long getId(); + + public String getName(); + + public Long getVmId(); + + public State getState(); + + public Date getCreated(); + + public String getDescription(); + + public String getDisplayName(); + + public Long getParent(); + + public Boolean getCurrent(); + + public Type getType(); + + public long getUpdatedCount(); + + public void incrUpdatedCount(); + + public Date getUpdated(); + + public Date getRemoved(); + + public long getAccountId(); +} diff --git a/api/src/com/cloud/vm/snapshot/VMSnapshotService.java b/api/src/com/cloud/vm/snapshot/VMSnapshotService.java new file mode 100644 index 00000000000..a1909fad950 --- /dev/null +++ b/api/src/com/cloud/vm/snapshot/VMSnapshotService.java @@ -0,0 +1,51 @@ +// 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.vm.snapshot; + +import java.util.List; + +import org.apache.cloudstack.api.command.user.vmsnapshot.CreateVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.DeleteVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToSnapshotCmd; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.uservm.UserVm; +import com.cloud.vm.VirtualMachine; + +public interface VMSnapshotService { + + List listVMSnapshots(ListVMSnapshotCmd cmd); + + VMSnapshot getVMSnapshotById(long id); + + VMSnapshot creatVMSnapshot(CreateVMSnapshotCmd cmd); + + VMSnapshot allocVMSnapshot(CreateVMSnapshotCmd cmd) + throws ResourceAllocationException; + + boolean deleteVMSnapshot(DeleteVMSnapshotCmd cmd); + + UserVm revertToSnapshot(RevertToSnapshotCmd cmd) throws InsufficientServerCapacityException, InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException; + + VirtualMachine getVMBySnapshotId(Long id); +} diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index d084271ca7c..7e8b2c0bf63 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -428,6 +428,11 @@ public class ApiConstants { public static final String COUNTERPARAM_LIST = "counterparam"; public static final String AUTOSCALE_USER_ID = "autoscaleuserid"; public static final String BAREMETAL_DISCOVER_NAME = "baremetaldiscovername"; + public static final String VM_SNAPSHOT_DESCRIPTION = "description"; + public static final String VM_SNAPSHOT_DISPLAYNAME = "name"; + public static final String VM_SNAPSHOT_ID = "vmsnapshotid"; + public static final String VM_SNAPSHOT_DISK_IDS = "vmsnapshotdiskids"; + public static final String VM_SNAPSHOT_MEMORY = "snapshotmemory"; public enum HostDetails { all, capacity, events, stats, min; diff --git a/api/src/org/apache/cloudstack/api/BaseCmd.java b/api/src/org/apache/cloudstack/api/BaseCmd.java index ef72af9cae9..0952397db7b 100644 --- a/api/src/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/org/apache/cloudstack/api/BaseCmd.java @@ -67,6 +67,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentLocator; import com.cloud.vm.BareMetalVmService; import com.cloud.vm.UserVmService; +import com.cloud.vm.snapshot.VMSnapshotService; public abstract class BaseCmd { private static final Logger s_logger = Logger.getLogger(BaseCmd.class.getName()); @@ -122,6 +123,7 @@ public abstract class BaseCmd { public static VpcService _vpcService; public static NetworkACLService _networkACLService; public static Site2SiteVpnService _s2sVpnService; + public static VMSnapshotService _vmSnapshotService; public static QueryService _queryService; @@ -157,6 +159,7 @@ public abstract class BaseCmd { _networkACLService = locator.getManager(NetworkACLService.class); _s2sVpnService = locator.getManager(Site2SiteVpnService.class); _queryService = locator.getManager(QueryService.class); + _vmSnapshotService = locator.getManager(VMSnapshotService.class); } public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException; diff --git a/api/src/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/org/apache/cloudstack/api/ResponseGenerator.java index 63df4dc5532..aff693f9059 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -64,6 +64,7 @@ import org.apache.cloudstack.api.response.RemoteAccessVpnResponse; import org.apache.cloudstack.api.response.ResourceCountResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; +import org.apache.cloudstack.api.response.S3Response; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceResponse; @@ -84,6 +85,7 @@ import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.TrafficTypeResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VMSnapshotResponse; import org.apache.cloudstack.api.response.VirtualRouterProviderResponse; import org.apache.cloudstack.api.response.VlanIpRangeResponse; import org.apache.cloudstack.api.response.VolumeResponse; @@ -92,8 +94,6 @@ import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.api.response.S3Response; - import com.cloud.async.AsyncJob; import com.cloud.capacity.Capacity; import com.cloud.configuration.Configuration; @@ -159,6 +159,7 @@ import com.cloud.user.UserAccount; import com.cloud.uservm.UserVm; import com.cloud.vm.InstanceGroup; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.snapshot.VMSnapshot; public interface ResponseGenerator { UserResponse createUserResponse(UserAccount user); @@ -372,4 +373,6 @@ public interface ResponseGenerator { GuestOSResponse createGuestOSResponse(GuestOS os); SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule sched); + + VMSnapshotResponse createVMSnapshotResponse(VMSnapshot vmSnapshot); } 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 new file mode 100644 index 00000000000..7ded7165f41 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java @@ -0,0 +1,125 @@ +// 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.vmsnapshot; + +import java.util.logging.Logger; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VMSnapshotResponse; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import com.cloud.vm.snapshot.VMSnapshot; + +@APICommand(name = "createVMSnapshot", description = "Creates snapshot for a vm.", responseObject = VMSnapshotResponse.class, since="4.1.0") +public class CreateVMSnapshotCmd extends BaseAsyncCreateCmd { + + public static final Logger s_logger = Logger + .getLogger(CreateVMSnapshotCmd.class.getName()); + private static final String s_name = "createvmsnapshotresponse"; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, required = true, entityType=UserVmResponse.class, description = "The ID of the vm") + private Long vmId; + + @Parameter(name = ApiConstants.VM_SNAPSHOT_DESCRIPTION, type = CommandType.STRING, required = false, description = "The discription of the snapshot") + private String description; + + @Parameter(name = ApiConstants.VM_SNAPSHOT_DISPLAYNAME, type = CommandType.STRING, required = false, description = "The display name of the snapshot") + private String displayName; + + @Parameter(name = ApiConstants.VM_SNAPSHOT_MEMORY, type = CommandType.BOOLEAN, required = false, description = "snapshot memory if true") + private Boolean snapshotMemory; + + public Boolean snapshotMemory() { + if (snapshotMemory == null) { + return false; + } else { + return snapshotMemory; + } + } + + public String getDisplayName() { + return displayName; + } + + public String getDescription() { + return description; + } + + public Long getVmId() { + return vmId; + } + + @Override + public void create() throws ResourceAllocationException { + VMSnapshot vmsnapshot = _vmSnapshotService.allocVMSnapshot(this); + if (vmsnapshot != null) { + this.setEntityId(vmsnapshot.getId()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create vm snapshot"); + } + } + + @Override + public String getEventDescription() { + return "creating snapshot for vm: " + getVmId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_SNAPSHOT_CREATE; + } + + @Override + public void execute() { + UserContext.current().setEventDetails("VM Id: " + getVmId()); + VMSnapshot result = _vmSnapshotService.creatVMSnapshot(this); + if (result != null) { + VMSnapshotResponse response = _responseGenerator + .createVMSnapshotResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException( + ApiErrorCode.INTERNAL_ERROR, + "Failed to create vm snapshot due to an internal error creating snapshot for vm " + + getVmId()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + UserVm userVM = _userVmService.getUserVm(vmId); + return userVM.getAccountId(); + } + +} diff --git a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java new file mode 100644 index 00000000000..592bc6646e9 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java @@ -0,0 +1,99 @@ +// 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.vmsnapshot; + +import org.apache.log4j.Logger; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseCmd.CommandType; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; + +import org.apache.cloudstack.api.response.VMSnapshotResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.snapshot.VMSnapshot; +//import com.cloud.api.ApiDBUtils; + +@APICommand(name="deleteVMSnapshot", description = "Deletes a vmsnapshot.", responseObject = SuccessResponse.class) +public class DeleteVMSnapshotCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger + .getLogger(DeleteVMSnapshotCmd.class.getName()); + private static final String s_name = "deletevmsnapshotresponse"; + + @Parameter(name=ApiConstants.VM_SNAPSHOT_ID, type=CommandType.UUID, entityType=VMSnapshotResponse.class, + required=true, description="The ID of the VM snapshot") + private Long id; + + public Long getId() { + return id; + } + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "vm_snapshots"; + } + + @Override + public long getEntityOwnerId() { + VMSnapshot vmSnapshot = _entityMgr.findById(VMSnapshot.class, getId()); + if (vmSnapshot != null) { + return vmSnapshot.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + UserContext.current().setEventDetails("vmsnapshot id: " + getId()); + boolean result = _vmSnapshotService.deleteVMSnapshot(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete vm snapshot"); + } + } + + @Override + public String getEventDescription() { + return "Delete VM snapshot: " + getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_SNAPSHOT_DELETE; + } + +} diff --git a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/ListVMSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/ListVMSnapshotCmd.java new file mode 100644 index 00000000000..8b184847792 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/ListVMSnapshotCmd.java @@ -0,0 +1,105 @@ +// 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.vmsnapshot; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListTaggedResourcesCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VMSnapshotResponse; + +import com.cloud.vm.snapshot.VMSnapshot; + +@APICommand(name="listVMSnapshot", description = "List virtual machine snapshot by conditions", responseObject = VMSnapshotResponse.class, since = "4.1.0") +public class ListVMSnapshotCmd extends BaseListTaggedResourcesCmd { + public static final Logger s_logger = Logger.getLogger(ListHostsCmd.class + .getName()); + + private static final String s_name = "listvmsnapshotresponse"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.VM_SNAPSHOT_ID, type=CommandType.UUID, entityType=VMSnapshotResponse.class, + description="The ID of the VM snapshot") + private Long id; + + @Parameter(name=ApiConstants.STATE, type=CommandType.STRING, description="state of the virtual machine snapshot") + private String state; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType=UserVmResponse.class, description = "the ID of the vm") + private Long vmId; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "lists snapshot by snapshot name or display name") + private String vmSnapshotName; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + public String getState() { + return state; + } + + public String getVmSnapshotName() { + return vmSnapshotName; + } + + public Long getVmId() { + return vmId; + } + + public Long getId() { + return id; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public void execute() { + List result = _vmSnapshotService + .listVMSnapshots(this); + ListResponse response = new ListResponse(); + List snapshotResponses = new ArrayList(); + for (VMSnapshot r : result) { + VMSnapshotResponse vmSnapshotResponse = _responseGenerator + .createVMSnapshotResponse(r); + vmSnapshotResponse.setObjectName("vmSnapshot"); + snapshotResponses.add(vmSnapshotResponse); + } + response.setResponses(snapshotResponses); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java new file mode 100644 index 00000000000..6642a859c06 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java @@ -0,0 +1,98 @@ +// 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.vmsnapshot; + +import java.util.logging.Logger; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VMSnapshotResponse; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import com.cloud.vm.snapshot.VMSnapshot; + +@APICommand(name = "revertToSnapshot",description = "Revert VM from a vmsnapshot.", responseObject = UserVmResponse.class) +public class RevertToSnapshotCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger + .getLogger(RevertToSnapshotCmd.class.getName()); + private static final String s_name = "reverttosnapshotresponse"; + + @Parameter(name = ApiConstants.VM_SNAPSHOT_ID, type = CommandType.UUID, required = true,entityType=VMSnapshotResponse.class,description = "The ID of the vm snapshot") + private Long vmSnapShotId; + + public Long getVmSnapShotId() { + return vmSnapShotId; + } + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "vm_snapshots"; + } + + @Override + public long getEntityOwnerId() { + VMSnapshot vmSnapshot = _vmSnapshotService.getVMSnapshotById(getVmSnapShotId()); + if (vmSnapshot == null) { + throw new InvalidParameterValueException( + "Unable to find the snapshot by id=" + getVmSnapShotId()); + } + UserVm userVM = _userVmService.getUserVm(vmSnapshot.getVmId()); + return userVM.getAccountId(); + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, ConcurrentOperationException { + UserContext.current().setEventDetails( + "vmsnapshot id: " + getVmSnapShotId()); + UserVm result = _vmSnapshotService.revertToSnapshot(this); + if (result != null) { + UserVmResponse response = _responseGenerator.createUserVmResponse( + "virtualmachine", result).get(0); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,"Failed to revert vm snapshot"); + } + } + + @Override + public String getEventDescription() { + return "Revert from VM snapshot: " + getVmSnapShotId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_SNAPSHOT_REVERT; + } + +} diff --git a/api/src/org/apache/cloudstack/api/response/VMSnapshotResponse.java b/api/src/org/apache/cloudstack/api/response/VMSnapshotResponse.java new file mode 100644 index 00000000000..3b30ab61a8f --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/VMSnapshotResponse.java @@ -0,0 +1,220 @@ +// 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.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.serializer.Param; +import com.cloud.vm.snapshot.VMSnapshot; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value=VMSnapshot.class) +public class VMSnapshotResponse extends BaseResponse implements ControlledEntityResponse{ + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the vm snapshot") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the vm snapshot") + private String name; + + @SerializedName(ApiConstants.STATE) + @Param(description = "the state of the vm snapshot") + private VMSnapshot.State state; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the vm snapshot") + private String description; + + @SerializedName(ApiConstants.DISPLAY_NAME) + @Param(description = "the display name of the vm snapshot") + private String displayName; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "the Zone ID of the vm snapshot") + private String zoneId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "the vm ID of the vm snapshot") + private String virtualMachineid; + + @SerializedName("parent") + @Param(description = "the parent ID of the vm snapshot") + private String parent; + + @SerializedName("parentName") + @Param(description = "the parent displayName of the vm snapshot") + private String parentName; + + @SerializedName("current") + @Param(description = "indiates if this is current snapshot") + private Boolean current; + + @SerializedName("type") + @Param(description = "VM Snapshot type") + private String type; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the create date of the vm snapshot") + private Date created; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account associated with the disk volume") + private String accountName; + + @SerializedName(ApiConstants.PROJECT_ID) @Param(description="the project id of the vpn") + private String projectId; + + @SerializedName(ApiConstants.PROJECT) @Param(description="the project name of the vpn") + private String projectName; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the ID of the domain associated with the disk volume") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the domain associated with the disk volume") + private String domainName; + + @Override + public String getObjectId() { + return getId(); + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getVirtualMachineid() { + return virtualMachineid; + } + + public void setVirtualMachineid(String virtualMachineid) { + this.virtualMachineid = virtualMachineid; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setState(VMSnapshot.State state) { + this.state = state; + } + + public VMSnapshot.State getState() { + return state; + } + + public Boolean getCurrent() { + return current; + } + + public void setCurrent(Boolean current) { + this.current = current; + } + + public void setParentName(String parentName) { + this.parentName = parentName; + } + + public String getParentName() { + return parentName; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public void setAccountName(String accountName) { + this.accountName = accountName; + + } + + @Override + public void setProjectId(String projectId) { + this.projectId = projectId; + + } + + @Override + public void setProjectName(String projectName) { + this.projectName = projectName; + + } + + @Override + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + @Override + public void setDomainName(String domainName) { + this.domainName = domainName; + + } +} diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index 390738fd4cf..f25dcb78e5f 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -1408,6 +1408,20 @@ label.zone.step.4.title=Step 4: Add an IP range label.zone.wide=Zone-Wide label.zone=Zone +#VM snapshot label +label.vmsnapshot=VM Snapshots +label.vmsnapshot.type=Type +label.vmsnapshot.parentname=Parent +label.vmsnapshot.current=isCurrent +label.vmsnapshot.memory=Snapshot memory +message.action.vmsnapshot.delete=Please confirm that you want to delete this VM snapshot. +label.action.vmsnapshot.delete=Delete VM snapshot +label.action.vmsnapshot.revert=Revert to VM snapshot +message.action.vmsnapshot.revert=Revert VM snapshot +label.action.vmsnapshot.create=Take VM Snapshot + + + #Messages message.acquire.public.ip=Please select a zone from which you want to acquire your new IP from. message.action.cancel.maintenance.mode=Please confirm that you want to cancel this maintenance. diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 3740fb00633..c91b1ed3d2a 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -514,3 +514,9 @@ listApis=15 getApiLimit=15 resetApiLimit=1 + +### VM Snapshot commands +listVMSnapshot=15 +createVMSnapshot=15 +deleteVMSnapshot=15 +revertToSnapshot=15 diff --git a/core/src/com/cloud/vm/snapshot/VMSnapshotVO.java b/core/src/com/cloud/vm/snapshot/VMSnapshotVO.java new file mode 100644 index 00000000000..03d4945fda0 --- /dev/null +++ b/core/src/com/cloud/vm/snapshot/VMSnapshotVO.java @@ -0,0 +1,224 @@ +// 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.vm.snapshot; + +import java.util.Date; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.TableGenerator; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import com.cloud.utils.db.GenericDao; + +@Entity +@Table(name = "vm_snapshots") +public class VMSnapshotVO implements VMSnapshot { + @Id + @TableGenerator(name = "vm_snapshots_sq", table = "sequence", pkColumnName = "name", valueColumnName = "value", pkColumnValue = "vm_snapshots_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.TABLE) + @Column(name = "id") + long id; + + @Column(name = "uuid") + String uuid = UUID.randomUUID().toString(); + + @Column(name = "name") + String name; + + @Column(name = "display_name") + String displayName; + + @Column(name = "description") + String description; + + @Column(name = "vm_id") + long vmId; + + @Column(name = "account_id") + long accountId; + + @Column(name = "domain_id") + long domainId; + + @Column(name = "vm_snapshot_type") + @Enumerated(EnumType.STRING) + VMSnapshot.Type type; + + @Column(name = "state", updatable = true, nullable = false) + @Enumerated(value = EnumType.STRING) + private State state; + + @Column(name = GenericDao.CREATED_COLUMN) + Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + Date removed; + + @Column(name = "current") + Boolean current; + + @Column(name = "parent") + Long parent; + + @Column(name = "updated") + @Temporal(value = TemporalType.TIMESTAMP) + Date updated; + + @Column(name="update_count", updatable = true, nullable=false) + protected long updatedCount; + + public Long getParent() { + return parent; + } + + public void setParent(Long parent) { + this.parent = parent; + } + + public VMSnapshotVO() { + + } + + public Date getRemoved() { + return removed; + } + + public VMSnapshotVO(Long accountId, Long domainId, Long vmId, + String description, String vmSnapshotName, String vsDisplayName, + Long serviceOfferingId, Type type, Boolean current) { + this.accountId = accountId; + this.domainId = domainId; + this.vmId = vmId; + this.state = State.Allocated; + this.description = description; + this.name = vmSnapshotName; + this.displayName = vsDisplayName; + this.type = type; + this.current = current; + } + + public String getDescription() { + return description; + } + + @Override + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + @Override + public long getId() { + return id; + } + + @Override + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public long getAccountId() { + return accountId; + } + + @Override + public long getDomainId() { + return domainId; + } + + @Override + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public Boolean getCurrent() { + return current; + } + + public void setCurrent(Boolean current) { + this.current = current; + } + + @Override + public long getUpdatedCount() { + return updatedCount; + } + + @Override + public void incrUpdatedCount() { + this.updatedCount++; + } + + @Override + public Date getUpdated() { + return updated; + } + + @Override + public Type getType() { + return type; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } +} diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java index f27e0269a64..a2e517d1fdb 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java @@ -20,15 +20,21 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.BackupSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; +import com.cloud.agent.api.CreateVMSnapshotCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; +import com.cloud.agent.api.DeleteVMSnapshotCommand; +import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; public interface VmwareStorageManager { Answer execute(VmwareHostService hostService, PrimaryStorageDownloadCommand cmd); - Answer execute(VmwareHostService hostService, BackupSnapshotCommand cmd); - Answer execute(VmwareHostService hostService, CreatePrivateTemplateFromVolumeCommand cmd); - Answer execute(VmwareHostService hostService, CreatePrivateTemplateFromSnapshotCommand cmd); - Answer execute(VmwareHostService hostService, CopyVolumeCommand cmd); - Answer execute(VmwareHostService hostService, CreateVolumeFromSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, BackupSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, CreatePrivateTemplateFromVolumeCommand cmd); + Answer execute(VmwareHostService hostService, CreatePrivateTemplateFromSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, CopyVolumeCommand cmd); + Answer execute(VmwareHostService hostService, CreateVolumeFromSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, CreateVMSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, DeleteVMSnapshotCommand cmd); + Answer execute(VmwareHostService hostService, RevertToVMSnapshotCommand cmd); } diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java index 8650274719e..c7b7cd3bcba 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java @@ -22,6 +22,7 @@ import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.rmi.RemoteException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -32,18 +33,27 @@ import com.cloud.agent.api.BackupSnapshotAnswer; import com.cloud.agent.api.BackupSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; +import com.cloud.agent.api.CreateVMSnapshotAnswer; +import com.cloud.agent.api.CreateVMSnapshotCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotAnswer; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; +import com.cloud.agent.api.DeleteVMSnapshotAnswer; +import com.cloud.agent.api.DeleteVMSnapshotCommand; +import com.cloud.agent.api.RevertToVMSnapshotAnswer; +import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.agent.api.to.VolumeTO; import com.cloud.hypervisor.vmware.mo.CustomFieldConstants; import com.cloud.hypervisor.vmware.mo.DatacenterMO; import com.cloud.hypervisor.vmware.mo.DatastoreMO; +import com.cloud.hypervisor.vmware.mo.HostMO; import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper; +import com.cloud.hypervisor.vmware.mo.TaskMO; import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHost; import com.cloud.hypervisor.vmware.util.VmwareContext; @@ -57,7 +67,11 @@ import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; import com.cloud.utils.Ternary; import com.cloud.utils.script.Script; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.snapshot.VMSnapshot; import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.TaskEvent; +import com.vmware.vim25.TaskInfo; import com.vmware.vim25.VirtualDeviceConfigSpec; import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualDisk; @@ -222,8 +236,12 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } } finally { - if(vmMo != null) - vmMo.removeAllSnapshots(); + if(vmMo != null){ + ManagedObjectReference snapshotMor = vmMo.getSnapshotMor(snapshotUuid); + if (snapshotMor != null){ + vmMo.removeSnapshot(snapshotUuid, false); + } + } try { if (workerVm != null) { @@ -377,47 +395,47 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } @Override - public Answer execute(VmwareHostService hostService, CreateVolumeFromSnapshotCommand cmd) { + public Answer execute(VmwareHostService hostService, CreateVolumeFromSnapshotCommand cmd) { - String primaryStorageNameLabel = cmd.getPrimaryStoragePoolNameLabel(); - Long accountId = cmd.getAccountId(); - Long volumeId = cmd.getVolumeId(); - String secondaryStorageUrl = cmd.getSecondaryStorageUrl(); - String backedUpSnapshotUuid = cmd.getSnapshotUuid(); + String primaryStorageNameLabel = cmd.getPrimaryStoragePoolNameLabel(); + Long accountId = cmd.getAccountId(); + Long volumeId = cmd.getVolumeId(); + String secondaryStorageUrl = cmd.getSecondaryStorageUrl(); + String backedUpSnapshotUuid = cmd.getSnapshotUuid(); - String details = null; - boolean success = false; - String newVolumeName = UUID.randomUUID().toString().replaceAll("-", ""); + String details = null; + boolean success = false; + String newVolumeName = UUID.randomUUID().toString().replaceAll("-", ""); - VmwareContext context = hostService.getServiceContext(cmd); - try { - VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); - - ManagedObjectReference morPrimaryDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, primaryStorageNameLabel); - if (morPrimaryDs == null) { - String msg = "Unable to find datastore: " + primaryStorageNameLabel; - s_logger.error(msg); - throw new Exception(msg); - } + VmwareContext context = hostService.getServiceContext(cmd); + try { + VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); + ManagedObjectReference morPrimaryDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, + primaryStorageNameLabel); + if (morPrimaryDs == null) { + String msg = "Unable to find datastore: " + primaryStorageNameLabel; + s_logger.error(msg); + throw new Exception(msg); + } - DatastoreMO primaryDsMo = new DatastoreMO(hyperHost.getContext(), morPrimaryDs); - details = createVolumeFromSnapshot(hyperHost, primaryDsMo, - newVolumeName, accountId, volumeId, secondaryStorageUrl, backedUpSnapshotUuid); - if (details == null) { - success = true; - } - } catch (Throwable e) { - if (e instanceof RemoteException) { - hostService.invalidateServiceContext(context); - } + DatastoreMO primaryDsMo = new DatastoreMO(hyperHost.getContext(), morPrimaryDs); + details = createVolumeFromSnapshot(hyperHost, primaryDsMo, + newVolumeName, accountId, volumeId, secondaryStorageUrl, backedUpSnapshotUuid); + if (details == null) { + success = true; + } + } catch (Throwable e) { + if (e instanceof RemoteException) { + hostService.invalidateServiceContext(context); + } + + s_logger.error("Unexpecpted exception ", e); + details = "CreateVolumeFromSnapshotCommand exception: " + StringUtils.getExceptionStackInfo(e); + } - s_logger.error("Unexpecpted exception ", e); - details = "CreateVolumeFromSnapshotCommand exception: " + StringUtils.getExceptionStackInfo(e); - } + return new CreateVolumeFromSnapshotAnswer(cmd, success, details, newVolumeName); + } - return new CreateVolumeFromSnapshotAnswer(cmd, success, details, newVolumeName); - } - // templateName: name in secondary storage // templateUuid: will be used at hypervisor layer private void copyTemplateFromSecondaryToPrimary(VmwareHypervisorHost hyperHost, DatastoreMO datastoreMo, String secondaryStorageUrl, @@ -881,4 +899,244 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { private static String getSnapshotRelativeDirInSecStorage(long accountId, long volumeId) { return "snapshots/" + accountId + "/" + volumeId; } + + @Override + public CreateVMSnapshotAnswer execute(VmwareHostService hostService, CreateVMSnapshotCommand cmd) { + List volumeTOs = cmd.getVolumeTOs(); + String vmName = cmd.getVmName(); + String vmSnapshotName = cmd.getTarget().getSnapshotName(); + String vmSnapshotDesc = cmd.getTarget().getDescription(); + boolean snapshotMemory = cmd.getTarget().getType() == VMSnapshot.Type.DiskAndMemory; + VirtualMachineMO vmMo = null; + VmwareContext context = hostService.getServiceContext(cmd); + Map mapNewDisk = new HashMap(); + try { + VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); + + // wait if there are already VM snapshot task running + ManagedObjectReference taskmgr = context.getServiceContent().getTaskManager(); + ManagedObjectReference[] tasks = (ManagedObjectReference[]) context.getServiceUtil().getDynamicProperty(taskmgr, "recentTask"); + for (ManagedObjectReference taskMor : tasks) { + TaskInfo info = (TaskInfo) (context.getServiceUtil().getDynamicProperty(taskMor, "info")); + if(info.getEntityName().equals(cmd.getVmName()) && info.getName().equalsIgnoreCase("CreateSnapshot_Task")){ + s_logger.debug("There is already a VM snapshot task running, wait for it"); + context.getServiceUtil().waitForTask(taskMor); + } + } + + vmMo = hyperHost.findVmOnHyperHost(vmName); + if(vmMo == null) + vmMo = hyperHost.findVmOnPeerHyperHost(vmName); + if (vmMo == null) { + String msg = "Unable to find VM for CreateVMSnapshotCommand"; + s_logger.debug(msg); + return new CreateVMSnapshotAnswer(cmd, false, msg); + } else { + if (vmMo.getSnapshotMor(vmSnapshotName) != null){ + s_logger.debug("VM snapshot " + vmSnapshotName + " already exists"); + }else if (!vmMo.createSnapshot(vmSnapshotName, vmSnapshotDesc, snapshotMemory, true)) { + return new CreateVMSnapshotAnswer(cmd, false, + "Unable to create snapshot due to esxi internal failed"); + } + // find VM disk file path after creating snapshot + VirtualDisk[] vdisks = vmMo.getAllDiskDevice(); + for (int i = 0; i < vdisks.length; i ++){ + @SuppressWarnings("deprecation") + List> vmdkFiles = vmMo.getDiskDatastorePathChain(vdisks[i], false); + for(Pair fileItem : vmdkFiles) { + String vmdkName = fileItem.first().split(" ")[1]; + if ( vmdkName.endsWith(".vmdk")){ + vmdkName = vmdkName.substring(0, vmdkName.length() - (".vmdk").length()); + } + String[] s = vmdkName.split("-"); + mapNewDisk.put(s[0], vmdkName); + } + } + + // update volume path using maps + for (VolumeTO volumeTO : volumeTOs) { + String parentUUID = volumeTO.getPath(); + String[] s = parentUUID.split("-"); + String key = s[0]; + volumeTO.setPath(mapNewDisk.get(key)); + } + return new CreateVMSnapshotAnswer(cmd, cmd.getTarget(), volumeTOs); + } + } catch (Exception e) { + String msg = e.getMessage(); + s_logger.error("failed to create snapshot for vm:" + vmName + " due to " + msg); + try { + if (vmMo.getSnapshotMor(vmSnapshotName) != null) { + vmMo.removeSnapshot(vmSnapshotName, false); + } + } catch (Exception e1) { + } + return new CreateVMSnapshotAnswer(cmd, false, e.getMessage()); + } + } + + @Override + public DeleteVMSnapshotAnswer execute(VmwareHostService hostService, DeleteVMSnapshotCommand cmd) { + List listVolumeTo = cmd.getVolumeTOs(); + VirtualMachineMO vmMo = null; + VmwareContext context = hostService.getServiceContext(cmd); + Map mapNewDisk = new HashMap(); + String vmName = cmd.getVmName(); + String vmSnapshotName = cmd.getTarget().getSnapshotName(); + try { + VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); + vmMo = hyperHost.findVmOnHyperHost(vmName); + if(vmMo == null) + vmMo = hyperHost.findVmOnPeerHyperHost(vmName); + if (vmMo == null) { + String msg = "Unable to find VM for RevertToVMSnapshotCommand"; + s_logger.debug(msg); + return new DeleteVMSnapshotAnswer(cmd, false, msg); + } else { + if (vmMo.getSnapshotMor(vmSnapshotName) == null) { + s_logger.debug("can not find the snapshot " + vmSnapshotName + ", assume it is already removed"); + } else { + if (!vmMo.removeSnapshot(vmSnapshotName, false)) { + String msg = "delete vm snapshot " + vmSnapshotName + " due to error occured in vmware"; + s_logger.error(msg); + return new DeleteVMSnapshotAnswer(cmd, false, msg); + } + } + s_logger.debug("snapshot: " + vmSnapshotName + " is removed"); + // after removed snapshot, the volumes' paths have been changed for the VM, needs to report new paths to manager + VirtualDisk[] vdisks = vmMo.getAllDiskDevice(); + for (int i = 0; i < vdisks.length; i++) { + @SuppressWarnings("deprecation") + List> vmdkFiles = vmMo.getDiskDatastorePathChain(vdisks[i], false); + for (Pair fileItem : vmdkFiles) { + String vmdkName = fileItem.first().split(" ")[1]; + if (vmdkName.endsWith(".vmdk")) { + vmdkName = vmdkName.substring(0, vmdkName.length() - (".vmdk").length()); + } + String[] s = vmdkName.split("-"); + mapNewDisk.put(s[0], vmdkName); + } + } + for (VolumeTO volumeTo : listVolumeTo) { + String key = null; + String parentUUID = volumeTo.getPath(); + String[] s = parentUUID.split("-"); + key = s[0]; + volumeTo.setPath(mapNewDisk.get(key)); + } + return new DeleteVMSnapshotAnswer(cmd, listVolumeTo); + } + } catch (Exception e) { + String msg = e.getMessage(); + s_logger.error("failed to delete vm snapshot " + vmSnapshotName + " of vm " + vmName + " due to " + msg); + return new DeleteVMSnapshotAnswer(cmd, false, msg); + } + } + + @Override + public RevertToVMSnapshotAnswer execute(VmwareHostService hostService, RevertToVMSnapshotCommand cmd) { + String snapshotName = cmd.getTarget().getSnapshotName(); + String vmName = cmd.getVmName(); + Boolean snapshotMemory = cmd.getTarget().getType() == VMSnapshot.Type.DiskAndMemory; + List listVolumeTo = cmd.getVolumeTOs(); + VirtualMachine.State vmState = VirtualMachine.State.Running; + VirtualMachineMO vmMo = null; + VmwareContext context = hostService.getServiceContext(cmd); + Map mapNewDisk = new HashMap(); + try { + VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, cmd); + + // wait if there are already VM revert task running + ManagedObjectReference taskmgr = context.getServiceContent().getTaskManager(); + ManagedObjectReference[] tasks = (ManagedObjectReference[]) context.getServiceUtil().getDynamicProperty(taskmgr, "recentTask"); + for (ManagedObjectReference taskMor : tasks) { + TaskInfo info = (TaskInfo) (context.getServiceUtil().getDynamicProperty(taskMor, "info")); + if(info.getEntityName().equals(cmd.getVmName()) && info.getName().equalsIgnoreCase("RevertToSnapshot_Task")){ + s_logger.debug("There is already a VM snapshot task running, wait for it"); + context.getServiceUtil().waitForTask(taskMor); + } + } + + HostMO hostMo = (HostMO) hyperHost; + vmMo = hyperHost.findVmOnHyperHost(vmName); + if(vmMo == null) + vmMo = hyperHost.findVmOnPeerHyperHost(vmName); + if (vmMo == null) { + String msg = "Unable to find VM for RevertToVMSnapshotCommand"; + s_logger.debug(msg); + return new RevertToVMSnapshotAnswer(cmd, false, msg); + } else { + boolean result = false; + if (snapshotName != null) { + ManagedObjectReference morSnapshot = vmMo.getSnapshotMor(snapshotName); + result = hostMo.revertToSnapshot(morSnapshot); + } else { + return new RevertToVMSnapshotAnswer(cmd, false, "Unable to find the snapshot by name " + snapshotName); + } + + if (result) { + VirtualDisk[] vdisks = vmMo.getAllDiskDevice(); + // build a map + for (int i = 0; i < vdisks.length; i++) { + @SuppressWarnings("deprecation") + List> vmdkFiles = vmMo.getDiskDatastorePathChain( + vdisks[i], false); + for (Pair fileItem : vmdkFiles) { + String vmdkName = fileItem.first().split(" ")[1]; + if (vmdkName.endsWith(".vmdk")) { + vmdkName = vmdkName.substring(0, vmdkName.length() - (".vmdk").length()); + } + String[] s = vmdkName.split("-"); + mapNewDisk.put(s[0], vmdkName); + } + } + String key = null; + for (VolumeTO volumeTo : listVolumeTo) { + String parentUUID = volumeTo.getPath(); + String[] s = parentUUID.split("-"); + key = s[0]; + volumeTo.setPath(mapNewDisk.get(key)); + } + if (!snapshotMemory) { + vmState = VirtualMachine.State.Stopped; + } + return new RevertToVMSnapshotAnswer(cmd, listVolumeTo, vmState); + } else { + return new RevertToVMSnapshotAnswer(cmd, false, + "Error while reverting to snapshot due to execute in esxi"); + } + } + } catch (Exception e) { + String msg = "revert vm " + vmName + " to snapshot " + snapshotName + " failed due to " + e.getMessage(); + s_logger.error(msg); + return new RevertToVMSnapshotAnswer(cmd, false, msg); + } + } + + + private VirtualMachineMO createWorkingVM(DatastoreMO dsMo, VmwareHypervisorHost hyperHost) throws Exception { + String uniqueName = UUID.randomUUID().toString(); + VirtualMachineMO workingVM = null; + VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); + vmConfig.setName(uniqueName); + vmConfig.setMemoryMB((long) 4); + vmConfig.setNumCPUs(1); + vmConfig.setGuestId(VirtualMachineGuestOsIdentifier._otherGuest.toString()); + VirtualMachineFileInfo fileInfo = new VirtualMachineFileInfo(); + fileInfo.setVmPathName(String.format("[%s]", dsMo.getName())); + vmConfig.setFiles(fileInfo); + + VirtualLsiLogicController scsiController = new VirtualLsiLogicController(); + scsiController.setSharedBus(VirtualSCSISharing.noSharing); + scsiController.setBusNumber(0); + scsiController.setKey(1); + VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); + scsiControllerSpec.setDevice(scsiController); + scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.add); + + vmConfig.setDeviceChange(new VirtualDeviceConfigSpec[] { scsiControllerSpec }); + hyperHost.createVm(vmConfig); + workingVM = hyperHost.findVmOnHyperHost(uniqueName); + return workingVM; + } } diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index dd917f75a6e..5f0bd21d59b 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -65,9 +65,13 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateStoragePoolCommand; +import com.cloud.agent.api.CreateVMSnapshotAnswer; +import com.cloud.agent.api.CreateVMSnapshotCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotAnswer; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.agent.api.DeleteVMSnapshotAnswer; +import com.cloud.agent.api.DeleteVMSnapshotCommand; import com.cloud.agent.api.GetDomRVersionAnswer; import com.cloud.agent.api.GetDomRVersionCmd; import com.cloud.agent.api.GetHostStatsAnswer; @@ -103,6 +107,8 @@ import com.cloud.agent.api.ReadyCommand; import com.cloud.agent.api.RebootAnswer; import com.cloud.agent.api.RebootCommand; import com.cloud.agent.api.RebootRouterCommand; +import com.cloud.agent.api.RevertToVMSnapshotAnswer; +import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.SetupAnswer; import com.cloud.agent.api.SetupCommand; import com.cloud.agent.api.SetupGuestNetworkAnswer; @@ -138,8 +144,8 @@ import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesAnswer; import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesVpcCommand; -import com.cloud.agent.api.routing.SetSourceNatCommand; import com.cloud.agent.api.routing.SetSourceNatAnswer; +import com.cloud.agent.api.routing.SetSourceNatCommand; import com.cloud.agent.api.routing.SetStaticNatRulesAnswer; import com.cloud.agent.api.routing.SetStaticNatRulesCommand; import com.cloud.agent.api.routing.Site2SiteVpnCfgCommand; @@ -442,7 +448,13 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa answer = execute((SetSourceNatCommand) cmd); } else if (clz == SetNetworkACLCommand.class) { answer = execute((SetNetworkACLCommand) cmd); - } else if (clz == SetPortForwardingRulesVpcCommand.class) { + } else if (cmd instanceof CreateVMSnapshotCommand) { + return execute((CreateVMSnapshotCommand)cmd); + } else if(cmd instanceof DeleteVMSnapshotCommand){ + return execute((DeleteVMSnapshotCommand)cmd); + } else if(cmd instanceof RevertToVMSnapshotCommand){ + return execute((RevertToVMSnapshotCommand)cmd); + }else if (clz == SetPortForwardingRulesVpcCommand.class) { answer = execute((SetPortForwardingRulesVpcCommand) cmd); } else if (clz == Site2SiteVpnCfgCommand.class) { answer = execute((Site2SiteVpnCfgCommand) cmd); @@ -2718,7 +2730,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa // before we stop VM, remove all possible snapshots on the VM to let // disk chain be collapsed s_logger.info("Remove all snapshot before stopping VM " + cmd.getVmName()); - vmMo.removeAllSnapshots(); if (vmMo.safePowerOff(_shutdown_waitMs)) { state = State.Stopped; return new StopAnswer(cmd, "Stop VM " + cmd.getVmName() + " Succeed", 0, true); @@ -3270,7 +3281,42 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } } + protected Answer execute(CreateVMSnapshotCommand cmd) { + try { + VmwareContext context = getServiceContext(); + VmwareManager mgr = context + .getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + return mgr.getStorageManager().execute(this, cmd); + } catch (Exception e) { + e.printStackTrace(); + return new CreateVMSnapshotAnswer(cmd, false, ""); + } + } + + protected Answer execute(DeleteVMSnapshotCommand cmd) { + try { + VmwareContext context = getServiceContext(); + VmwareManager mgr = context + .getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + + return mgr.getStorageManager().execute(this, cmd); + } catch (Exception e) { + e.printStackTrace(); + return new DeleteVMSnapshotAnswer(cmd, false, ""); + } + } + + protected Answer execute(RevertToVMSnapshotCommand cmd){ + try{ + VmwareContext context = getServiceContext(); + VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + return mgr.getStorageManager().execute(this, cmd); + }catch (Exception e){ + e.printStackTrace(); + return new RevertToVMSnapshotAnswer(cmd,false,""); + } + } protected Answer execute(CreateVolumeFromSnapshotCommand cmd) { if (s_logger.isInfoEnabled()) { s_logger.info("Executing resource CreateVolumeFromSnapshotCommand: " + _gson.toJson(cmd)); diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 065d3be0bdd..13816aab62a 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -87,9 +87,13 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateStoragePoolCommand; +import com.cloud.agent.api.CreateVMSnapshotAnswer; +import com.cloud.agent.api.CreateVMSnapshotCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotAnswer; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.agent.api.DeleteVMSnapshotAnswer; +import com.cloud.agent.api.DeleteVMSnapshotCommand; import com.cloud.agent.api.GetDomRVersionAnswer; import com.cloud.agent.api.GetDomRVersionCmd; import com.cloud.agent.api.GetHostStatsAnswer; @@ -126,6 +130,8 @@ import com.cloud.agent.api.ReadyCommand; import com.cloud.agent.api.RebootAnswer; import com.cloud.agent.api.RebootCommand; import com.cloud.agent.api.RebootRouterCommand; +import com.cloud.agent.api.RevertToVMSnapshotAnswer; +import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.SecurityGroupRuleAnswer; import com.cloud.agent.api.SecurityGroupRulesCmd; import com.cloud.agent.api.SetupAnswer; @@ -234,6 +240,7 @@ import com.cloud.utils.net.NetUtils; import com.cloud.vm.DiskProfile; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.snapshot.VMSnapshot; import com.trilead.ssh2.SCPClient; import com.xensource.xenapi.Bond; import com.xensource.xenapi.Connection; @@ -253,6 +260,10 @@ import com.xensource.xenapi.Types; import com.xensource.xenapi.Types.BadServerResponse; import com.xensource.xenapi.Types.ConsoleProtocol; import com.xensource.xenapi.Types.IpConfigurationMode; +import com.xensource.xenapi.Types.OperationNotAllowed; +import com.xensource.xenapi.Types.SrFull; +import com.xensource.xenapi.Types.VbdType; +import com.xensource.xenapi.Types.VmBadPowerState; import com.xensource.xenapi.Types.VmPowerState; import com.xensource.xenapi.Types.XenAPIException; import com.xensource.xenapi.VBD; @@ -570,11 +581,109 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return execute((Site2SiteVpnCfgCommand) cmd); } else if (clazz == CheckS2SVpnConnectionsCommand.class) { return execute((CheckS2SVpnConnectionsCommand) cmd); + } else if (clazz == CreateVMSnapshotCommand.class) { + return execute((CreateVMSnapshotCommand)cmd); + } else if (clazz == DeleteVMSnapshotCommand.class) { + return execute((DeleteVMSnapshotCommand)cmd); + } else if (clazz == RevertToVMSnapshotCommand.class) { + return execute((RevertToVMSnapshotCommand)cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } } + + + private Answer execute(RevertToVMSnapshotCommand cmd) { + String vmName = cmd.getVmName(); + List listVolumeTo = cmd.getVolumeTOs(); + VMSnapshot.Type vmSnapshotType = cmd.getTarget().getType(); + Boolean snapshotMemory = vmSnapshotType == VMSnapshot.Type.DiskAndMemory; + Connection conn = getConnection(); + VirtualMachine.State vmState = null; + VM vm = null; + try { + + // remove vm from s_vms, for delta sync + s_vms.remove(_cluster, _name, vmName); + + Set vmSnapshots = VM.getByNameLabel(conn, cmd.getTarget().getSnapshotName()); + if(vmSnapshots.size() == 0) + return new RevertToVMSnapshotAnswer(cmd, false, "Cannot find vmSnapshot with name: " + cmd.getTarget().getSnapshotName()); + + VM vmSnapshot = vmSnapshots.iterator().next(); + + // find target VM or creating a work VM + try { + vm = getVM(conn, vmName); + } catch (Exception e) { + vm = createWorkingVM(conn, vmName, cmd.getGuestOSType(), listVolumeTo); + } + + if (vm == null) { + return new RevertToVMSnapshotAnswer(cmd, false, + "Revert to VM Snapshot Failed due to can not find vm: " + vmName); + } + + // call plugin to execute revert + revertToSnapshot(conn, vmSnapshot, vmName, vm.getUuid(conn), snapshotMemory, _host.uuid); + vm = getVM(conn, vmName); + Set vbds = vm.getVBDs(conn); + Map vdiMap = new HashMap(); + // get vdi:vbdr to a map + for (VBD vbd : vbds) { + VBD.Record vbdr = vbd.getRecord(conn); + if (vbdr.type == Types.VbdType.DISK) { + VDI vdi = vbdr.VDI; + vdiMap.put(vbdr.userdevice, vdi); + } + } + + if (!snapshotMemory) { + vm.destroy(conn); + vmState = VirtualMachine.State.Stopped; + } else { + s_vms.put(_cluster, _name, vmName, State.Running); + vmState = VirtualMachine.State.Running; + } + + // after revert, VM's volumes path have been changed, need to report to manager + for (VolumeTO volumeTo : listVolumeTo) { + Long deviceId = volumeTo.getDeviceId(); + VDI vdi = vdiMap.get(deviceId.toString()); + volumeTo.setPath(vdi.getUuid(conn)); + } + + return new RevertToVMSnapshotAnswer(cmd, listVolumeTo,vmState); + } catch (Exception e) { + s_logger.error("revert vm " + vmName + + " to snapshot " + cmd.getTarget().getSnapshotName() + " failed due to " + e.getMessage()); + return new RevertToVMSnapshotAnswer(cmd, false, e.getMessage()); + } + } + + private String revertToSnapshot(Connection conn, VM vmSnapshot, + String vmName, String oldVmUuid, Boolean snapshotMemory, String hostUUID) + throws XenAPIException, XmlRpcException { + + String results = callHostPluginAsync(conn, "vmopsSnapshot", + "revert_memory_snapshot", 10 * 60 * 1000, "snapshotUUID", + vmSnapshot.getUuid(conn), "vmName", vmName, "oldVmUuid", + oldVmUuid, "snapshotMemory", snapshotMemory.toString(), "hostUUID", hostUUID); + String errMsg = null; + if (results == null || results.isEmpty()) { + errMsg = "revert_memory_snapshot return null"; + } else { + if (results.equals("0")) { + return results; + } else { + errMsg = "revert_memory_snapshot exception"; + } + } + s_logger.warn(errMsg); + throw new CloudRuntimeException(errMsg); + } + protected XsLocalNetwork getNativeNetworkForTraffic(Connection conn, TrafficType type, String name) throws XenAPIException, XmlRpcException { if (name != null) { if (s_logger.isDebugEnabled()) { @@ -6138,6 +6247,199 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } + protected Answer execute(final CreateVMSnapshotCommand cmd) { + String vmName = cmd.getVmName(); + String vmSnapshotName = cmd.getTarget().getSnapshotName(); + List listVolumeTo = cmd.getVolumeTOs(); + VirtualMachine.State vmState = cmd.getVmState(); + String guestOSType = cmd.getGuestOSType(); + + boolean snapshotMemory = cmd.getTarget().getType() == VMSnapshot.Type.DiskAndMemory; + long timeout = 600; + + Connection conn = getConnection(); + VM vm = null; + VM vmSnapshot = null; + boolean success = false; + + try { + // check if VM snapshot already exists + Set vmSnapshots = VM.getByNameLabel(conn, cmd.getTarget().getSnapshotName()); + if(vmSnapshots.size() > 0) + return new CreateVMSnapshotAnswer(cmd, cmd.getTarget(), cmd.getVolumeTOs()); + + // check if there is already a task for this VM snapshot + Task task = null; + Set tasks = Task.getByNameLabel(conn, "Async.VM.snapshot"); + tasks.addAll(Task.getByNameLabel(conn, "Async.VM.checkpoint")); + for (Task taskItem : tasks) { + if(taskItem.getOtherConfig(conn).containsKey("CS_VM_SNAPSHOT_KEY")){ + String vmSnapshotTaskName = taskItem.getOtherConfig(conn).get("CS_VM_SNAPSHOT_KEY"); + if(vmSnapshotTaskName != null && vmSnapshotTaskName.equals(cmd.getTarget().getSnapshotName())){ + task = taskItem; + } + } + } + + // create a new task if there is no existing task for this VM snapshot + if(task == null){ + try { + vm = getVM(conn, vmName); + } catch (Exception e) { + if (!snapshotMemory) { + vm = createWorkingVM(conn, vmName, guestOSType, listVolumeTo); + } + } + + if (vm == null) { + return new CreateVMSnapshotAnswer(cmd, false, + "Creating VM Snapshot Failed due to can not find vm: " + + vmName); + } + + // call Xenserver API + if (!snapshotMemory) { + task = vm.snapshotAsync(conn, vmSnapshotName); + } else { + Set vbds = vm.getVBDs(conn); + Pool pool = Pool.getByUuid(conn, _host.pool); + for (VBD vbd: vbds){ + VBD.Record vbdr = vbd.getRecord(conn); + if (vbdr.userdevice.equals("0")){ + VDI vdi = vbdr.VDI; + SR sr = vdi.getSR(conn); + // store memory image on the same SR with ROOT volume + pool.setSuspendImageSR(conn, sr); + } + } + task = vm.checkpointAsync(conn, vmSnapshotName); + } + task.addToOtherConfig(conn, "CS_VM_SNAPSHOT_KEY", vmSnapshotName); + } + + waitForTask(conn, task, 1000, timeout * 1000); + checkForSuccess(conn, task); + String result = task.getResult(conn); + + // extract VM snapshot ref from result + String ref = result.substring("".length(), result.length() - "".length()); + vmSnapshot = Types.toVM(ref); + + success = true; + return new CreateVMSnapshotAnswer(cmd, cmd.getTarget(), cmd.getVolumeTOs()); + } catch (Exception e) { + String msg = e.getMessage(); + s_logger.error("Creating VM Snapshot " + cmd.getTarget().getSnapshotName() + " failed due to: " + msg); + return new CreateVMSnapshotAnswer(cmd, false, msg); + } finally { + try { + if (!success) { + if (vmSnapshot != null) { + s_logger.debug("Delete exsisting VM Snapshot " + + vmSnapshotName + + " after making VolumeTO failed"); + Set vbds = vmSnapshot.getVBDs(conn); + for (VBD vbd : vbds) { + VBD.Record vbdr = vbd.getRecord(conn); + if (vbdr.type == VbdType.DISK) { + VDI vdi = vbdr.VDI; + vdi.destroy(conn); + } + } + vmSnapshot.destroy(conn); + } + } + if (vmState == VirtualMachine.State.Stopped) { + if (vm != null) { + vm.destroy(conn); + } + } + } catch (Exception e2) { + s_logger.error("delete snapshot error due to " + + e2.getMessage()); + } + } + } + + private VM createWorkingVM(Connection conn, String vmName, + String guestOSType, List listVolumeTo) + throws BadServerResponse, VmBadPowerState, SrFull, + OperationNotAllowed, XenAPIException, XmlRpcException { + String guestOsTypeName = getGuestOsType(guestOSType, false); + if (guestOsTypeName == null) { + String msg = " Hypervisor " + this.getClass().getName() + + " doesn't support guest OS type " + guestOSType + + ". you can choose 'Other install media' to run it as HVM"; + s_logger.warn(msg); + throw new CloudRuntimeException(msg); + } + VM template = getVM(conn, guestOsTypeName); + VM vm = template.createClone(conn, vmName); + vm.setIsATemplate(conn, false); + Map vdiMap = new HashMap(); + for (VolumeTO volume : listVolumeTo) { + String vdiUuid = volume.getPath(); + try { + VDI vdi = VDI.getByUuid(conn, vdiUuid); + vdiMap.put(vdi, volume); + } catch (Types.UuidInvalid e) { + s_logger.warn("Unable to find vdi by uuid: " + vdiUuid + + ", skip it"); + } + } + for (VDI vdi : vdiMap.keySet()) { + VolumeTO volumeTO = vdiMap.get(vdi); + VBD.Record vbdr = new VBD.Record(); + vbdr.VM = vm; + vbdr.VDI = vdi; + if (volumeTO.getType() == Volume.Type.ROOT) { + vbdr.bootable = true; + vbdr.unpluggable = false; + } else { + vbdr.bootable = false; + vbdr.unpluggable = true; + } + vbdr.userdevice = new Long(volumeTO.getDeviceId()).toString(); + vbdr.mode = Types.VbdMode.RW; + vbdr.type = Types.VbdType.DISK; + VBD.create(conn, vbdr); + } + return vm; + } + + protected Answer execute(final DeleteVMSnapshotCommand cmd) { + String snapshotName = cmd.getTarget().getSnapshotName(); + Connection conn = getConnection(); + + try { + List vdiList = new ArrayList(); + Set snapshots = VM.getByNameLabel(conn, snapshotName); + if(snapshots.size() == 0){ + s_logger.warn("VM snapshot with name " + snapshotName + " does not exist, assume it is already deleted"); + return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs()); + } + VM snapshot = snapshots.iterator().next(); + Set vbds = snapshot.getVBDs(conn); + for (VBD vbd : vbds) { + if (vbd.getType(conn) == VbdType.DISK) { + VDI vdi = vbd.getVDI(conn); + vdiList.add(vdi); + } + } + if(cmd.getTarget().getType() == VMSnapshot.Type.DiskAndMemory) + vdiList.add(snapshot.getSuspendVDI(conn)); + snapshot.destroy(conn); + for (VDI vdi : vdiList) { + vdi.destroy(conn); + } + return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs()); + } catch (Exception e) { + s_logger.warn("Catch Exception: " + e.getClass().toString() + + " due to " + e.toString(), e); + return new DeleteVMSnapshotAnswer(cmd, false, e.getMessage()); + } + } + protected Answer execute(final AttachIsoCommand cmd) { Connection conn = getConnection(); boolean attach = cmd.isAttach(); diff --git a/scripts/vm/hypervisor/xenserver/vmopsSnapshot b/scripts/vm/hypervisor/xenserver/vmopsSnapshot index 80e21f8aaad..d183bec060a 100755 --- a/scripts/vm/hypervisor/xenserver/vmopsSnapshot +++ b/scripts/vm/hypervisor/xenserver/vmopsSnapshot @@ -556,6 +556,33 @@ def deleteSnapshotBackup(session, args): return "1" -if __name__ == "__main__": - XenAPIPlugin.dispatch({"getVhdParent":getVhdParent, "create_secondary_storage_folder":create_secondary_storage_folder, "delete_secondary_storage_folder":delete_secondary_storage_folder, "post_create_private_template":post_create_private_template, "backupSnapshot": backupSnapshot, "deleteSnapshotBackup": deleteSnapshotBackup, "unmountSnapshotsDir": unmountSnapshotsDir}) +@echo +def revert_memory_snapshot(session, args): + util.SMlog("Calling revert_memory_snapshot with " + str(args)) + vmName = args['vmName'] + snapshotUUID = args['snapshotUUID'] + oldVmUuid = args['oldVmUuid'] + snapshotMemory = args['snapshotMemory'] + hostUUID = args['hostUUID'] + try: + cmd = '''xe vbd-list vm-uuid=%s | grep 'vdi-uuid' | grep -v 'not in database' | sed -e 's/vdi-uuid ( RO)://g' ''' % oldVmUuid + vdiUuids = os.popen(cmd).read().split() + cmd2 = '''xe vm-param-get param-name=power-state uuid=''' + oldVmUuid + if os.popen(cmd2).read().split()[0] != 'halted': + os.system("xe vm-shutdown force=true vm=" + vmName) + os.system("xe vm-destroy uuid=" + oldVmUuid) + os.system("xe snapshot-revert snapshot-uuid=" + snapshotUUID) + if snapshotMemory == 'true': + os.system("xe vm-resume vm=" + vmName + " on=" + hostUUID) + for vdiUuid in vdiUuids: + os.system("xe vdi-destroy uuid=" + vdiUuid) + except OSError, (errno, strerror): + errMsg = "OSError while reverting vm " + vmName + " to snapshot " + snapshotUUID + " with errno: " + str(errno) + " and strerr: " + strerror + util.SMlog(errMsg) + raise xs_errors.XenError(errMsg) + return "0" + +if __name__ == "__main__": + XenAPIPlugin.dispatch({"getVhdParent":getVhdParent, "create_secondary_storage_folder":create_secondary_storage_folder, "delete_secondary_storage_folder":delete_secondary_storage_folder, "post_create_private_template":post_create_private_template, "backupSnapshot": backupSnapshot, "deleteSnapshotBackup": deleteSnapshotBackup, "unmountSnapshotsDir": unmountSnapshotsDir, "revert_memory_snapshot":revert_memory_snapshot}) + diff --git a/server/src/com/cloud/api/ApiDBUtils.java b/server/src/com/cloud/api/ApiDBUtils.java index 0b08b26cc32..61bdd829b47 100755 --- a/server/src/com/cloud/api/ApiDBUtils.java +++ b/server/src/com/cloud/api/ApiDBUtils.java @@ -21,6 +21,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Hashtable; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; @@ -259,6 +260,8 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; public class ApiDBUtils { private static ManagementServer _ms; @@ -357,7 +360,7 @@ public class ApiDBUtils { private static VpcOfferingDao _vpcOfferingDao; private static SnapshotPolicyDao _snapshotPolicyDao; private static AsyncJobDao _asyncJobDao; - + private static VMSnapshotDao _vmSnapshotDao; static { _ms = (ManagementServer) ComponentLocator.getComponent(ManagementServer.Name); ComponentLocator locator = ComponentLocator.getLocator(ManagementServer.Name); @@ -454,7 +457,7 @@ public class ApiDBUtils { _diskOfferingJoinDao = locator.getDao(DiskOfferingJoinDao.class); _srvOfferingJoinDao = locator.getDao(ServiceOfferingJoinDao.class); _dcJoinDao = locator.getDao(DataCenterJoinDao.class); - + _vmSnapshotDao = locator.getDao(VMSnapshotDao.class); // Note: stats collector should already have been initialized by this time, otherwise a null instance is returned _statsCollector = StatsCollector.getInstance(); } @@ -1003,6 +1006,11 @@ public class ApiDBUtils { return _networkModel.canUseForDeploy(network); } + public static VMSnapshot getVMSnapshotById(Long vmSnapshotId) { + VMSnapshot vmSnapshot = _vmSnapshotDao.findById(vmSnapshotId); + return vmSnapshot; + } + public static String getUuid(String resourceId, TaggedResourceType resourceType) { return _taggedResourceService.getUuid(resourceId, resourceType); } diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 1c8849a9e53..456967ee08b 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -133,7 +133,6 @@ import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; - import org.apache.cloudstack.api.response.S3Response; import com.cloud.async.AsyncJob; import com.cloud.capacity.Capacity; @@ -242,8 +241,15 @@ import com.cloud.vm.InstanceGroup; import com.cloud.vm.NicProfile; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VmStats; +import com.cloud.vm.dao.UserVmData; +import com.cloud.vm.dao.UserVmData.NicData; +import com.cloud.vm.dao.UserVmData.SecurityGroupData; +import com.cloud.vm.snapshot.VMSnapshot; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.response.VMSnapshotResponse; -public class ApiResponseHelper implements ResponseGenerator { +public class ApiResponseHelper implements ResponseGenerator{ public final Logger s_logger = Logger.getLogger(ApiResponseHelper.class); private static final DecimalFormat s_percentFormat = new DecimalFormat("##.##"); @@ -393,6 +399,25 @@ public class ApiResponseHelper implements ResponseGenerator { return snapshotResponse; } + @Override + public VMSnapshotResponse createVMSnapshotResponse(VMSnapshot vmSnapshot) { + VMSnapshotResponse vmSnapshotResponse = new VMSnapshotResponse(); + vmSnapshotResponse.setId(vmSnapshot.getUuid()); + vmSnapshotResponse.setName(vmSnapshot.getName()); + vmSnapshotResponse.setState(vmSnapshot.getState()); + vmSnapshotResponse.setCreated(vmSnapshot.getCreated()); + vmSnapshotResponse.setDescription(vmSnapshot.getDescription()); + vmSnapshotResponse.setDisplayName(vmSnapshot.getDisplayName()); + UserVm vm = ApiDBUtils.findUserVmById(vmSnapshot.getVmId()); + if(vm!=null) + vmSnapshotResponse.setVirtualMachineid(vm.getUuid()); + if(vmSnapshot.getParent() != null) + vmSnapshotResponse.setParentName(ApiDBUtils.getVMSnapshotById(vmSnapshot.getParent()).getDisplayName()); + vmSnapshotResponse.setCurrent(vmSnapshot.getCurrent()); + vmSnapshotResponse.setType(vmSnapshot.getType().toString()); + return vmSnapshotResponse; + } + @Override public SnapshotPolicyResponse createSnapshotPolicyResponse(SnapshotPolicy policy) { SnapshotPolicyResponse policyResponse = new SnapshotPolicyResponse(); @@ -3098,7 +3123,6 @@ public class ApiResponseHelper implements ResponseGenerator { } - @Override public GuestOSResponse createGuestOSResponse(GuestOS guestOS) { GuestOSResponse response = new GuestOSResponse(); @@ -3136,6 +3160,4 @@ public class ApiResponseHelper implements ResponseGenerator { response.setObjectName("snapshot"); return response; } - - } diff --git a/server/src/com/cloud/capacity/CapacityManagerImpl.java b/server/src/com/cloud/capacity/CapacityManagerImpl.java index c410109b9ef..3bd8adcc874 100755 --- a/server/src/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/com/cloud/capacity/CapacityManagerImpl.java @@ -60,9 +60,11 @@ import com.cloud.storage.VMTemplateHostVO; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VMTemplateSwiftVO; import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.swift.SwiftManager; +import com.cloud.uservm.UserVm; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; @@ -76,7 +78,11 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Event; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @Local(value = CapacityManager.class) public class CapacityManagerImpl implements CapacityManager, StateListener, Listener, ResourceListener { @@ -108,7 +114,11 @@ public class CapacityManagerImpl implements CapacityManager, StateListener volumes = _volumeDao.findByPoolId(pool.getId()); + long totalSize = 0; + for (VolumeVO volume : volumes) { + if(volume.getInstanceId() == null) + continue; + Long vmId = volume.getInstanceId(); + UserVm vm = _userVMDao.findById(vmId); + if(vm == null) + continue; + ServiceOffering offering = _offeringsDao.findById(vm.getServiceOfferingId()); + List vmSnapshots = _vmSnapshotDao.findByVm(vmId); + long pathCount = 0; + long memorySnapshotSize = 0; + for (VMSnapshotVO vmSnapshotVO : vmSnapshots) { + if(_vmSnapshotDao.listByParent(vmSnapshotVO.getId()).size() == 0) + pathCount++; + if(vmSnapshotVO.getType() == VMSnapshot.Type.DiskAndMemory) + memorySnapshotSize += (offering.getRamSize() * 1024 * 1024); + } + if(pathCount <= 1) + totalSize = totalSize + memorySnapshotSize; + else + totalSize = totalSize + volume.getSize() * (pathCount - 1) + memorySnapshotSize; + } + return totalSize; + } + @Override public long getAllocatedPoolCapacity(StoragePoolVO pool, VMTemplateVO templateForVmCreation){ // Get size for all the volumes Pair sizes = _volumeDao.getCountAndTotalByPool(pool.getId()); long totalAllocatedSize = sizes.second() + sizes.first() * _extraBytesPerVolume; + + // Get size for VM Snapshots + totalAllocatedSize = totalAllocatedSize + getVMSnapshotAllocatedCapacity(pool); // Iterate through all templates on this storage pool boolean tmpinstalled = false; diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 5e4996b144c..63ba9b703be 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -33,6 +33,7 @@ import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.storage.snapshot.SnapshotManager; import com.cloud.template.TemplateManager; import com.cloud.vm.UserVmManager; +import com.cloud.vm.snapshot.VMSnapshotManager; public enum Config { @@ -364,8 +365,17 @@ public enum Config { ExternalBaremetalResourceClassName("Advanced", ManagementServer.class, String.class, "external,baremetal.resource.classname", null, "class name for handling external baremetal resource", null), EnableBaremetalSecurityGroupAgentEcho("Advanced", ManagementServer.class, Boolean.class, "enable.baremetal.securitygroup.agent.echo", "false", "After starting provision process, periodcially echo security agent installed in the template. Treat provisioning as success only if echo successfully", null), IntervalToEchoBaremetalSecurityGroupAgent("Advanced", ManagementServer.class, Integer.class, "interval.baremetal.securitygroup.agent.echo", "10", "Interval to echo baremetal security group agent, in seconds", null), - TimeoutToEchoBaremetalSecurityGroupAgent("Advanced", ManagementServer.class, Integer.class, "timeout.baremetal.securitygroup.agent.echo", "3600", "Timeout to echo baremetal security group agent, in seconds, the provisioning process will be treated as a failure", null); + TimeoutToEchoBaremetalSecurityGroupAgent("Advanced", ManagementServer.class, Integer.class, "timeout.baremetal.securitygroup.agent.echo", "3600", "Timeout to echo baremetal security group agent, in seconds, the provisioning process will be treated as a failure", null), + + + // VMSnapshots + VMSnapshotMax("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.max", "10", "Maximum vm snapshots for a vm", null), + VMSnapshotCreateWait("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.create.wait", "600", "In second, timeout for create vm snapshot", null), + VMSnapshotExpungeInterval("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.expunge.interval", "60", "The interval (in seconds) to wait before running the expunge thread.", null), + VMSnapshotExpungeWorkers("Advanced", VMSnapshotManager.class, Integer.class, "vmsnapshot.expunge.workers", "1", "Number of workers performing expunge ", null); + + private final String _category; private final Class _componentClass; private final Class _type; diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index 98da7adfa39..df35c0fff5c 100755 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -229,8 +229,8 @@ import com.cloud.vm.dao.UserVmDaoImpl; import com.cloud.vm.dao.UserVmDetailsDaoImpl; import com.cloud.vm.dao.VMInstanceDaoImpl; import com.cloud.event.dao.EventJoinDaoImpl; - - +import com.cloud.vm.snapshot.VMSnapshotManagerImpl; +import com.cloud.vm.snapshot.dao.VMSnapshotDaoImpl; public class DefaultComponentLibrary extends ComponentLibraryBase implements ComponentLibrary { protected void populateDaos() { @@ -380,7 +380,6 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com addDao("Site2SiteVpnGatewayDao", Site2SiteVpnGatewayDaoImpl.class); addDao("Site2SiteCustomerGatewayDao", Site2SiteCustomerGatewayDaoImpl.class); addDao("Site2SiteVpnConnnectionDao", Site2SiteVpnConnectionDaoImpl.class); - addDao("UserVmJoinDao", UserVmJoinDaoImpl.class); addDao("DomainRouterJoinDao", DomainRouterJoinDaoImpl.class); addDao("SecurityGroupJoinDao", SecurityGroupJoinDaoImpl.class); @@ -398,6 +397,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com addDao("DiskOfferingJoinDao", DiskOfferingJoinDaoImpl.class); addDao("ServiceOfferingJoinDao", ServiceOfferingJoinDaoImpl.class); addDao("DataCenterJoinDao", DataCenterJoinDaoImpl.class); + addDao("VMSnapshotDao", VMSnapshotDaoImpl.class); } @Override @@ -462,6 +462,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com addManager("TaggedResourcesManager", TaggedResourceManagerImpl.class); addManager("Site2SiteVpnManager", Site2SiteVpnManagerImpl.class); addManager("QueryManager", QueryManagerImpl.class); + addManager("VMSnapshot Manager", VMSnapshotManagerImpl.class); } @Override diff --git a/server/src/com/cloud/resource/ResourceManagerImpl.java b/server/src/com/cloud/resource/ResourceManagerImpl.java index f82424a10c2..bb1c8fd7843 100755 --- a/server/src/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/com/cloud/resource/ResourceManagerImpl.java @@ -1292,7 +1292,7 @@ public class ResourceManagerImpl implements ResourceManager, ResourceService, Ma for (ClusterVO cluster : clustersForZone) { HypervisorType hType = cluster.getHypervisorType(); - if (!forVirtualRouter || (forVirtualRouter && hType != HypervisorType.BareMetal && hType != HypervisorType.Ovm)) { + if ( (cluster.getAllocationState() == AllocationState.Enabled) && (!forVirtualRouter || (forVirtualRouter && hType != HypervisorType.BareMetal && hType != HypervisorType.Ovm))) { hypervisorTypes.add(hType); } } diff --git a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 15d8c53d06f..8c1ea7b6e06 100755 --- a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -128,6 +128,9 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @Local(value = { SnapshotManager.class, SnapshotService.class }) public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Manager { @@ -188,7 +191,8 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma private VolumeDao _volumeDao; @Inject private ResourceTagDao _resourceTagDao; - + @Inject + protected VMSnapshotDao _vmSnapshotDao; String _name; private int _totalRetries; private int _pauseInterval; @@ -401,6 +405,7 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma } // if volume is attached to a vm in destroyed or expunging state; disallow + // if volume is attached to a vm in taking vm snapshot; disallow if (volume.getInstanceId() != null) { UserVmVO userVm = _vmDao.findById(volume.getInstanceId()); if (userVm != null) { @@ -414,6 +419,12 @@ public class SnapshotManagerImpl implements SnapshotManager, SnapshotService, Ma if(activeSnapshots.size() > 1) throw new CloudRuntimeException("There is other active snapshot tasks on the instance to which the volume is attached, please try again later"); } + List activeVMSnapshots = _vmSnapshotDao.listByInstanceId(userVm.getId(), + VMSnapshot.State.Creating, VMSnapshot.State.Reverting, VMSnapshot.State.Expunging); + if (activeVMSnapshots.size() > 0) { + throw new CloudRuntimeException( + "There is other active vm snapshot tasks on the instance to which the volume is attached, please try again later"); + } } } diff --git a/server/src/com/cloud/tags/TaggedResourceManagerImpl.java b/server/src/com/cloud/tags/TaggedResourceManagerImpl.java index 42d3c8332da..01338d0bf88 100644 --- a/server/src/com/cloud/tags/TaggedResourceManagerImpl.java +++ b/server/src/com/cloud/tags/TaggedResourceManagerImpl.java @@ -71,6 +71,7 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.uuididentity.dao.IdentityDao; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @Local(value = { TaggedResourceService.class}) @@ -119,6 +120,8 @@ public class TaggedResourceManagerImpl implements TaggedResourceService, Manager VpcDao _vpcDao; @Inject StaticRouteDao _staticRouteDao; + @Inject + VMSnapshotDao _vmSnapshotDao; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -138,6 +141,7 @@ public class TaggedResourceManagerImpl implements TaggedResourceService, Manager _daoMap.put(TaggedResourceType.Vpc, _vpcDao); _daoMap.put(TaggedResourceType.NetworkACL, _firewallDao); _daoMap.put(TaggedResourceType.StaticRoute, _staticRouteDao); + _daoMap.put(TaggedResourceType.VMSnapshot, _vmSnapshotDao); return true; } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index ecf124228d4..37c70e3eec9 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -142,6 +142,16 @@ import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import com.cloud.vm.dao.InstanceGroupDao; +import com.cloud.vm.dao.InstanceGroupVMMapDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotManager; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @Local(value = { UserVmManager.class, UserVmService.class }) public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager { @@ -275,7 +285,11 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager PhysicalNetworkDao _physicalNetworkDao; @Inject VpcManager _vpcMgr; - + @Inject + VMSnapshotDao _vmSnapshotDao; + @Inject + protected VMSnapshotManager _vmSnapshotMgr; + protected ScheduledExecutorService _executor = null; protected int _expungeInterval; protected int _expungeDelay; @@ -517,6 +531,9 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager //permission check _accountMgr.checkAccess(caller, null, true, volume, vm); + //check if vm has snapshot, if true: can't attache volume + boolean attach = true; + checkVMSnapshots(vm, volumeId, attach); //Check if volume is stored on secondary Storage. boolean isVolumeOnSec = false; VolumeHostVO volHostVO = _volumeHostDao.findByVolumeId(volume.getId()); @@ -725,8 +742,19 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager } } - @Override - @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume", async = true) + private void checkVMSnapshots(UserVmVO vm, Long volumeId, boolean attach) { + // Check that if vm has any VM snapshot + Long vmId = vm.getId(); + List listSnapshot = _vmSnapshotDao.listByInstanceId(vmId, + VMSnapshot.State.Ready, VMSnapshot.State.Creating, VMSnapshot.State.Reverting, VMSnapshot.State.Expunging); + if (listSnapshot != null && listSnapshot.size() != 0) { + throw new InvalidParameterValueException( + "The VM has VM snapshots, do not allowed to attach volume. Please delete the VM snapshots first."); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "event_detaching_volume1", async = true) public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) { Account caller = UserContext.current().getCaller(); if ((cmmd.getId() == null && cmmd.getDeviceId() == null && cmmd.getVirtualMachineId() == null) || (cmmd.getId() != null && (cmmd.getDeviceId() != null || cmmd.getVirtualMachineId() != null)) @@ -775,6 +803,9 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager throw new InvalidParameterValueException("Please specify a VM that is either running or stopped."); } + // Check that if the volume has snapshot + boolean attach = false; + checkVMSnapshots(vm, volumeId, attach); AsyncJobExecutor asyncExecutor = BaseAsyncJobExecutor.getCurrentExecutor(); if (asyncExecutor != null) { AsyncJobVO job = asyncExecutor.getJob(); @@ -905,12 +936,25 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager if (vmInstance == null) { throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); } - + _accountMgr.checkAccess(caller, null, true, vmInstance); // Check that the specified service offering ID is valid _itMgr.checkIfCanUpgrade(vmInstance, svcOffId); + // remove diskAndMemory VM snapshots + List vmSnapshots = _vmSnapshotDao.findByVm(vmId); + for (VMSnapshotVO vmSnapshotVO : vmSnapshots) { + if(vmSnapshotVO.getType() == VMSnapshot.Type.DiskAndMemory){ + if(!_vmSnapshotMgr.deleteAllVMSnapshots(vmId, VMSnapshot.Type.DiskAndMemory)){ + String errMsg = "Failed to remove VM snapshot during upgrading, snapshot id " + vmSnapshotVO.getId(); + s_logger.debug(errMsg); + throw new CloudRuntimeException(errMsg); + } + + } + } + _itMgr.upgradeVmDb(vmId, svcOffId); return _vmDao.findById(vmInstance.getId()); @@ -1293,7 +1337,6 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager } volume = _volsDao.findById(snapshot.getVolumeId()); - VolumeVO snapshotVolume = _volsDao.findByIdIncludingRemoved(snapshot.getVolumeId()); //check permissions _accountMgr.checkAccess(caller, null, true, snapshot); diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index e0647bbe67f..a5b7129834d 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -154,12 +154,17 @@ import com.cloud.utils.fsm.StateMachine2; import com.cloud.vm.ItWorkVO.Step; import com.cloud.vm.VirtualMachine.Event; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineProfile.Param; import com.cloud.vm.dao.ConsoleProxyDao; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotManager; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @Local(value = VirtualMachineManager.class) public class VirtualMachineManagerImpl implements VirtualMachineManager, Listener { @@ -230,6 +235,8 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene protected HypervisorGuruManager _hvGuruMgr; @Inject protected NetworkDao _networkDao; + @Inject + protected VMSnapshotDao _vmSnapshotDao; @Inject(adapter = DeploymentPlanner.class) protected Adapters _planners; @@ -239,6 +246,9 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene @Inject protected ResourceManager _resourceMgr; + + @Inject + protected VMSnapshotManager _vmSnapshotMgr = null; Map> _vmGurus = new HashMap>(); protected StateMachine2 _stateMachine; @@ -599,7 +609,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene ConcurrentOperationException, ResourceUnavailableException { return advanceStart(vm, params, caller, account, null); } - + @Override public T advanceStart(T vm, Map params, User caller, Account account, DeploymentPlan planToDeploy) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -1186,7 +1196,12 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene s_logger.debug("Unable to stop " + vm); return false; } - + + if (!_vmSnapshotMgr.deleteAllVMSnapshots(vm.getId(),null)){ + s_logger.debug("Unable to delete all snapshots for " + vm); + return false; + } + try { if (!stateTransitTo(vm, VirtualMachine.Event.DestroyRequested, vm.getHostId())) { s_logger.debug("Unable to destroy the vm because it is not in the correct state: " + vm); @@ -1633,6 +1648,23 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene s_logger.debug("Found " + vms.size() + " VMs for host " + hostId); for (VMInstanceVO vm : vms) { AgentVmInfo info = infos.remove(vm.getId()); + + // sync VM Snapshots related transient states + List expungingVMSnapshot = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging); + if( vm.getState() == State.RevertingToRunning || vm.getState() == State.RevertingToStopped + || vm.getState() == State.RunningSnapshotting || vm.getState() == State.StoppedSnapshotting + || expungingVMSnapshot.size() == 1){ + s_logger.info("Found vm " + vm.getInstanceName() + " in state. " + vm.getState() + ", needs to sync VM snapshot state"); + if(!_vmSnapshotMgr.syncVMSnapshot(vm, null, hostId)){ + s_logger.warn("Failed to sync VM in a transient snapshot related state: " + vm.getInstanceName()); + continue; + }else{ + s_logger.info("Successfully sync VM in a transient snapshot related state: " + vm.getInstanceName() + " to " + vm.getState()); + } + if(expungingVMSnapshot.size() == 1) + _vmSnapshotMgr.syncVMSnapshot(vm, expungingVMSnapshot.get(0), hostId); + } + VMInstanceVO castedVm = null; if (info == null) { info = new AgentVmInfo(vm.getInstanceName(), getVmGuru(vm), vm, State.Stopped); @@ -1750,8 +1782,31 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene for (VMInstanceVO vm : set_vms) { AgentVmInfo info = infos.remove(vm.getId()); VMInstanceVO castedVm = null; - if ((info == null && (vm.getState() == State.Running || vm.getState() == State.Starting)) - || (info != null && (info.state == State.Running && vm.getState() == State.Starting))) + + // sync VM Snapshots related transient states + List expungingVMSnapshot = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging); + if( vm.getState() == State.RevertingToRunning || vm.getState() == State.RevertingToStopped + || vm.getState() == State.RunningSnapshotting || vm.getState() == State.StoppedSnapshotting + || expungingVMSnapshot.size() == 1){ + s_logger.info("Found vm " + vm.getInstanceName() + " in state. " + vm.getState() + ", needs to sync VM snapshot state"); + Long hostId = null; + if(info != null && info.getHostUuid() != null){ + Host host = _hostDao.findByGuid(info.getHostUuid()); + hostId = host == null ? (vm.getHostId() == null ? vm.getLastHostId() : vm.getHostId()) : host.getId(); + } + if(!_vmSnapshotMgr.syncVMSnapshot(vm, null, hostId)){ + s_logger.warn("Failed to sync VM in a transient snapshot related state: " + vm.getInstanceName()); + continue; + }else{ + s_logger.info("Successfully sync VM in a transient snapshot related state: " + vm.getInstanceName() + " to " + vm.getState()); + } + if(expungingVMSnapshot.size() == 1) + _vmSnapshotMgr.syncVMSnapshot(vm, expungingVMSnapshot.get(0), hostId); + } + + if ((info == null && (vm.getState() == State.Running || vm.getState() == State.Starting )) + || (info != null && (info.state == State.Running && vm.getState() == State.Starting)) + || (info != null && (info.state == State.Running && vm.getState() == State.RevertingToRunning))) { s_logger.info("Found vm " + vm.getInstanceName() + " in inconsistent state. " + vm.getState() + " on CS while " + (info == null ? "Stopped" : "Running") + " on agent"); info = new AgentVmInfo(vm.getInstanceName(), getVmGuru(vm), vm, State.Stopped); @@ -1792,7 +1847,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene } } else if (info != null && (vm.getState() == State.Stopped || vm.getState() == State.Stopping - || vm.isRemoved() || vm.getState() == State.Destroyed || vm.getState() == State.Expunging)) { + || vm.isRemoved() || vm.getState() == State.Destroyed || vm.getState() == State.Expunging )) { Host host = _hostDao.findByGuid(info.getHostUuid()); if (host != null){ s_logger.warn("Stopping a VM which is stopped/stopping/destroyed/expunging " + info.name); @@ -2267,6 +2322,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene Long clusterId = agent.getClusterId(); long agentId = agent.getId(); + if (agent.getHypervisorType() == HypervisorType.XenServer) { // only for Xen StartupRoutingCommand startup = (StartupRoutingCommand) cmd; HashMap> allStates = startup.getClusterVMStateChanges(); @@ -2383,7 +2439,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene if (newServiceOffering == null) { throw new InvalidParameterValueException("Unable to find a service offering with id " + newServiceOfferingId); } - + // Check that the VM is stopped if (!vmInstance.getState().equals(State.Stopped)) { s_logger.warn("Unable to upgrade virtual machine " + vmInstance.toString() + " in state " + vmInstance.getState()); diff --git a/server/src/com/cloud/vm/snapshot/VMSnapshotManager.java b/server/src/com/cloud/vm/snapshot/VMSnapshotManager.java new file mode 100644 index 00000000000..77c3b695c52 --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/VMSnapshotManager.java @@ -0,0 +1,46 @@ +// 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.vm.snapshot; + +import com.cloud.utils.component.Manager; +import com.cloud.vm.VMInstanceVO; + +public interface VMSnapshotManager extends VMSnapshotService, Manager { + public static final int VMSNAPSHOTMAX = 10; + + + /** + * Delete all VM snapshots belonging to one VM + * @param id, VM id + * @param type, + * @return true for success, false for failure + */ + boolean deleteAllVMSnapshots(long id, VMSnapshot.Type type); + + /** + * Sync VM's state when VM in reverting or snapshotting, or VM snapshot in expunging state + * Used for fullsync after agent connects + * + * @param vm, the VM in question + * @param vmSnapshot, if this is not null, sync an VM snapshot in expunging state + * @param hostId + * @return true if succeeds, false if fails + */ + boolean syncVMSnapshot(VMInstanceVO vm, VMSnapshotVO vmSnapshot, Long hostId); + +} diff --git a/server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java b/server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java new file mode 100644 index 00000000000..49a4eec444a --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -0,0 +1,828 @@ +// 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.vm.snapshot; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.user.vmsnapshot.CreateVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.DeleteVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToSnapshotCmd; +import org.apache.log4j.Logger; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.CreateVMSnapshotAnswer; +import com.cloud.agent.api.CreateVMSnapshotCommand; +import com.cloud.agent.api.DeleteVMSnapshotAnswer; +import com.cloud.agent.api.DeleteVMSnapshotCommand; +import com.cloud.agent.api.RevertToVMSnapshotAnswer; +import com.cloud.agent.api.RevertToVMSnapshotCommand; +import com.cloud.agent.api.VMSnapshotTO; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.projects.Project.ListProjectResourcesCriteria; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.StoragePoolVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.StoragePoolDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.UserContext; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.component.Inject; +import com.cloud.utils.component.Manager; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; + +@Local(value = { VMSnapshotManager.class, VMSnapshotService.class }) +public class VMSnapshotManagerImpl implements VMSnapshotManager, VMSnapshotService, Manager { + private static final Logger s_logger = Logger.getLogger(VMSnapshotManagerImpl.class); + String _name; + @Inject VMSnapshotDao _vmSnapshotDao; + @Inject VolumeDao _volumeDao; + @Inject AccountDao _accountDao; + @Inject VMInstanceDao _vmInstanceDao; + @Inject UserVmDao _userVMDao; + @Inject HostDao _hostDao; + @Inject UserDao _userDao; + @Inject AgentManager _agentMgr; + @Inject HypervisorGuruManager _hvGuruMgr; + @Inject AccountManager _accountMgr; + @Inject GuestOSDao _guestOSDao; + @Inject StoragePoolDao _storagePoolDao; + @Inject SnapshotDao _snapshotDao; + @Inject VirtualMachineManager _itMgr; + @Inject ConfigurationDao _configDao; + int _vmSnapshotMax; + StateMachine2 _stateMachine; + StateMachine2 _vmSnapshottateMachine ; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + + ComponentLocator locator = ComponentLocator.getCurrentLocator(); + _configDao = locator.getDao(ConfigurationDao.class); + if (_configDao == null) { + throw new ConfigurationException("Unable to get the configuration dao."); + } + s_logger.info("Snapshot Manager is configured."); + + _vmSnapshotMax = NumbersUtil.parseInt(_configDao.getValue("vmsnapshot.max"), VMSNAPSHOTMAX); + + _stateMachine = VirtualMachine.State.getStateMachine(); + _vmSnapshottateMachine = VMSnapshot.State.getStateMachine(); + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public List listVMSnapshots(ListVMSnapshotCmd cmd) { + Account caller = getCaller(); + List permittedAccounts = new ArrayList(); + + boolean listAll = cmd.listAll(); + Long id = cmd.getId(); + Long vmId = cmd.getVmId(); + + String state = cmd.getState(); + String keyword = cmd.getKeyword(); + String name = cmd.getVmSnapshotName(); + String accountName = cmd.getAccountName(); + + Ternary domainIdRecursiveListProject = new Ternary( + cmd.getDomainId(), cmd.isRecursive(), null); + _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, listAll, + false); + Long domainId = domainIdRecursiveListProject.first(); + Boolean isRecursive = domainIdRecursiveListProject.second(); + ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); + + Filter searchFilter = new Filter(VMSnapshotVO.class, "created", false, cmd.getStartIndex(), cmd.getPageSizeVal()); + SearchBuilder sb = _vmSnapshotDao.createSearchBuilder(); + _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + + sb.and("vm_id", sb.entity().getVmId(), SearchCriteria.Op.EQ); + sb.and("domain_id", sb.entity().getDomainId(), SearchCriteria.Op.EQ); + sb.and("status", sb.entity().getState(), SearchCriteria.Op.IN); + sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and("display_name", sb.entity().getDisplayName(), SearchCriteria.Op.EQ); + sb.and("account_id", sb.entity().getAccountId(), SearchCriteria.Op.EQ); + sb.done(); + + SearchCriteria sc = sb.create(); + _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + + if (accountName != null && cmd.getDomainId() != null) { + Account account = _accountMgr.getActiveAccountByName(accountName, cmd.getDomainId()); + sc.setParameters("account_id", account.getId()); + } + + if (vmId != null) { + sc.setParameters("vm_id", vmId); + } + + if (domainId != null) { + sc.setParameters("domain_id", domainId); + } + + if (state == null) { + VMSnapshot.State[] status = { VMSnapshot.State.Ready, VMSnapshot.State.Creating, VMSnapshot.State.Allocated, + VMSnapshot.State.Error, VMSnapshot.State.Expunging, VMSnapshot.State.Reverting }; + sc.setParameters("status", (Object[]) status); + } else { + sc.setParameters("state", state); + } + + if (name != null) { + sc.setParameters("display_name", name); + } + + if (keyword != null) { + SearchCriteria ssc = _vmSnapshotDao.createSearchCriteria(); + ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + ssc.addOr("display_name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + ssc.addOr("description", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + sc.addAnd("name", SearchCriteria.Op.SC, ssc); + } + + if (id != null) { + sc.setParameters("id", id); + } + + return _vmSnapshotDao.search(sc, searchFilter); + + } + + protected Account getCaller(){ + return UserContext.current().getCaller(); + } + + @Override + public VMSnapshot allocVMSnapshot(CreateVMSnapshotCmd cmd) throws ResourceAllocationException { + Long vmId = cmd.getVmId(); + String vsDisplayName = cmd.getDisplayName(); + String vsDescription = cmd.getDescription(); + Boolean snapshotMemory = cmd.snapshotMemory(); + + Account caller = getCaller(); + + // check if VM exists + UserVmVO userVmVo = _userVMDao.findById(vmId); + if (userVmVo == null) { + throw new InvalidParameterValueException("Creating VM snapshot failed due to VM:" + vmId + " is a system VM or does not exist"); + } + + // VM snapshot display name must be unique for a VM + String timeString = DateUtil.getDateDisplayString(DateUtil.GMT_TIMEZONE, new Date(), DateUtil.YYYYMMDD_FORMAT); + String vmSnapshotName = userVmVo.getInstanceName() + "_VS_" + timeString; + if (vsDisplayName == null) { + vsDisplayName = vmSnapshotName; + } + if(_vmSnapshotDao.findByName(vmId,vsDisplayName) != null){ + throw new InvalidParameterValueException("Creating VM snapshot failed due to VM snapshot with name" + vsDisplayName + " already exists"); + } + + // check VM state + if (userVmVo.getState() != VirtualMachine.State.Running && userVmVo.getState() != VirtualMachine.State.Stopped) { + throw new InvalidParameterValueException("Creating vm snapshot failed due to VM:" + vmId + " is not in the running or Stopped state"); + } + + // for KVM, only allow snapshot with memory when VM is in running state + if(userVmVo.getHypervisorType() == HypervisorType.KVM && userVmVo.getState() == State.Running && !snapshotMemory){ + throw new InvalidParameterValueException("KVM VM does not allow to take a disk-only snapshot when VM is in running state"); + } + + // check access + _accountMgr.checkAccess(caller, null, true, userVmVo); + + // check max snapshot limit for per VM + if (_vmSnapshotDao.findByVm(vmId).size() >= _vmSnapshotMax) { + throw new CloudRuntimeException("Creating vm snapshot failed due to a VM can just have : " + _vmSnapshotMax + + " VM snapshots. Please delete old ones"); + } + + // check if there are active volume snapshots tasks + List listVolumes = _volumeDao.findByInstance(vmId); + for (VolumeVO volume : listVolumes) { + List activeSnapshots = _snapshotDao.listByInstanceId(volume.getInstanceId(), Snapshot.Status.Creating, + Snapshot.Status.CreatedOnPrimary, Snapshot.Status.BackingUp); + 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."); + } + } + + // check if there are other active VM snapshot tasks + if (checkActiveVMSnapshotTasks(vmId)) { + throw new CloudRuntimeException("There is other active vm snapshot tasks on the instance, please try again later"); + } + + VMSnapshot.Type vmSnapshotType = VMSnapshot.Type.Disk; + if(snapshotMemory && userVmVo.getState() == VirtualMachine.State.Running) + vmSnapshotType = VMSnapshot.Type.DiskAndMemory; + + try { + VMSnapshotVO vmSnapshotVo = new VMSnapshotVO(userVmVo.getAccountId(), userVmVo.getDomainId(), vmId, vsDescription, vmSnapshotName, + vsDisplayName, userVmVo.getServiceOfferingId(), vmSnapshotType, null); + VMSnapshot vmSnapshot = _vmSnapshotDao.persist(vmSnapshotVo); + if (vmSnapshot == null) { + throw new CloudRuntimeException("Failed to create snapshot for vm: " + vmId); + } + return vmSnapshot; + } catch (Exception e) { + String msg = e.getMessage(); + s_logger.error("Create vm snapshot record failed for vm: " + vmId + " due to: " + msg); + } + return null; + } + + @Override + public String getName() { + return _name; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_SNAPSHOT_CREATE, eventDescription = "creating_vm_snapshot", async = true) + public VMSnapshot creatVMSnapshot(CreateVMSnapshotCmd cmd) { + Long vmId = cmd.getVmId(); + UserVmVO userVm = _userVMDao.findById(vmId); + VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(cmd.getEntityId()); + Long hostId = pickRunningHost(vmId); + transitState(vmSnapshot, VMSnapshot.Event.CreateRequested, userVm, VirtualMachine.Event.SnapshotRequested, hostId); + return createVmSnapshotInternal(userVm, vmSnapshot, hostId); + } + + protected VMSnapshot createVmSnapshotInternal(UserVmVO userVm, VMSnapshotVO vmSnapshot, Long hostId) { + try { + CreateVMSnapshotAnswer answer = null; + GuestOSVO guestOS = _guestOSDao.findById(userVm.getGuestOSId()); + + // prepare snapshotVolumeTos + List volumeTOs = getVolumeTOList(userVm.getId()); + + // prepare target snapshotTO and its parent snapshot (current snapshot) + VMSnapshotTO current = null; + VMSnapshotVO currentSnapshot = _vmSnapshotDao.findCurrentSnapshotByVmId(userVm.getId()); + if (currentSnapshot != null) + current = getSnapshotWithParents(currentSnapshot); + VMSnapshotTO target = new VMSnapshotTO(vmSnapshot.getId(), vmSnapshot.getName(), vmSnapshot.getType(), null, vmSnapshot.getDescription(), false, + current); + if (current == null) + vmSnapshot.setParent(null); + else + vmSnapshot.setParent(current.getId()); + + CreateVMSnapshotCommand ccmd = new CreateVMSnapshotCommand(userVm.getInstanceName(),target ,volumeTOs, guestOS.getDisplayName(),userVm.getState()); + + answer = (CreateVMSnapshotAnswer) sendToPool(hostId, ccmd); + if (answer != null && answer.getResult()) { + processAnswer(vmSnapshot, userVm, answer, hostId); + transitState(vmSnapshot, VMSnapshot.Event.OperationSucceeded, userVm, VirtualMachine.Event.OperationSucceeded, hostId); + s_logger.debug("Create vm snapshot " + vmSnapshot.getName() + " succeeded for vm: " + userVm.getInstanceName()); + }else{ + String errMsg = answer.getDetails(); + s_logger.error("Agent reports creating vm snapshot " + vmSnapshot.getName() + " failed for vm: " + userVm.getInstanceName() + " due to " + errMsg); + transitState(vmSnapshot, VMSnapshot.Event.OperationFailed, userVm, VirtualMachine.Event.OperationFailed, hostId); + } + return vmSnapshot; + } catch (Exception e) { + String msg = e.getMessage(); + s_logger.error("Create vm snapshot " + vmSnapshot.getName() + " failed for vm: " + userVm.getInstanceName() + " due to " + msg); + throw new CloudRuntimeException(msg); + } finally{ + if(vmSnapshot.getState() == VMSnapshot.State.Allocated){ + s_logger.warn("Create vm snapshot " + vmSnapshot.getName() + " failed for vm: " + userVm.getInstanceName()); + _vmSnapshotDao.remove(vmSnapshot.getId()); + } + } + } + + protected List getVolumeTOList(Long vmId) { + List volumeTOs = new ArrayList(); + List volumeVos = _volumeDao.findByInstance(vmId); + + for (VolumeVO volume : volumeVos) { + StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); + VolumeTO volumeTO = new VolumeTO(volume, pool); + volumeTOs.add(volumeTO); + } + return volumeTOs; + } + + // get snapshot and its parents recursively + private VMSnapshotTO getSnapshotWithParents(VMSnapshotVO snapshot) { + Map snapshotMap = new HashMap(); + List 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()); + currentTO.setParent(convert2VMSnapshotTO(parent)); + current = snapshotMap.get(current.getParent()); + currentTO = currentTO.getParent(); + } + return result; + } + + private VMSnapshotTO convert2VMSnapshotTO(VMSnapshotVO vo) { + return new VMSnapshotTO(vo.getId(), vo.getName(), vo.getType(), vo.getCreated().getTime(), vo.getDescription(), + vo.getCurrent(), null); + } + + private boolean vmSnapshotStateTransitTo(VMSnapshotVO vsnp, VMSnapshot.Event event) throws NoTransitionException { + return _vmSnapshottateMachine.transitTo(vsnp, event, null, _vmSnapshotDao); + } + + @DB + protected boolean transitState(VMSnapshotVO vsnp, VMSnapshot.Event e1, VMInstanceVO vm, VirtualMachine.Event e2, Long hostId){ + final Transaction txn = Transaction.currentTxn(); + try { + txn.start(); + vmSnapshotStateTransitTo(vsnp, e1); + vmStateTransitTo(vm, e2, hostId, null); + return txn.commit(); + } catch (NoTransitionException e) { + txn.rollback(); + return false; + }finally{ + txn.close(); + } + } + + @DB + protected void processAnswer(VMSnapshotVO vmSnapshot, UserVmVO userVm, Answer as, Long hostId) { + final Transaction txn = Transaction.currentTxn(); + try { + txn.start(); + if (as instanceof CreateVMSnapshotAnswer) { + CreateVMSnapshotAnswer answer = (CreateVMSnapshotAnswer) as; + finalizeCreate(vmSnapshot, answer.getVolumeTOs()); + } else if (as instanceof RevertToVMSnapshotAnswer) { + RevertToVMSnapshotAnswer answer = (RevertToVMSnapshotAnswer) as; + finalizeRevert(vmSnapshot, answer.getVolumeTOs()); + } else if (as instanceof DeleteVMSnapshotAnswer) { + DeleteVMSnapshotAnswer answer = (DeleteVMSnapshotAnswer) as; + finalizeDelete(vmSnapshot, answer.getVolumeTOs()); + _vmSnapshotDao.remove(vmSnapshot.getId()); + } + txn.commit(); + } catch (Exception e) { + String errMsg = "Error while process answer: " + as.getClass() + " due to " + e.getMessage(); + s_logger.error(errMsg, e); + txn.rollback(); + throw new CloudRuntimeException(errMsg); + } finally { + txn.close(); + } + } + + protected void finalizeDelete(VMSnapshotVO vmSnapshot, List VolumeTOs) { + // update volumes path + updateVolumePath(VolumeTOs); + + // update children's parent snapshots + List children= _vmSnapshotDao.listByParent(vmSnapshot.getId()); + for (VMSnapshotVO child : children) { + child.setParent(vmSnapshot.getParent()); + _vmSnapshotDao.persist(child); + } + + // update current snapshot + VMSnapshotVO current = _vmSnapshotDao.findCurrentSnapshotByVmId(vmSnapshot.getVmId()); + if(current != null && current.getId() == vmSnapshot.getId() && vmSnapshot.getParent() != null){ + VMSnapshotVO parent = _vmSnapshotDao.findById(vmSnapshot.getParent()); + parent.setCurrent(true); + _vmSnapshotDao.persist(parent); + } + vmSnapshot.setCurrent(false); + _vmSnapshotDao.persist(vmSnapshot); + } + + protected void finalizeCreate(VMSnapshotVO vmSnapshot, List VolumeTOs) { + // update volumes path + updateVolumePath(VolumeTOs); + + vmSnapshot.setCurrent(true); + + // change current snapshot + if (vmSnapshot.getParent() != null) { + VMSnapshotVO previousCurrent = _vmSnapshotDao.findById(vmSnapshot.getParent()); + previousCurrent.setCurrent(false); + _vmSnapshotDao.persist(previousCurrent); + } + _vmSnapshotDao.persist(vmSnapshot); + } + + protected void finalizeRevert(VMSnapshotVO vmSnapshot, List volumeToList) { + // update volumes path + updateVolumePath(volumeToList); + + // update current snapshot, current snapshot is the one reverted to + VMSnapshotVO previousCurrent = _vmSnapshotDao.findCurrentSnapshotByVmId(vmSnapshot.getVmId()); + previousCurrent.setCurrent(false); + vmSnapshot.setCurrent(true); + _vmSnapshotDao.persist(previousCurrent); + _vmSnapshotDao.persist(vmSnapshot); + } + + private void updateVolumePath(List volumeTOs) { + for (VolumeTO volume : volumeTOs) { + if (volume.getPath() != null) { + VolumeVO volumeVO = _volumeDao.findById(volume.getId()); + volumeVO.setPath(volume.getPath()); + _volumeDao.persist(volumeVO); + } + } + } + + public VMSnapshotManagerImpl() { + + } + + protected Answer sendToPool(Long hostId, Command cmd) throws AgentUnavailableException, OperationTimedoutException { + long targetHostId = _hvGuruMgr.getGuruProcessedCommandTargetHost(hostId, cmd); + Answer answer = _agentMgr.send(targetHostId, cmd); + return answer; + } + + private boolean checkActiveVMSnapshotTasks(Long vmId){ + List activeVMSnapshots = _vmSnapshotDao.listByInstanceId(vmId, + VMSnapshot.State.Creating, VMSnapshot.State.Expunging,VMSnapshot.State.Reverting,VMSnapshot.State.Allocated); + return activeVMSnapshots.size() > 0; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_SNAPSHOT_DELETE, eventDescription = "delete_vm_snapshot", async=true) + public boolean deleteVMSnapshot(DeleteVMSnapshotCmd cmd) { + Long vmSnapshotId = cmd.getId(); + Account caller = getCaller(); + + VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(vmSnapshotId); + if (vmSnapshot == null) { + throw new InvalidParameterValueException("unable to find the vm snapshot with id " + vmSnapshotId); + } + + _accountMgr.checkAccess(caller, null, true, vmSnapshot); + + // check VM snapshot states, only allow to delete vm snapshots in created and error state + if (VMSnapshot.State.Ready != vmSnapshot.getState() && VMSnapshot.State.Error != vmSnapshot.getState()) { + throw new InvalidParameterValueException("Can't delete the vm snapshotshot " + vmSnapshotId + " due to it is not in Created or Error State"); + } + + // check if there are other active VM snapshot tasks + if (checkActiveVMSnapshotTasks(vmSnapshot.getVmId())) { + throw new InvalidParameterValueException("There is other active vm snapshot tasks on the instance, please try again later"); + } + + if(vmSnapshot.getState() == VMSnapshot.State.Allocated){ + return _vmSnapshotDao.remove(vmSnapshot.getId()); + }else{ + return deleteSnapshotInternal(vmSnapshot); + } + } + + @DB + protected boolean deleteSnapshotInternal(VMSnapshotVO vmSnapshot) { + UserVmVO userVm = _userVMDao.findById(vmSnapshot.getVmId()); + + try { + vmSnapshotStateTransitTo(vmSnapshot,VMSnapshot.Event.ExpungeRequested); + Long hostId = pickRunningHost(vmSnapshot.getVmId()); + + // prepare snapshotVolumeTos + List volumeTOs = getVolumeTOList(vmSnapshot.getVmId()); + + // prepare DeleteVMSnapshotCommand + String vmInstanceName = userVm.getInstanceName(); + VMSnapshotTO parent = getSnapshotWithParents(vmSnapshot).getParent(); + VMSnapshotTO vmSnapshotTO = new VMSnapshotTO(vmSnapshot.getId(), vmSnapshot.getName(), vmSnapshot.getType(), + vmSnapshot.getCreated().getTime(), vmSnapshot.getDescription(), vmSnapshot.getCurrent(), parent); + GuestOSVO guestOS = _guestOSDao.findById(userVm.getGuestOSId()); + DeleteVMSnapshotCommand deleteSnapshotCommand = new DeleteVMSnapshotCommand(vmInstanceName, vmSnapshotTO, volumeTOs,guestOS.getDisplayName()); + + DeleteVMSnapshotAnswer answer = (DeleteVMSnapshotAnswer) sendToPool(hostId, deleteSnapshotCommand); + + if (answer != null && answer.getResult()) { + processAnswer(vmSnapshot, userVm, answer, hostId); + return true; + } else { + s_logger.error("Delete vm snapshot " + vmSnapshot.getName() + " of vm " + userVm.getInstanceName() + " failed due to " + answer.getDetails()); + return false; + } + } catch (Exception e) { + String msg = "Delete vm snapshot " + vmSnapshot.getName() + " of vm " + userVm.getInstanceName() + " failed due to " + e.getMessage(); + s_logger.error(msg , e); + throw new CloudRuntimeException(e.getMessage()); + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_SNAPSHOT_REVERT, eventDescription = "revert_vm", async = true) + public UserVm revertToSnapshot(RevertToSnapshotCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException { + + Long vmSnapshotId = cmd.getVmSnapShotId(); + + // check if VM snapshot exists in DB + VMSnapshotVO vmSnapshotVo = _vmSnapshotDao.findById(vmSnapshotId); + if (vmSnapshotVo == null) { + throw new InvalidParameterValueException( + "unable to find the vm snapshot with id " + vmSnapshotId); + } + Long vmId = vmSnapshotVo.getVmId(); + UserVmVO userVm = _userVMDao.findById(vmId); + // check if VM exists + if (userVm == null) { + throw new InvalidParameterValueException("Revert vm to snapshot: " + + vmSnapshotId + " failed due to vm: " + vmId + + " is not found"); + } + + // check if there are other active VM snapshot tasks + if (checkActiveVMSnapshotTasks(vmId)) { + throw new InvalidParameterValueException("There is other active vm snapshot tasks on the instance, please try again later"); + } + + Account caller = getCaller(); + _accountMgr.checkAccess(caller, null, true, vmSnapshotVo); + + // VM should be in running or stopped states + if (userVm.getState() != VirtualMachine.State.Running + && userVm.getState() != VirtualMachine.State.Stopped) { + throw new InvalidParameterValueException( + "VM Snapshot reverting failed due to vm is not in the state of Running or Stopped."); + } + + // if snapshot is not created, error out + if (vmSnapshotVo.getState() != VMSnapshot.State.Ready) { + throw new InvalidParameterValueException( + "VM Snapshot reverting failed due to vm snapshot is not in the state of Created."); + } + + UserVO callerUser = _userDao.findById(UserContext.current().getCallerUserId()); + + UserVmVO vm = null; + Long hostId = null; + Account owner = _accountDao.findById(vmSnapshotVo.getAccountId()); + + // start or stop VM first, if revert from stopped state to running state, or from running to stopped + if(userVm.getState() == VirtualMachine.State.Stopped && vmSnapshotVo.getType() == VMSnapshot.Type.DiskAndMemory){ + try { + vm = _itMgr.advanceStart(userVm, new HashMap(), callerUser, owner); + hostId = vm.getHostId(); + } catch (Exception e) { + s_logger.error("Start VM " + userVm.getInstanceName() + " before reverting failed due to " + e.getMessage()); + throw new CloudRuntimeException(e.getMessage()); + } + }else { + if(userVm.getState() == VirtualMachine.State.Running && vmSnapshotVo.getType() == VMSnapshot.Type.Disk){ + try { + _itMgr.advanceStop(userVm, true, callerUser, owner); + } catch (Exception e) { + s_logger.error("Stop VM " + userVm.getInstanceName() + " before reverting failed due to " + e.getMessage()); + throw new CloudRuntimeException(e.getMessage()); + } + } + hostId = pickRunningHost(userVm.getId()); + } + + if(hostId == null) + throw new CloudRuntimeException("Can not find any host to revert snapshot " + vmSnapshotVo.getName()); + + // check if there are other active VM snapshot tasks + if (checkActiveVMSnapshotTasks(userVm.getId())) { + throw new InvalidParameterValueException("There is other active vm snapshot tasks on the instance, please try again later"); + } + + userVm = _userVMDao.findById(userVm.getId()); + transitState(vmSnapshotVo, VMSnapshot.Event.RevertRequested, userVm, VirtualMachine.Event.RevertRequested, hostId); + return revertInternal(userVm, vmSnapshotVo, hostId); + } + + private UserVm revertInternal(UserVmVO userVm, VMSnapshotVO vmSnapshotVo, Long hostId) { + + try { + + VMSnapshotVO snapshot = _vmSnapshotDao.findById(vmSnapshotVo.getId()); + // prepare RevertToSnapshotCommand + List volumeTOs = getVolumeTOList(userVm.getId()); + String vmInstanceName = userVm.getInstanceName(); + VMSnapshotTO parent = getSnapshotWithParents(snapshot).getParent(); + VMSnapshotTO vmSnapshotTO = new VMSnapshotTO(snapshot.getId(), snapshot.getName(), snapshot.getType(), + snapshot.getCreated().getTime(), snapshot.getDescription(), snapshot.getCurrent(), parent); + + GuestOSVO guestOS = _guestOSDao.findById(userVm.getGuestOSId()); + RevertToVMSnapshotCommand revertToSnapshotCommand = new RevertToVMSnapshotCommand(vmInstanceName, vmSnapshotTO, volumeTOs, guestOS.getDisplayName()); + + RevertToVMSnapshotAnswer answer = (RevertToVMSnapshotAnswer) sendToPool(hostId, revertToSnapshotCommand); + if (answer.getResult()) { + processAnswer(vmSnapshotVo, userVm, answer, hostId); + transitState(vmSnapshotVo, VMSnapshot.Event.OperationSucceeded, userVm, VirtualMachine.Event.OperationSucceeded, hostId); + } else { + String errMsg = "Revert VM: " + userVm.getInstanceName() + " to snapshot: "+ vmSnapshotVo.getName() + " failed"; + if(answer != null && answer.getDetails() != null) + errMsg = errMsg + " due to " + answer.getDetails(); + s_logger.error(errMsg); + // agent report revert operation fails + transitState(vmSnapshotVo, VMSnapshot.Event.OperationFailed, userVm, VirtualMachine.Event.OperationFailed, hostId); + throw new CloudRuntimeException(errMsg); + } + } catch (Exception e) { + // for other exceptions, do not change VM state, leave it for snapshotSync + String errMsg = "revert vm: " + userVm.getInstanceName() + " to snapshot " + vmSnapshotVo.getName() + " failed due to " + e.getMessage(); + s_logger.error(errMsg); + throw new CloudRuntimeException(e.getMessage()); + } + return userVm; + } + + private boolean vmStateTransitTo(VMInstanceVO vm, VirtualMachine.Event e, Long hostId, String reservationId) throws NoTransitionException { + vm.setReservationId(reservationId); + return _stateMachine.transitTo(vm, e, new Pair(vm.getHostId(), hostId), _vmInstanceDao); + } + + @Override + public VMSnapshot getVMSnapshotById(long id) { + VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(id); + return vmSnapshot; + } + + private Long pickRunningHost(Long vmId) { + UserVmVO vm = _userVMDao.findById(vmId); + // use VM's host if VM is running + if(vm.getState() == State.Running) + return vm.getHostId(); + + // check if lastHostId is available + if(vm.getLastHostId() != null){ + HostVO lastHost = _hostDao.findById(vm.getLastHostId()); + if(lastHost.getStatus() == com.cloud.host.Status.Up && !lastHost.isInMaintenanceStates()) + return lastHost.getId(); + } + + List listVolumes = _volumeDao.findByInstance(vmId); + if (listVolumes == null || listVolumes.size() == 0) { + throw new InvalidParameterValueException("vmInstance has no volumes"); + } + VolumeVO volume = listVolumes.get(0); + Long poolId = volume.getPoolId(); + if (poolId == null) { + throw new InvalidParameterValueException("pool id is not found"); + } + StoragePoolVO storagePool = _storagePoolDao.findById(poolId); + if (storagePool == null) { + throw new InvalidParameterValueException("storage pool is not found"); + } + List listHost = _hostDao.listAllUpAndEnabledNonHAHosts(Host.Type.Routing, storagePool.getClusterId(), storagePool.getPodId(), + storagePool.getDataCenterId(), null); + if (listHost == null || listHost.size() == 0) { + throw new InvalidParameterValueException("no host in up state is found"); + } + return listHost.get(0).getId(); + } + + @Override + public VirtualMachine getVMBySnapshotId(Long id) { + VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(id); + Long vmId = vmSnapshot.getVmId(); + UserVmVO vm = _userVMDao.findById(vmId); + return vm; + } + + @Override + public boolean deleteAllVMSnapshots(long vmId, VMSnapshot.Type type) { + boolean result = true; + List listVmSnapshots = _vmSnapshotDao.findByVm(vmId); + if (listVmSnapshots == null || listVmSnapshots.isEmpty()) { + return true; + } + for (VMSnapshotVO snapshot : listVmSnapshots) { + if(type != null && snapshot.getType() != type) + continue; + if (!deleteSnapshotInternal(snapshot)) { + result = false; + break; + } + } + return result; + } + + @Override + public boolean syncVMSnapshot(VMInstanceVO vm, VMSnapshotVO vmSnapshot, Long hostId) { + try{ + + UserVmVO userVm = _userVMDao.findById(vm.getId()); + if(userVm == null) + return false; + if(userVm.getState() == State.RevertingToRunning || userVm.getState() == State.RevertingToStopped){ + List vmSnapshots = _vmSnapshotDao.listByInstanceId(userVm.getId(), VMSnapshot.State.Reverting); + if(vmSnapshots.size() != 1){ + s_logger.error("VM:" + userVm.getInstanceName()+ " expected to have one VM snapshot in reverting state but actually has " + vmSnapshots.size()); + return false; + } + // send revert command again + revertInternal(userVm, vmSnapshots.get(0), hostId); + return true; + }else if (userVm.getState() == State.RunningSnapshotting || userVm.getState() == State.StoppedSnapshotting){ + List vmSnapshots = _vmSnapshotDao.listByInstanceId(userVm.getId(), VMSnapshot.State.Creating); + if(vmSnapshots.size() != 1){ + s_logger.error("VM:" + userVm.getInstanceName()+ " expected to have one VM snapshot in creating state but actually has " + vmSnapshots.size()); + return false; + } + // send create vm snapshot command again + createVmSnapshotInternal(userVm, vmSnapshots.get(0), hostId); + return true; + + }else if (vmSnapshot != null && vmSnapshot.getState() == VMSnapshot.State.Expunging){ + return deleteSnapshotInternal(vmSnapshot); + } + }catch (Exception e) { + s_logger.error(e.getMessage(),e); + return false; + } + return false; + } + +} diff --git a/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDao.java b/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDao.java new file mode 100644 index 00000000000..7532edf03f7 --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDao.java @@ -0,0 +1,39 @@ +// 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.vm.snapshot.dao; + +import java.util.List; + +import com.cloud.utils.db.GenericDao; +import com.cloud.utils.fsm.StateDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; + +public interface VMSnapshotDao extends GenericDao, StateDao { + + List findByVm(Long vmId); + + List listExpungingSnapshot(); + + List listByInstanceId(Long vmId, VMSnapshot.State... status); + + VMSnapshotVO findCurrentSnapshotByVmId(Long vmId); + + List listByParent(Long vmSnapshotId); + + VMSnapshotVO findByName(Long vm_id, String name); +} diff --git a/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java b/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java new file mode 100644 index 00000000000..5e592d0d4dc --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java @@ -0,0 +1,160 @@ +// 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.vm.snapshot.dao; + +import java.util.Date; +import java.util.List; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.db.UpdateBuilder; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshot.Event; +import com.cloud.vm.snapshot.VMSnapshot.State; +import com.cloud.vm.snapshot.VMSnapshotVO; + +@Local(value = { VMSnapshotDao.class }) +public class VMSnapshotDaoImpl extends GenericDaoBase + implements VMSnapshotDao { + private static final Logger s_logger = Logger.getLogger(VMSnapshotDaoImpl.class); + private final SearchBuilder SnapshotSearch; + private final SearchBuilder ExpungingSnapshotSearch; + private final SearchBuilder SnapshotStatusSearch; + private final SearchBuilder AllFieldsSearch; + + protected VMSnapshotDaoImpl() { + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), Op.EQ); + AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), Op.EQ); + AllFieldsSearch.and("vm_id", AllFieldsSearch.entity().getVmId(), Op.EQ); + AllFieldsSearch.and("deviceId", AllFieldsSearch.entity().getVmId(), Op.EQ); + AllFieldsSearch.and("id", AllFieldsSearch.entity().getId(), Op.EQ); + AllFieldsSearch.and("removed", AllFieldsSearch.entity().getState(), Op.EQ); + AllFieldsSearch.and("parent", AllFieldsSearch.entity().getParent(), Op.EQ); + AllFieldsSearch.and("current", AllFieldsSearch.entity().getCurrent(), Op.EQ); + AllFieldsSearch.and("vm_snapshot_type", AllFieldsSearch.entity().getType(), Op.EQ); + AllFieldsSearch.and("updatedCount", AllFieldsSearch.entity().getUpdatedCount(), Op.EQ); + AllFieldsSearch.and("display_name", AllFieldsSearch.entity().getDisplayName(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), SearchCriteria.Op.EQ); + AllFieldsSearch.done(); + + SnapshotSearch = createSearchBuilder(); + SnapshotSearch.and("vm_id", SnapshotSearch.entity().getVmId(), + SearchCriteria.Op.EQ); + SnapshotSearch.done(); + + ExpungingSnapshotSearch = createSearchBuilder(); + ExpungingSnapshotSearch.and("state", ExpungingSnapshotSearch.entity() + .getState(), SearchCriteria.Op.EQ); + ExpungingSnapshotSearch.and("removed", ExpungingSnapshotSearch.entity() + .getRemoved(), SearchCriteria.Op.NULL); + ExpungingSnapshotSearch.done(); + + SnapshotStatusSearch = createSearchBuilder(); + SnapshotStatusSearch.and("vm_id", SnapshotStatusSearch.entity() + .getVmId(), SearchCriteria.Op.EQ); + SnapshotStatusSearch.and("state", SnapshotStatusSearch.entity() + .getState(), SearchCriteria.Op.IN); + SnapshotStatusSearch.done(); + } + + @Override + public List findByVm(Long vmId) { + SearchCriteria sc = SnapshotSearch.create(); + sc.setParameters("vm_id", vmId); + return listBy(sc, null); + } + + @Override + public List listExpungingSnapshot() { + SearchCriteria sc = ExpungingSnapshotSearch.create(); + sc.setParameters("state", State.Expunging); + return listBy(sc, null); + } + + @Override + public List listByInstanceId(Long vmId, State... status) { + SearchCriteria sc = SnapshotStatusSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("state", (Object[]) status); + return listBy(sc, null); + } + + @Override + public VMSnapshotVO findCurrentSnapshotByVmId(Long vmId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("current", 1); + return findOneBy(sc); + } + + @Override + public List listByParent(Long vmSnapshotId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("parent", vmSnapshotId); + sc.setParameters("state", State.Ready ); + return listBy(sc, null); + } + + @Override + public VMSnapshotVO findByName(Long vm_id, String name) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vm_id", vm_id); + sc.setParameters("display_name", name ); + return null; + } + + @Override + public boolean updateState(State currentState, Event event, State nextState, VMSnapshot vo, Object data) { + + Long oldUpdated = vo.getUpdatedCount(); + Date oldUpdatedTime = vo.getUpdated(); + + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("id", vo.getId()); + sc.setParameters("state", currentState); + sc.setParameters("updatedCount", vo.getUpdatedCount()); + + vo.incrUpdatedCount(); + + UpdateBuilder builder = getUpdateBuilder(vo); + builder.set(vo, "state", nextState); + builder.set(vo, "updated", new Date()); + + int rows = update((VMSnapshotVO)vo, sc); + if (rows == 0 && s_logger.isDebugEnabled()) { + VMSnapshotVO dbVol = findByIdIncludingRemoved(vo.getId()); + if (dbVol != null) { + StringBuilder str = new StringBuilder("Unable to update ").append(vo.toString()); + str.append(": DB Data={id=").append(dbVol.getId()).append("; state=").append(dbVol.getState()).append("; updatecount=").append(dbVol.getUpdatedCount()).append(";updatedTime=").append(dbVol.getUpdated()); + str.append(": New Data={id=").append(vo.getId()).append("; state=").append(nextState).append("; event=").append(event).append("; updatecount=").append(vo.getUpdatedCount()).append("; updatedTime=").append(vo.getUpdated()); + str.append(": stale Data={id=").append(vo.getId()).append("; state=").append(currentState).append("; event=").append(event).append("; updatecount=").append(oldUpdated).append("; updatedTime=").append(oldUpdatedTime); + } else { + s_logger.debug("Unable to update VM snapshot: id=" + vo.getId() + ", as there is no such snapshot exists in the database anymore"); + } + } + return rows > 0; + } + +} diff --git a/server/test/com/cloud/vm/snapshot/VMSnapshotManagerTest.java b/server/test/com/cloud/vm/snapshot/VMSnapshotManagerTest.java new file mode 100644 index 00000000000..fe49207b491 --- /dev/null +++ b/server/test/com/cloud/vm/snapshot/VMSnapshotManagerTest.java @@ -0,0 +1,182 @@ +// 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.vm.snapshot; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.command.user.vmsnapshot.CreateVMSnapshotCmd; +import org.junit.Test; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.CreateVMSnapshotAnswer; +import com.cloud.agent.api.CreateVMSnapshotCommand; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.StoragePoolDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + + + +public class VMSnapshotManagerTest { + @Spy VMSnapshotManagerImpl _vmSnapshotMgr = new VMSnapshotManagerImpl(); + @Mock Account admin; + @Mock VMSnapshotDao _vmSnapshotDao; + @Mock VolumeDao _volumeDao; + @Mock AccountDao _accountDao; + @Mock VMInstanceDao _vmInstanceDao; + @Mock UserVmDao _userVMDao; + @Mock HostDao _hostDao; + @Mock UserDao _userDao; + @Mock AgentManager _agentMgr; + @Mock HypervisorGuruManager _hvGuruMgr; + @Mock AccountManager _accountMgr; + @Mock GuestOSDao _guestOSDao; + @Mock StoragePoolDao _storagePoolDao; + @Mock SnapshotDao _snapshotDao; + @Mock VirtualMachineManager _itMgr; + @Mock ConfigurationDao _configDao; + int _vmSnapshotMax = 10; + + @Mock CreateVMSnapshotCmd createCmd; + + private static long TEST_VM_ID = 3L; + @Mock UserVmVO vmMock; + @Mock VolumeVO volumeMock; + + @Before + public void setup(){ + MockitoAnnotations.initMocks(this); + doReturn(admin).when(_vmSnapshotMgr).getCaller(); + _vmSnapshotMgr._accountDao = _accountDao; + _vmSnapshotMgr._userVMDao = _userVMDao; + _vmSnapshotMgr._vmSnapshotDao = _vmSnapshotDao; + _vmSnapshotMgr._volumeDao = _volumeDao; + _vmSnapshotMgr._accountMgr = _accountMgr; + _vmSnapshotMgr._snapshotDao = _snapshotDao; + _vmSnapshotMgr._guestOSDao = _guestOSDao; + + doNothing().when(_accountMgr).checkAccess(any(Account.class), any(AccessType.class), + any(Boolean.class), any(ControlledEntity.class)); + + _vmSnapshotMgr._vmSnapshotMax = _vmSnapshotMax; + + when(createCmd.getVmId()).thenReturn(TEST_VM_ID); + when(createCmd.getDisplayName()).thenReturn("testName"); + + when(_userVMDao.findById(anyLong())).thenReturn(vmMock); + when(_vmSnapshotDao.findByName(anyLong(), anyString())).thenReturn(null); + when(_vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList()); + + List mockVolumeList = new ArrayList(); + mockVolumeList.add(volumeMock); + when(volumeMock.getInstanceId()).thenReturn(TEST_VM_ID); + when(_volumeDao.findByInstance(anyLong())).thenReturn(mockVolumeList); + + when(vmMock.getInstanceName()).thenReturn("i-3-VM-TEST"); + when(vmMock.getState()).thenReturn(State.Running); + + when(_guestOSDao.findById(anyLong())).thenReturn(mock(GuestOSVO.class)); + } + + // vmId null case + @Test(expected=InvalidParameterValueException.class) + public void testAllocVMSnapshotF1() throws ResourceAllocationException{ + when(_userVMDao.findById(TEST_VM_ID)).thenReturn(null); + _vmSnapshotMgr.allocVMSnapshot(createCmd); + } + + // vm state not in [running, stopped] case + @Test(expected=InvalidParameterValueException.class) + public void testAllocVMSnapshotF2() throws ResourceAllocationException{ + when(vmMock.getState()).thenReturn(State.Starting); + _vmSnapshotMgr.allocVMSnapshot(createCmd); + } + + // max snapshot limit case + @SuppressWarnings("unchecked") + @Test(expected=CloudRuntimeException.class) + public void testAllocVMSnapshotF3() throws ResourceAllocationException{ + List mockList = mock(List.class); + when(mockList.size()).thenReturn(10); + when(_vmSnapshotDao.findByVm(TEST_VM_ID)).thenReturn(mockList); + _vmSnapshotMgr.allocVMSnapshot(createCmd); + } + + // active volume snapshots case + @SuppressWarnings("unchecked") + @Test(expected=CloudRuntimeException.class) + public void testAllocVMSnapshotF4() throws ResourceAllocationException{ + List mockList = mock(List.class); + when(mockList.size()).thenReturn(1); + when(_snapshotDao.listByInstanceId(TEST_VM_ID,Snapshot.Status.Creating, + Snapshot.Status.CreatedOnPrimary, Snapshot.Status.BackingUp)).thenReturn(mockList); + _vmSnapshotMgr.allocVMSnapshot(createCmd); + } + + @Test + public void testCreateVMSnapshot() throws AgentUnavailableException, OperationTimedoutException{ + when(_vmSnapshotDao.findCurrentSnapshotByVmId(anyLong())).thenReturn(null); + doReturn(new ArrayList()).when(_vmSnapshotMgr).getVolumeTOList(anyLong()); + doReturn(new CreateVMSnapshotAnswer(null,true,"")).when(_vmSnapshotMgr).sendToPool(anyLong(), any(CreateVMSnapshotCommand.class)); + doReturn(true).when(_vmSnapshotMgr).transitState(any(VMSnapshotVO.class), + any(VMSnapshot.Event.class), any(VMInstanceVO.class), any(VirtualMachine.Event.class), anyLong()); + doNothing().when(_vmSnapshotMgr).processAnswer(any(VMSnapshotVO.class), + any(UserVmVO.class), any(Answer.class), anyLong()); + _vmSnapshotMgr.createVmSnapshotInternal(vmMock, mock(VMSnapshotVO.class), 5L); + + } + +} diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index ead98a588fd..42d86bf0e88 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -2543,6 +2543,36 @@ INSERT INTO `cloud`.`counter` (id, uuid, source, name, value,created) VALUES (1, INSERT INTO `cloud`.`counter` (id, uuid, source, name, value,created) VALUES (2, UUID(), 'snmp','Linux System CPU - percentage', '1.3.6.1.4.1.2021.11.10.0', now()); INSERT INTO `cloud`.`counter` (id, uuid, source, name, value,created) VALUES (3, UUID(), 'snmp','Linux CPU Idle - percentage', '1.3.6.1.4.1.2021.11.11.0', now()); INSERT INTO `cloud`.`counter` (id, uuid, source, name, value,created) VALUES (100, UUID(), 'netscaler','Response Time - microseconds', 'RESPTIME', now()); +CREATE TABLE `cloud`.`vm_snapshots` ( + `id` bigint(20) unsigned NOT NULL auto_increment COMMENT 'Primary Key', + `uuid` varchar(40) NOT NULL, + `name` varchar(255) NOT NULL, + `display_name` varchar(255) default NULL, + `description` varchar(255) default NULL, + `vm_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, + `vm_snapshot_type` varchar(32) default NULL, + `state` varchar(32) NOT NULL, + `parent` bigint unsigned default NULL, + `current` int(1) unsigned default NULL, + `update_count` bigint unsigned NOT NULL DEFAULT 0, + `updated` datetime default NULL, + `created` datetime default NULL, + `removed` datetime default NULL, + PRIMARY KEY (`id`), + CONSTRAINT UNIQUE KEY `uc_vm_snapshots_uuid` (`uuid`), + INDEX `vm_snapshots_name` (`name`), + INDEX `vm_snapshots_vm_id` (`vm_id`), + INDEX `vm_snapshots_account_id` (`account_id`), + INDEX `vm_snapshots_display_name` (`display_name`), + INDEX `vm_snapshots_removed` (`removed`), + INDEX `vm_snapshots_parent` (`parent`), + CONSTRAINT `fk_vm_snapshots_vm_id__vm_instance_id` FOREIGN KEY `fk_vm_snapshots_vm_id__vm_instance_id` (`vm_id`) REFERENCES `vm_instance` (`id`), + CONSTRAINT `fk_vm_snapshots_account_id__account_id` FOREIGN KEY `fk_vm_snapshots_account_id__account_id` (`account_id`) REFERENCES `account` (`id`), + CONSTRAINT `fk_vm_snapshots_domain_id__domain_id` FOREIGN KEY `fk_vm_snapshots_domain_id__domain_id` (`domain_id`) REFERENCES `domain` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + SET foreign_key_checks = 1; diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index eb4284374de..bca4b10b046 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -1931,7 +1931,7 @@ div.detail-group.actions td { } .detail-group table td.detail-actions { - width: 55%; + width: 59%; height: 26px; } diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp index 203c9daa52e..2a08e6d69f7 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -1431,6 +1431,16 @@ dictionary = { 'label.delete.NiciraNvp': '', 'label.nicira.controller.address': '', 'label.nicira.transportzoneuuid': '', -'label.nicira.l3gatewayserviceuuid': '' +'label.nicira.l3gatewayserviceuuid': '', +'label.vmsnapshot.current': '', +'label.vmsnapshot.parentname': '', +'label.vmsnapshot.type': '', +'label.vmsnapshot.memory': '', +'label.vmsnapshot': '', +'label.action.vmsnapshot.create': '', +'label.action.vmsnapshot.delete': '', +'label.action.vmsnapshot.revert': '', +'message.action.vmsnapshot.delete': '', +'message.action.vmsnapshot.revert': '' }; diff --git a/ui/index.jsp b/ui/index.jsp index 5481da80130..582193714c9 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -1673,6 +1673,8 @@ under the License. + + diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 5b5ed1853e7..d9542a1a330 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -48,7 +48,15 @@ displayname: { label: 'label.display.name' }, zonename: { label: 'label.zone.name' }, state: { - label: 'label.state', + label: 'label.state', + converter: function(data) { + if(data == 'StoppedSnapshotting' || data == 'RunningSnapshotting') + return 'Snapshotting'; + if(data == 'RevertingToRunning' || data == 'RevertingToStopped'){ + return 'Reverting'; + } + return data; + }, indicator: { 'Running': 'on', 'Stopped': 'off', @@ -153,6 +161,194 @@ notification: { poll: pollAsyncJobResult } + }, + start: { + label: 'label.action.start.instance' , + action: function(args) { + $.ajax({ + url: createURL("startVirtualMachine&id=" + args.context.instances[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.startvirtualmachineresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.virtualmachine; + }, + getActionFilter: function() { + return vmActionfilter; + } + } + } + ); + } + }); + }, + messages: { + confirm: function(args) { + return 'message.action.start.instance'; + }, + notification: function(args) { + return 'label.action.start.instance'; + }, + complete: function(args) { + if(args.password != null) { + alert('Password of the VM is ' + args.password); + } + return 'label.action.start.instance'; + } + }, + notification: { + poll: pollAsyncJobResult + } + }, + stop: { + label: 'label.action.stop.instance', + addRow: 'false', + createForm: { + title: 'label.action.stop.instance', + desc: 'message.action.stop.instance', + fields: { + forced: { + label: 'force.stop', + isBoolean: true, + isChecked: false + } + } + }, + action: function(args) { + var array1 = []; + array1.push("&forced=" + (args.data.forced == "on")); + $.ajax({ + url: createURL("stopVirtualMachine&id=" + args.context.instances[0].id + array1.join("")), + dataType: "json", + async: true, + success: function(json) { + var jid = json.stopvirtualmachineresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.virtualmachine; + }, + getActionFilter: function() { + return vmActionfilter; + } + } + } + ); + } + }); + }, + messages: { + confirm: function(args) { + return 'message.action.stop.instance'; + }, + + notification: function(args) { + return 'label.action.stop.instance'; + } + }, + notification: { + poll: pollAsyncJobResult + } + }, + restart: { + label: 'instances.actions.reboot.label', + action: function(args) { + $.ajax({ + url: createURL("rebootVirtualMachine&id=" + args.context.instances[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.rebootvirtualmachineresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.virtualmachine; + }, + getActionFilter: function() { + return vmActionfilter; + } + } + } + ); + } + }); + }, + messages: { + confirm: function(args) { + return 'message.action.reboot.instance'; + }, + notification: function(args) { + return 'instances.actions.reboot.label'; + } + }, + notification: { + poll: pollAsyncJobResult + } + }, + + destroy: { + label: 'label.action.destroy.instance', + messages: { + confirm: function(args) { + return 'message.action.destroy.instance'; + }, + notification: function(args) { + return 'label.action.destroy.instance'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("destroyVirtualMachine&id=" + args.context.instances[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.destroyvirtualmachineresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.virtualmachine; + }, + getActionFilter: function() { + return vmActionfilter; + } + } + } + ); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + restore: { + label: 'label.action.restore.instance', + messages: { + confirm: function(args) { + return 'message.action.restore.instance'; + }, + notification: function(args) { + return 'label.action.restore.instance'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("recoverVirtualMachine&id=" + args.context.instances[0].id), + dataType: "json", + async: true, + success: function(json) { + var item = json.recovervirtualmachineresponse.virtualmachine; + args.response.success({data:item}); + } + }); + } } }, @@ -231,7 +427,7 @@ detailView: { name: 'Instance details', - viewAll: { path: 'storage.volumes', label: 'label.volumes' }, + viewAll: [{ path: 'storage.volumes', label: 'label.volumes' }, { path: 'vmsnapshots', label: 'Snapshots' } ], tabFilter: function(args) { var hiddenTabs = []; @@ -409,6 +605,70 @@ poll: pollAsyncJobResult } }, + + snapshot: { + messages: { + notification: function(args) { + return 'label.action.vmsnapshot.create'; + } + }, + label: 'label.action.vmsnapshot.create', + addRow: 'false', + createForm: { + title: 'label.action.vmsnapshot.create', + fields: { + name: { + label: 'label.name', + isInput: true + }, + description: { + label: 'label.description', + isTextarea: true + }, + snapshotMemory: { + label: 'label.vmsnapshot.memory', + isBoolean: true, + isChecked: false + } + } + }, + action: function(args) { + var array1 = []; + array1.push("&snapshotmemory=" + (args.data.snapshotMemory == "on")); + var displayname = args.data.name; + if (displayname != null && displayname.length > 0) { + array1.push("&name=" + todb(displayname)); + } + var description = args.data.description; + if (description != null && description.length > 0) { + array1.push("&description=" + todb(description)); + } + $.ajax({ + url: createURL("createVMSnapshot&virtualmachineid=" + args.context.instances[0].id + array1.join("")), + dataType: "json", + async: true, + success: function(json) { + var jid = json.createvmsnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.virtualmachine; + }, + getActionFilter: function() { + return vmActionfilter; + } + } + }); + } + }); + + }, + notification: { + pool: pollAsyncJobResult + } + }, + destroy: { label: 'label.action.destroy.instance', compactLabel: 'label.destroy', @@ -1232,6 +1492,7 @@ else if (jsonObj.state == 'Running') { allowedActions.push("stop"); allowedActions.push("restart"); + allowedActions.push("snapshot"); allowedActions.push("destroy"); allowedActions.push("changeService"); allowedActions.push("reset"); @@ -1257,7 +1518,7 @@ allowedActions.push("start"); allowedActions.push("destroy"); allowedActions.push("reset"); - + allowedActions.push("snapshot"); if(isAdmin()) allowedActions.push("migrateToAnotherStorage"); diff --git a/ui/scripts/ui/widgets/detailView.js b/ui/scripts/ui/widgets/detailView.js index 4b4fbd5a477..5f0c0f0ee4b 100644 --- a/ui/scripts/ui/widgets/detailView.js +++ b/ui/scripts/ui/widgets/detailView.js @@ -892,14 +892,20 @@ $actions.prependTo($firstRow.closest('div.detail-group').closest('.details')); } if (detailViewArgs.viewAll && showViewAll) { + + if( !(detailViewArgs.viewAll instanceof Array)){ + detailViewArgs.viewAll = [detailViewArgs.viewAll]; + } + $.each(detailViewArgs.viewAll, function(n, view){ $('
') .addClass('view-all') .append( $('') .attr({ href: '#' }) - .data('detail-view-link-view-all', detailViewArgs.viewAll) + .css('padding','0 1px') + .data('detail-view-link-view-all', view) .append( - $('').html(_l('label.view') + ' ' + _l(detailViewArgs.viewAll.label)) + $('').html(_l('label.view') + ' ' + _l(view.label)) ) ) .append( @@ -908,9 +914,10 @@ .appendTo( $('') .addClass('view-all') + .css('padding','9px 3px 8px 0') .appendTo($actions.find('tr')) ); - + }); } } diff --git a/ui/scripts/vm_snapshots.js b/ui/scripts/vm_snapshots.js new file mode 100644 index 00000000000..0d6305bfbe4 --- /dev/null +++ b/ui/scripts/vm_snapshots.js @@ -0,0 +1,196 @@ +// 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&id=" + 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("revertToSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.reverttosnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + } + } + } + //detailview end + } + } +})(jQuery, cloudStack); \ No newline at end of file diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostMO.java index a765b42fd78..40343b692c3 100755 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostMO.java @@ -58,6 +58,8 @@ import com.vmware.vim25.PropertySpec; import com.vmware.vim25.SelectionSpec; import com.vmware.vim25.TraversalSpec; import com.vmware.vim25.VirtualMachineConfigSpec; +import com.vmware.vim25.VirtualMachineSnapshotInfo; +import com.vmware.vim25.VirtualMachineSnapshotTree; import com.vmware.vim25.VirtualNicManagerNetConfig; import com.vmware.vim25.NasDatastoreInfo; @@ -949,4 +951,20 @@ public class HostMO extends BaseMO implements VmwareHypervisorHost { HostRuntimeInfo runtimeInfo = (HostRuntimeInfo)_context.getServiceUtil().getDynamicProperty(_mor, "runtime"); return runtimeInfo.getConnectionState() == HostSystemConnectionState.connected; } + + public boolean revertToSnapshot(ManagedObjectReference morSnapshot) + throws Exception { + ManagedObjectReference morTask = _context.getService() + .revertToSnapshot_Task(morSnapshot, _mor, false); + String result = _context.getServiceUtil().waitForTask(morTask); + if (result.equals("sucess")) { + _context.waitForTaskProgressDone(morTask); + return true; + } else { + s_logger.error("VMware revert to snapshot failed due to " + + TaskMO.getTaskFailureInfo(_context, morTask)); + } + + return false; + } } diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java index cd54127fcc2..d36814d9148 100644 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java @@ -405,6 +405,26 @@ public class VirtualMachineMO extends BaseMO { return false; } + public boolean revertToSnapshot(String snapshotName) throws Exception { + ManagedObjectReference morSnapshot = getSnapshotMor(snapshotName); + if (morSnapshot == null) { + s_logger.warn("Unable to find snapshot: " + snapshotName); + return false; + } + ManagedObjectReference morTask = _context.getService() + .revertToSnapshot_Task(morSnapshot, _mor, null); + String result = _context.getServiceUtil().waitForTask(morTask); + if (result.equals("sucess")) { + _context.waitForTaskProgressDone(morTask); + return true; + } else { + s_logger.error("VMware revert to snapshot failed due to " + + TaskMO.getTaskFailureInfo(_context, morTask)); + } + + return false; + } + public boolean removeAllSnapshots() throws Exception { VirtualMachineSnapshotInfo snapshotInfo = getSnapshotInfo();