From 9a12756ae42f0bf372dcc9d86b2a60710104075a Mon Sep 17 00:00:00 2001 From: Mice Xia Date: Thu, 14 Feb 2013 01:12:35 +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 | 6 +- api/src/com/cloud/vm/snapshot/VMSnapshot.java | 110 +++ .../cloud/vm/snapshot/VMSnapshotService.java | 48 + .../apache/cloudstack/api/ApiConstants.java | 5 + .../cloudstack/api/ApiConstants.java.orig | 468 ++++++++++ .../org/apache/cloudstack/api/BaseCmd.java | 2 + .../cloudstack/api/ResponseGenerator.java | 5 +- .../user/vmsnapshot/CreateVMSnapshotCmd.java | 125 +++ .../user/vmsnapshot/DeleteVMSnapshotCmd.java | 85 ++ .../user/vmsnapshot/ListVMSnapshotCmd.java | 89 ++ .../user/vmsnapshot/RevertToSnapshotCmd.java | 92 ++ .../api/response/VMSnapshotResponse.java | 220 +++++ .../classes/resources/messages.properties | 14 + client/tomcatconf/commands.properties.in | 6 + .../vmware/manager/VmwareStorageManager.java | 16 +- .../manager/VmwareStorageManagerImpl.java | 332 ++++++- .../vmware/resource/VmwareResource.java | 50 +- .../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 | 29 +- .../cloud/capacity/CapacityManagerImpl.java | 43 +- .../src/com/cloud/configuration/Config.java | 11 +- .../cloud/server/ManagementServerImpl.java | 8 + .../storage/snapshot/SnapshotManagerImpl.java | 14 +- .../cloud/tags/TaggedResourceManagerImpl.java | 4 + .../src/com/cloud/vm/UserVmManagerImpl.java | 74 +- .../cloud/vm/VirtualMachineManagerImpl.java | 65 +- .../cloud/vm/snapshot/VMSnapshotManager.java | 47 + .../vm/snapshot/VMSnapshotManagerImpl.java | 833 ++++++++++++++++++ .../cloud/vm/snapshot/dao/VMSnapshotDao.java | 39 + .../vm/snapshot/dao/VMSnapshotDaoImpl.java | 161 ++++ .../vm/snapshot/VMSnapshotManagerTest.java | 186 ++++ setup/db/create-schema.sql | 30 + ui/css/cloudstack3.css | 2 +- ui/dictionary.jsp | 12 +- ui/index.jsp | 2 + ui/scripts/instances.js | 259 +++++- 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 + 55 files changed, 4595 insertions(+), 80 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/ApiConstants.java.orig 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 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 a8846b96261..4cbe82b357b 100644 --- a/api/src/com/cloud/agent/api/to/VolumeTO.java +++ b/api/src/com/cloud/agent/api/to/VolumeTO.java @@ -124,6 +124,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 0dd97cb438c..0087edca743 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -331,6 +331,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.REVERTTO"; // 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 248b98247e3..4300dd548c1 100755 --- a/api/src/com/cloud/vm/VirtualMachine.java +++ b/api/src/com/cloud/vm/VirtualMachine.java @@ -112,7 +112,7 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, Identity, I s_fsm.addTransition(State.Error, VirtualMachine.Event.DestroyRequested, State.Expunging); s_fsm.addTransition(State.Error, VirtualMachine.Event.ExpungeOperation, State.Expunging); } - + public static boolean isVmStarted(State oldState, Event e, State newState) { if (oldState == State.Starting && newState == State.Running) { return true; @@ -174,7 +174,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..f0ee7ee7e8a --- /dev/null +++ b/api/src/com/cloud/vm/snapshot/VMSnapshot.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.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.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..83f86bc90db --- /dev/null +++ b/api/src/com/cloud/vm/snapshot/VMSnapshotService.java @@ -0,0 +1,48 @@ +// 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.ListVMSnapshotCmd; + +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(Long vmId, Long vmSnapshotId); + + VMSnapshot allocVMSnapshot(Long vmId, String vsDisplayName, String vsDescription, Boolean snapshotMemory) + throws ResourceAllocationException; + + boolean deleteVMSnapshot(Long vmSnapshotId); + + UserVm revertToSnapshot(Long vmSnapshotId) 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 d29408e66f2..cd7d700d2b5 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -438,6 +438,11 @@ public class ApiConstants { public static final String AUTOSCALE_USER_ID = "autoscaleuserid"; public static final String BAREMETAL_DISCOVER_NAME = "baremetaldiscovername"; public static final String UCS_DN = "ucsdn"; + 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/ApiConstants.java.orig b/api/src/org/apache/cloudstack/api/ApiConstants.java.orig new file mode 100644 index 00000000000..3801506ffaa --- /dev/null +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java.orig @@ -0,0 +1,468 @@ +// 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; + + +public class ApiConstants { + public static final String ACCOUNT = "account"; + public static final String ACCOUNTS = "accounts"; + public static final String ACCOUNT_TYPE = "accounttype"; + public static final String ACCOUNT_ID = "accountid"; + public static final String ALGORITHM = "algorithm"; + public static final String ALLOCATED_ONLY = "allocatedonly"; + public static final String API_KEY = "userapikey"; + public static final String APPLIED = "applied"; + public static final String AVAILABLE = "available"; + public static final String BITS = "bits"; + public static final String BOOTABLE = "bootable"; + public static final String BIND_DN = "binddn"; + public static final String BIND_PASSWORD = "bindpass"; + public static final String CATEGORY = "category"; + public static final String CERTIFICATE = "certificate"; + public static final String PRIVATE_KEY = "privatekey"; + public static final String DOMAIN_SUFFIX = "domainsuffix"; + public static final String DNS_SEARCH_ORDER = "dnssearchorder"; + public static final String CIDR = "cidr"; + public static final String IP6_CIDR = "ip6cidr"; + public static final String CIDR_LIST = "cidrlist"; + public static final String CLEANUP = "cleanup"; + public static final String CLUSTER_ID = "clusterid"; + public static final String CLUSTER_NAME = "clustername"; + public static final String CLUSTER_TYPE = "clustertype"; + public static final String COMPONENT = "component"; + public static final String CPU_NUMBER = "cpunumber"; + public static final String CPU_SPEED = "cpuspeed"; + public static final String CREATED = "created"; + public static final String CUSTOMIZED = "customized"; + public static final String DESCRIPTION = "description"; + public static final String DESTINATION_ZONE_ID = "destzoneid"; + public static final String DETAILS = "details"; + public static final String DEVICE_ID = "deviceid"; + public static final String DISK_OFFERING_ID = "diskofferingid"; + public static final String DISK_SIZE = "disksize"; + public static final String DISPLAY_NAME = "displayname"; + public static final String DISPLAY_TEXT = "displaytext"; + public static final String DNS1 = "dns1"; + public static final String DNS2 = "dns2"; + public static final String DOMAIN = "domain"; + public static final String DOMAIN_ID = "domainid"; + public static final String DURATION = "duration"; + public static final String EMAIL = "email"; + public static final String END_DATE = "enddate"; + public static final String END_IP = "endip"; + public static final String END_IPV6 = "endipv6"; + public static final String END_PORT = "endport"; + public static final String ENTRY_TIME = "entrytime"; + public static final String FETCH_LATEST = "fetchlatest"; + public static final String FIRSTNAME = "firstname"; + public static final String FORCED = "forced"; + public static final String FORCED_DESTROY_LOCAL_STORAGE = "forcedestroylocalstorage"; + public static final String FORMAT = "format"; + public static final String FOR_VIRTUAL_NETWORK = "forvirtualnetwork"; + public static final String GATEWAY = "gateway"; + public static final String IP6_GATEWAY = "ip6gateway"; + public static final String GROUP = "group"; + public static final String GROUP_ID = "groupid"; + public static final String GUEST_CIDR_ADDRESS = "guestcidraddress"; + public static final String HA_ENABLE = "haenable"; + public static final String HOST_ID = "hostid"; + public static final String HOST_NAME = "hostname"; + public static final String HYPERVISOR = "hypervisor"; + public static final String INLINE = "inline"; + public static final String INSTANCE = "instance"; + public static final String ICMP_CODE = "icmpcode"; + public static final String ICMP_TYPE = "icmptype"; + public static final String ID = "id"; + public static final String IDS = "ids"; + public static final String INTERNAL_DNS1 = "internaldns1"; + public static final String INTERNAL_DNS2 = "internaldns2"; + public static final String INTERVAL_TYPE = "intervaltype"; + public static final String IP_ADDRESS = "ipaddress"; + public static final String IP6_ADDRESS = "ip6address"; + public static final String IP_ADDRESS_ID = "ipaddressid"; + public static final String IS_ASYNC = "isasync"; + public static final String IP_AVAILABLE = "ipavailable"; + public static final String IP_LIMIT = "iplimit"; + public static final String IP_TOTAL = "iptotal"; + public static final String IS_CLEANUP_REQUIRED = "iscleanuprequired"; + public static final String IS_EXTRACTABLE = "isextractable"; + public static final String IS_FEATURED = "isfeatured"; + public static final String IS_PUBLIC = "ispublic"; + public static final String IS_PERSISTENT = "ispersistent"; + public static final String IS_READY = "isready"; + public static final String IS_RECURSIVE = "isrecursive"; + public static final String ISO_FILTER = "isofilter"; + public static final String ISO_GUEST_OS_NONE = "None"; + public static final String JOB_ID = "jobid"; + public static final String JOB_STATUS = "jobstatus"; + public static final String LASTNAME = "lastname"; + public static final String LEVEL = "level"; + public static final String LENGTH = "length"; + public static final String LIMIT_CPU_USE = "limitcpuuse"; + public static final String LOCK = "lock"; + public static final String LUN = "lun"; + public static final String LBID = "lbruleid"; + public static final String MAX = "max"; + public static final String MAX_SNAPS = "maxsnaps"; + public static final String MEMORY = "memory"; + public static final String MODE = "mode"; + public static final String NAME = "name"; + public static final String METHOD_NAME = "methodname"; + public static final String NETWORK_DOMAIN = "networkdomain"; + public static final String NETMASK = "netmask"; + public static final String NEW_NAME = "newname"; + public static final String NUM_RETRIES = "numretries"; + public static final String OFFER_HA = "offerha"; + public static final String IS_SYSTEM_OFFERING = "issystem"; + public static final String IS_DEFAULT_USE = "defaultuse"; + public static final String OP = "op"; + public static final String OS_CATEGORY_ID = "oscategoryid"; + public static final String OS_TYPE_ID = "ostypeid"; + public static final String PARAMS = "params"; + public static final String PARENT_DOMAIN_ID = "parentdomainid"; + public static final String PASSWORD = "password"; + public static final String NEW_PASSWORD = "new_password"; + public static final String PASSWORD_ENABLED = "passwordenabled"; + public static final String SSHKEY_ENABLED = "sshkeyenabled"; + public static final String PATH = "path"; + public static final String POD_ID = "podid"; + public static final String POD_IDS = "podids"; + public static final String POLICY_ID = "policyid"; + public static final String PORT = "port"; + public static final String PORTAL = "portal"; + public static final String PORT_FORWARDING_SERVICE_ID = "portforwardingserviceid"; + public static final String PRIVATE_INTERFACE = "privateinterface"; + public static final String PRIVATE_IP = "privateip"; + public static final String PRIVATE_PORT = "privateport"; + public static final String PRIVATE_START_PORT = "privateport"; + public static final String PRIVATE_END_PORT = "privateendport"; + public static final String PRIVATE_ZONE = "privatezone"; + public static final String PROTOCOL = "protocol"; + public static final String PUBLIC_INTERFACE = "publicinterface"; + public static final String PUBLIC_IP_ID = "publicipid"; + public static final String PUBLIC_IP = "publicip"; + public static final String PUBLIC_PORT = "publicport"; + public static final String PUBLIC_START_PORT = "publicport"; + public static final String PUBLIC_END_PORT = "publicendport"; + public static final String PUBLIC_ZONE = "publiczone"; + public static final String RECEIVED_BYTES = "receivedbytes"; + public static final String REQUIRES_HVM = "requireshvm"; + public static final String RESOURCE_TYPE = "resourcetype"; + public static final String RESPONSE = "response"; + public static final String QUERY_FILTER = "queryfilter"; + public static final String SCHEDULE = "schedule"; + public static final String SCOPE = "scope"; + public static final String SECRET_KEY = "usersecretkey"; + public static final String SINCE = "since"; + public static final String KEY = "key"; + public static final String SEARCH_BASE = "searchbase"; + public static final String SECURITY_GROUP_IDS = "securitygroupids"; + public static final String SECURITY_GROUP_NAMES = "securitygroupnames"; + public static final String SECURITY_GROUP_NAME = "securitygroupname"; + public static final String SECURITY_GROUP_ID = "securitygroupid"; + public static final String SENT = "sent"; + public static final String SENT_BYTES = "sentbytes"; + public static final String SERVICE_OFFERING_ID = "serviceofferingid"; + public static final String SHOW_CAPACITIES = "showcapacities"; + public static final String SIZE = "size"; + public static final String SNAPSHOT_ID = "snapshotid"; + public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid"; + public static final String SNAPSHOT_TYPE = "snapshottype"; + public static final String SOURCE_ZONE_ID = "sourcezoneid"; + public static final String START_DATE = "startdate"; + public static final String START_IP = "startip"; + public static final String START_IPV6 = "startipv6"; + public static final String START_PORT = "startport"; + public static final String STATE = "state"; + public static final String STATUS = "status"; + public static final String STORAGE_TYPE = "storagetype"; + public static final String SYSTEM_VM_TYPE = "systemvmtype"; + public static final String TAGS = "tags"; + public static final String TARGET_IQN = "targetiqn"; + public static final String TEMPLATE_FILTER = "templatefilter"; + public static final String TEMPLATE_ID = "templateid"; + public static final String ISO_ID = "isoid"; + public static final String TIMEOUT = "timeout"; + public static final String TIMEZONE = "timezone"; + public static final String TYPE = "type"; + public static final String TRUST_STORE = "truststore"; + public static final String TRUST_STORE_PASSWORD = "truststorepass"; + public static final String URL = "url"; + public static final String USAGE_INTERFACE = "usageinterface"; + public static final String USER_DATA = "userdata"; + public static final String USER_ID = "userid"; + public static final String USE_SSL = "ssl"; + public static final String USERNAME = "username"; + public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist"; + public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork"; + public static final String VALUE = "value"; + public static final String VIRTUAL_MACHINE_ID = "virtualmachineid"; + public static final String VIRTUAL_MACHINE_IDS = "virtualmachineids"; + public static final String VLAN = "vlan"; + public static final String VLAN_ID = "vlanid"; + public static final String VM_AVAILABLE = "vmavailable"; + public static final String VM_LIMIT = "vmlimit"; + public static final String VM_TOTAL = "vmtotal"; + public static final String VNET = "vnet"; + public static final String VOLUME_ID = "volumeid"; + public static final String ZONE_ID = "zoneid"; + public static final String ZONE_NAME = "zonename"; + public static final String NETWORK_TYPE = "networktype"; + public static final String PAGE = "page"; + public static final String PAGE_SIZE = "pagesize"; + public static final String COUNT = "count"; + public static final String TRAFFIC_TYPE = "traffictype"; + public static final String NETWORK_OFFERING_ID = "networkofferingid"; + public static final String NETWORK_IDS = "networkids"; + public static final String NETWORK_ID = "networkid"; + public static final String NIC_ID = "nicid"; + public static final String SPECIFY_VLAN = "specifyvlan"; + public static final String IS_DEFAULT = "isdefault"; + public static final String IS_SYSTEM = "issystem"; + public static final String AVAILABILITY = "availability"; + public static final String NETWORKRATE = "networkrate"; + public static final String HOST_TAGS = "hosttags"; + public static final String SSH_KEYPAIR = "keypair"; + public static final String HOST_CPU_CAPACITY = "hostcpucapacity"; + public static final String HOST_CPU_NUM = "hostcpunum"; + public static final String HOST_MEM_CAPACITY = "hostmemcapacity"; + public static final String HOST_MAC = "hostmac"; + public static final String HOST_TAG = "hosttag"; + public static final String PXE_SERVER_TYPE = "pxeservertype"; + public static final String LINMIN_USERNAME = "linminusername"; + public static final String LINMIN_PASSWORD = "linminpassword"; + public static final String LINMIN_APID = "linminapid"; + public static final String DHCP_SERVER_TYPE = "dhcpservertype"; + public static final String LINK_LOCAL_IP = "linklocalip"; + public static final String LINK_LOCAL_MAC_ADDRESS = "linklocalmacaddress"; + public static final String LINK_LOCAL_MAC_NETMASK = "linklocalnetmask"; + public static final String LINK_LOCAL_NETWORK_ID = "linklocalnetworkid"; + public static final String PRIVATE_MAC_ADDRESS = "privatemacaddress"; + public static final String PRIVATE_NETMASK = "privatenetmask"; + public static final String PRIVATE_NETWORK_ID = "privatenetworkid"; + public static final String ALLOCATION_STATE = "allocationstate"; + public static final String MANAGED_STATE = "managedstate"; + public static final String STORAGE_ID = "storageid"; + public static final String PING_STORAGE_SERVER_IP = "pingstorageserverip"; + public static final String PING_DIR = "pingdir"; + public static final String TFTP_DIR = "tftpdir"; + public static final String PING_CIFS_USERNAME = "pingcifsusername"; + public static final String PING_CIFS_PASSWORD = "pingcifspassword"; + public static final String CHECKSUM = "checksum"; + public static final String NETWORK_DEVICE_TYPE = "networkdevicetype"; + public static final String NETWORK_DEVICE_PARAMETER_LIST = "networkdeviceparameterlist"; + public static final String ZONE_TOKEN = "zonetoken"; + public static final String DHCP_PROVIDER = "dhcpprovider"; + public static final String RESULT = "success"; + public static final String LUN_ID = "lunId"; + public static final String IQN = "iqn"; + public static final String AGGREGATE_NAME = "aggregatename"; + public static final String POOL_NAME = "poolname"; + public static final String VOLUME_NAME = "volumename"; + public static final String SNAPSHOT_POLICY = "snapshotpolicy"; + public static final String SNAPSHOT_RESERVATION = "snapshotreservation"; + public static final String IP_NETWORK_LIST = "iptonetworklist"; + public static final String PARAM_LIST = "param"; + public static final String FOR_LOAD_BALANCING = "forloadbalancing"; + public static final String KEYBOARD = "keyboard"; + public static final String OPEN_FIREWALL = "openfirewall"; + public static final String TEMPLATE_TAG = "templatetag"; + public static final String HYPERVISOR_VERSION = "hypervisorversion"; + public static final String MAX_GUESTS_LIMIT = "maxguestslimit"; + public static final String PROJECT_ID = "projectid"; + public static final String PROJECT_IDS = "projectids"; + public static final String PROJECT = "project"; + public static final String ROLE = "role"; + public static final String USER = "user"; + public static final String ACTIVE_ONLY = "activeonly"; + public static final String TOKEN = "token"; + public static final String ACCEPT = "accept"; + public static final String SORT_KEY = "sortkey"; + public static final String ACCOUNT_DETAILS = "accountdetails"; + public static final String SERVICE_PROVIDER_LIST = "serviceproviderlist"; + public static final String SERVICE_CAPABILITY_LIST = "servicecapabilitylist"; + public static final String CAN_CHOOSE_SERVICE_CAPABILITY = "canchooseservicecapability"; + public static final String PROVIDER = "provider"; + public static final String NETWORK_SPEED = "networkspeed"; + public static final String BROADCAST_DOMAIN_RANGE = "broadcastdomainrange"; + public static final String ISOLATION_METHODS = "isolationmethods"; + public static final String PHYSICAL_NETWORK_ID = "physicalnetworkid"; + public static final String DEST_PHYSICAL_NETWORK_ID = "destinationphysicalnetworkid"; + public static final String ENABLED = "enabled"; + public static final String SERVICE_NAME = "servicename"; + public static final String DHCP_RANGE = "dhcprange"; + public static final String UUID = "uuid"; + public static final String SECURITY_GROUP_EANBLED = "securitygroupenabled"; + public static final String LOCAL_STORAGE_ENABLED = "localstorageenabled"; + public static final String GUEST_IP_TYPE = "guestiptype"; + public static final String XEN_NETWORK_LABEL = "xennetworklabel"; + public static final String KVM_NETWORK_LABEL = "kvmnetworklabel"; + public static final String VMWARE_NETWORK_LABEL = "vmwarenetworklabel"; + public static final String NETWORK_SERVICE_PROVIDER_ID = "nspid"; + public static final String SERVICE_LIST = "servicelist"; + public static final String CAN_ENABLE_INDIVIDUAL_SERVICE = "canenableindividualservice"; + public static final String SUPPORTED_SERVICES = "supportedservices"; + public static final String NSP_ID = "nspid"; + public static final String ACL_TYPE = "acltype"; + public static final String SUBDOMAIN_ACCESS = "subdomainaccess"; + public static final String LOAD_BALANCER_DEVICE_ID = "lbdeviceid"; + public static final String LOAD_BALANCER_DEVICE_NAME = "lbdevicename"; + public static final String LOAD_BALANCER_DEVICE_STATE = "lbdevicestate"; + public static final String LOAD_BALANCER_DEVICE_CAPACITY = "lbdevicecapacity"; + public static final String LOAD_BALANCER_DEVICE_DEDICATED = "lbdevicededicated"; + public static final String FIREWALL_DEVICE_ID = "fwdeviceid"; + public static final String FIREWALL_DEVICE_NAME = "fwdevicename"; + public static final String FIREWALL_DEVICE_STATE = "fwdevicestate"; + public static final String FIREWALL_DEVICE_CAPACITY = "fwdevicecapacity"; + public static final String FIREWALL_DEVICE_DEDICATED = "fwdevicededicated"; + public static final String SERVICE = "service"; + public static final String ASSOCIATED_NETWORK_ID = "associatednetworkid"; + public static final String ASSOCIATED_NETWORK_NAME = "associatednetworkname"; + public static final String SOURCE_NAT_SUPPORTED = "sourcenatsupported"; + public static final String RESOURCE_STATE = "resourcestate"; + public static final String PROJECT_INVITE_REQUIRED = "projectinviterequired"; + public static final String REQUIRED = "required"; + public static final String RESTART_REQUIRED = "restartrequired"; + public static final String ALLOW_USER_CREATE_PROJECTS = "allowusercreateprojects"; + public static final String CONSERVE_MODE = "conservemode"; + public static final String TRAFFIC_TYPE_IMPLEMENTOR = "traffictypeimplementor"; + public static final String KEYWORD = "keyword"; + public static final String LIST_ALL = "listall"; + public static final String SPECIFY_IP_RANGES = "specifyipranges"; + public static final String IS_SOURCE_NAT = "issourcenat"; + public static final String IS_STATIC_NAT = "isstaticnat"; + public static final String SORT_BY = "sortby"; + public static final String CHANGE_CIDR = "changecidr"; + public static final String PURPOSE = "purpose"; + public static final String IS_TAGGED = "istagged"; + public static final String INSTANCE_NAME = "instancename"; + public static final String START_VM = "startvm"; + public static final String HA_HOST = "hahost"; + public static final String CUSTOM_DISK_OFF_MAX_SIZE = "customdiskofferingmaxsize"; + public static final String DEFAULT_ZONE_ID = "defaultzoneid"; + public static final String GUID = "guid"; + + public static final String EXTERNAL_SWITCH_MGMT_DEVICE_ID = "vsmdeviceid"; + public static final String EXTERNAL_SWITCH_MGMT_DEVICE_NAME = "vsmdevicename"; + public static final String EXTERNAL_SWITCH_MGMT_DEVICE_STATE = "vsmdevicestate"; + // Would we need to have a capacity field for Cisco N1KV VSM? Max hosts managed by it perhaps? May remove this later. + public static final String EXTERNAL_SWITCH_MGMT_DEVICE_CAPACITY = "vsmdevicecapacity"; + public static final String CISCO_NEXUS_VSM_NAME = "vsmname"; + public static final String VSM_USERNAME = "vsmusername"; + public static final String VSM_PASSWORD = "vsmpassword"; + public static final String VSM_IPADDRESS = "vsmipaddress"; + public static final String VSM_MGMT_VLAN_ID = "vsmmgmtvlanid"; + public static final String VSM_PKT_VLAN_ID = "vsmpktvlanid"; + public static final String VSM_CTRL_VLAN_ID = "vsmctrlvlanid"; + public static final String VSM_STORAGE_VLAN_ID = "vsmstoragevlanid"; + public static final String VSM_DOMAIN_ID = "vsmdomainid"; + public static final String VSM_CONFIG_MODE = "vsmconfigmode"; + public static final String VSM_CONFIG_STATE = "vsmconfigstate"; + public static final String VSM_DEVICE_STATE = "vsmdevicestate"; + public static final String ADD_VSM_FLAG = "addvsmflag"; + public static final String END_POINT = "endpoint"; + public static final String REGION_ID = "regionid"; + public static final String IS_PROPAGATE = "ispropagate"; + public static final String VPC_OFF_ID = "vpcofferingid"; + public static final String NETWORK = "network"; + public static final String VPC_ID = "vpcid"; + public static final String GATEWAY_ID = "gatewayid"; + public static final String CAN_USE_FOR_DEPLOY = "canusefordeploy"; + public static final String RESOURCE_IDS = "resourceids"; + public static final String RESOURCE_ID = "resourceid"; + public static final String CUSTOMER = "customer"; + public static final String S2S_VPN_GATEWAY_ID = "s2svpngatewayid"; + public static final String S2S_CUSTOMER_GATEWAY_ID = "s2scustomergatewayid"; + public static final String IPSEC_PSK = "ipsecpsk"; + public static final String GUEST_IP = "guestip"; + public static final String REMOVED = "removed"; + public static final String IKE_POLICY = "ikepolicy"; + public static final String ESP_POLICY = "esppolicy"; + public static final String IKE_LIFETIME = "ikelifetime"; + public static final String ESP_LIFETIME = "esplifetime"; + public static final String DPD = "dpd"; + public static final String FOR_VPC = "forvpc"; + public static final String SHRINK_OK = "shrinkok"; + public static final String NICIRA_NVP_DEVICE_ID = "nvpdeviceid"; + public static final String NICIRA_NVP_TRANSPORT_ZONE_UUID = "transportzoneuuid"; + public static final String NICIRA_NVP_DEVICE_NAME = "niciradevicename"; + public static final String NICIRA_NVP_GATEWAYSERVICE_UUID = "l3gatewayserviceuuid"; + public static final String S3_ACCESS_KEY = "accesskey"; + public static final String S3_SECRET_KEY = "secretkey"; + public static final String S3_END_POINT = "endpoint"; + public static final String S3_BUCKET_NAME = "bucket"; + public static final String S3_HTTPS_FLAG = "usehttps"; + public static final String S3_CONNECTION_TIMEOUT = "connectiontimeout"; + public static final String S3_MAX_ERROR_RETRY = "maxerrorretry"; + public static final String S3_SOCKET_TIMEOUT = "sockettimeout"; + public static final String INCL_ZONES = "includezones"; + public static final String EXCL_ZONES = "excludezones"; + public static final String SOURCE = "source"; + public static final String COUNTER_ID = "counterid"; + public static final String AGGR_OPERATOR = "aggroperator"; + public static final String AGGR_FUNCTION = "aggrfunction"; + public static final String AGGR_VALUE = "aggrvalue"; + public static final String THRESHOLD = "threshold"; + public static final String RELATIONAL_OPERATOR = "relationaloperator"; + public static final String OTHER_DEPLOY_PARAMS = "otherdeployparams"; + public static final String MIN_MEMBERS = "minmembers"; + public static final String MAX_MEMBERS = "maxmembers"; + public static final String AUTOSCALE_VM_DESTROY_TIME = "destroyvmgraceperiod"; + public static final String VMPROFILE_ID = "vmprofileid"; + public static final String VMGROUP_ID = "vmgroupid"; + public static final String CS_URL = "csurl"; + public static final String SCALEUP_POLICY_IDS = "scaleuppolicyids"; + public static final String SCALEDOWN_POLICY_IDS = "scaledownpolicyids"; + public static final String SCALEUP_POLICIES = "scaleuppolicies"; + public static final String SCALEDOWN_POLICIES = "scaledownpolicies"; + public static final String INTERVAL = "interval"; + public static final String QUIETTIME = "quiettime"; + public static final String ACTION = "action"; + public static final String CONDITION_ID = "conditionid"; + public static final String CONDITION_IDS = "conditionids"; + public static final String COUNTERPARAM_LIST = "counterparam"; + public static final String AUTOSCALE_USER_ID = "autoscaleuserid"; + public static final String BAREMETAL_DISCOVER_NAME = "baremetaldiscovername"; +<<<<<<< HEAD + public static final String UCS_DN = "ucsdn"; +======= + 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"; +>>>>>>> CLOUDSTACK-684 Support VM Snapshot + + public enum HostDetails { + all, capacity, events, stats, min; + } + + public enum VMDetails { + all, group, nics, stats, secgrp, tmpl, servoff, iso, volume, min; + } + + public enum LDAPParams { + hostname, port, usessl, queryfilter, searchbase, dn, passwd, truststore, truststorepass; + + @Override + public String toString() { + return "ldap." + name(); + } + } + + +} diff --git a/api/src/org/apache/cloudstack/api/BaseCmd.java b/api/src/org/apache/cloudstack/api/BaseCmd.java index a9ac489671b..17f789f88fa 100644 --- a/api/src/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/org/apache/cloudstack/api/BaseCmd.java @@ -71,6 +71,7 @@ import com.cloud.user.ResourceLimitService; import com.cloud.utils.Pair; 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()); @@ -128,6 +129,7 @@ public abstract class BaseCmd { @Inject public QueryService _queryService; @Inject public UsageService _usageService; @Inject public NetworkUsageService _networkUsageService; + @Inject public VMSnapshotService _vmSnapshotService; 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 0dc85de1647..267238af37b 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -88,6 +88,7 @@ import org.apache.cloudstack.api.response.TrafficTypeResponse; import org.apache.cloudstack.api.response.UsageRecordResponse; 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; @@ -163,6 +164,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); @@ -381,5 +383,6 @@ public interface ResponseGenerator { UsageRecordResponse createUsageResponse(Usage usageRecord); - TrafficMonitorResponse createTrafficMonitorResponse(Host trafficMonitor); + TrafficMonitorResponse createTrafficMonitorResponse(Host trafficMonitor); + 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..f0dbf16b250 --- /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) +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(getVmId(),getDisplayName(),getDescription(),snapshotMemory()); + 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(getVmId(),getEntityId()); + 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..a2b2c08b381 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java @@ -0,0 +1,85 @@ +// 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.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.SuccessResponse; +import org.apache.cloudstack.api.response.VMSnapshotResponse; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.vm.snapshot.VMSnapshot; + +@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; + } + + @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(getId()); + 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..936d348950d --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/ListVMSnapshotCmd.java @@ -0,0 +1,89 @@ +// 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 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.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) +public class ListVMSnapshotCmd extends BaseListTaggedResourcesCmd { + + private static final String s_name = "listvmsnapshotresponse"; + + @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; + + public String getState() { + return state; + } + + public String getVmSnapshotName() { + return vmSnapshotName; + } + + public Long getVmId() { + return vmId; + } + + public Long getId() { + return id; + } + + @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..d7b4599d6c4 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java @@ -0,0 +1,92 @@ +// 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.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +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; + } + + @Override + public long getEntityOwnerId() { + VMSnapshot vmSnapshot = _entityMgr.findById(VMSnapshot.class, getVmSnapShotId()); + if (vmSnapshot != null) { + return vmSnapshot.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, ConcurrentOperationException { + UserContext.current().setEventDetails( + "vmsnapshot id: " + getVmSnapShotId()); + UserVm result = _vmSnapshotService.revertToSnapshot(getVmSnapShotId()); + 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 9c4fcb35554..11b1ca11114 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -1410,6 +1410,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 d70649bf318..e1d0fb20731 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -537,3 +537,9 @@ addRegion=1 updateRegion=1 removeRegion=1 listRegions=15 + +### VM Snapshot commands +listVMSnapshot=15 +createVMSnapshot=15 +deleteVMSnapshot=15 +revertToSnapshot=15 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 841a535bbce..5cac253fc0c 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; @@ -445,7 +451,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); @@ -2799,7 +2811,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); @@ -3351,7 +3362,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 b3cb54f783e..22f4ba9cb80 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 @@ -90,9 +90,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; @@ -129,6 +133,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; @@ -237,6 +243,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; @@ -256,6 +263,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; @@ -579,11 +590,109 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return execute((CheckS2SVpnConnectionsCommand) cmd); } else if (cmd instanceof StorageSubSystemCommand) { return this.storageResource.handleStorageCommands((StorageSubSystemCommand)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()) { @@ -6167,6 +6276,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 39fe31c443d..6fb1b18c1df 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 83132c6aa76..e6b1bf16a03 100755 --- a/server/src/com/cloud/api/ApiDBUtils.java +++ b/server/src/com/cloud/api/ApiDBUtils.java @@ -209,6 +209,8 @@ import com.cloud.vm.dao.DomainRouterDao; 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.dao.VMSnapshotDao; @Component public class ApiDBUtils { @@ -309,6 +311,7 @@ public class ApiDBUtils { static SnapshotPolicyDao _snapshotPolicyDao; static AsyncJobDao _asyncJobDao; static HostDetailsDao _hostDetailsDao; + static VMSnapshotDao _vmSnapshotDao; @Inject private ManagementServer ms; @Inject public AsyncJobManager asyncMgr; @@ -407,7 +410,7 @@ public class ApiDBUtils { @Inject private SnapshotPolicyDao snapshotPolicyDao; @Inject private AsyncJobDao asyncJobDao; @Inject private HostDetailsDao hostDetailsDao; - + @Inject private VMSnapshotDao vmSnapshotDao; @PostConstruct void init() { _ms = ms; @@ -505,7 +508,7 @@ public class ApiDBUtils { _snapshotPolicyDao = snapshotPolicyDao; _asyncJobDao = asyncJobDao; _hostDetailsDao = hostDetailsDao; - + _vmSnapshotDao = vmSnapshotDao; // Note: stats collector should already have been initialized by this time, otherwise a null instance is returned _statsCollector = StatsCollector.getInstance(); } @@ -1054,6 +1057,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 8c9761520bb..a94e93568e2 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -93,7 +93,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 org.springframework.stereotype.Component; @@ -195,6 +194,13 @@ import org.apache.cloudstack.region.Region; import org.apache.cloudstack.usage.Usage; import org.apache.cloudstack.usage.UsageService; import org.apache.cloudstack.usage.UsageTypes; +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; import org.apache.log4j.Logger; import java.text.DecimalFormat; @@ -358,6 +364,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(); @@ -3093,7 +3118,6 @@ public class ApiResponseHelper implements ResponseGenerator { response.setObjectName("vpnconnection"); return response; } - @Override public GuestOSResponse createGuestOSResponse(GuestOS guestOS) { GuestOSResponse response = new GuestOSResponse(); @@ -3133,7 +3157,6 @@ public class ApiResponseHelper implements ResponseGenerator { } - @Override public UsageRecordResponse createUsageResponse(Usage usageRecord) { UsageRecordResponse usageRecResponse = new UsageRecordResponse(); diff --git a/server/src/com/cloud/capacity/CapacityManagerImpl.java b/server/src/com/cloud/capacity/CapacityManagerImpl.java index d7b70535b06..4787c7bb37f 100755 --- a/server/src/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/com/cloud/capacity/CapacityManagerImpl.java @@ -62,9 +62,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; @@ -78,7 +80,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; @Component @Local(value = CapacityManager.class) @@ -110,7 +116,11 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, ConfigurationManager _configMgr; @Inject HypervisorCapabilitiesDao _hypervisorCapabilitiesDao; - + @Inject + protected VMSnapshotDao _vmSnapshotDao; + @Inject + protected UserVmDao _userVMDao; + private int _vmCapacityReleaseInterval; private ScheduledExecutorService _executor; private boolean _stopped; @@ -437,12 +447,43 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, } + private long getVMSnapshotAllocatedCapacity(StoragePoolVO pool){ + List 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 b1a13083399..26499bcd4f2 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 { @@ -371,8 +372,16 @@ public enum Config { ApiLimitInterval("Advanced", ManagementServer.class, Integer.class, "api.throttling.interval", "1", "Time interval (in seconds) to reset API count", null), ApiLimitMax("Advanced", ManagementServer.class, Integer.class, "api.throttling.max", "25", "Max allowed number of APIs within fixed interval", null), - ApiLimitCacheSize("Advanced", ManagementServer.class, Integer.class, "api.throttling.cachesize", "50000", "Account based API count cache size", null); + ApiLimitCacheSize("Advanced", ManagementServer.class, Integer.class, "api.throttling.cachesize", "50000", "Account based API count cache size", 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/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 0943bfc201e..d3812067118 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -99,6 +99,10 @@ import org.apache.cloudstack.api.command.user.tag.*; import org.apache.cloudstack.api.command.user.template.*; import org.apache.cloudstack.api.command.user.vm.*; import org.apache.cloudstack.api.command.user.vmgroup.*; +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.cloudstack.api.command.user.volume.*; import org.apache.cloudstack.api.command.user.vpc.*; import org.apache.cloudstack.api.command.user.vpn.*; @@ -2143,6 +2147,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ResetVpnConnectionCmd.class); cmdList.add(UpdateVpnCustomerGatewayCmd.class); cmdList.add(ListZonesByCmd.class); + cmdList.add(ListVMSnapshotCmd.class); + cmdList.add(CreateVMSnapshotCmd.class); + cmdList.add(RevertToSnapshotCmd.class); + cmdList.add(DeleteVMSnapshotCmd.class); return cmdList; } diff --git a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java index c7beff08684..e06da75580c 100755 --- a/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -104,6 +104,9 @@ import org.apache.log4j.Logger; import javax.ejb.Local; import javax.naming.ConfigurationException; import java.util.*; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @Component @Local(value = { SnapshotManager.class, SnapshotService.class }) @@ -167,7 +170,9 @@ public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager, private ResourceTagDao _resourceTagDao; @Inject private ConfigurationDao _configDao; - + @Inject + private VMSnapshotDao _vmSnapshotDao; + String _name; private int _totalRetries; private int _pauseInterval; private int _deltaSnapshotMax; @@ -395,6 +400,7 @@ public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager, } // 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) { @@ -408,6 +414,12 @@ public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager, 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 e44343dda8e..06756b617eb 100644 --- a/server/src/com/cloud/tags/TaggedResourceManagerImpl.java +++ b/server/src/com/cloud/tags/TaggedResourceManagerImpl.java @@ -73,6 +73,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; @Component @@ -121,6 +122,8 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso VpcDao _vpcDao; @Inject StaticRouteDao _staticRouteDao; + @Inject + VMSnapshotDao _vmSnapshotDao; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -139,6 +142,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso _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 d5c66ded91e..19887ff9e25 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -249,12 +249,37 @@ import com.cloud.utils.exception.ExecutionException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.*; +import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; +import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; +import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd; +import org.apache.cloudstack.api.command.user.vm.*; +import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; +import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; +import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; +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 extends ManagerBase implements UserVmManager, UserVmService { @@ -392,7 +417,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use protected GuestOSCategoryDao _guestOSCategoryDao; @Inject UsageEventDao _usageEventDao; - + @Inject + protected VMSnapshotDao _vmSnapshotDao; + @Inject + protected VMSnapshotManager _vmSnapshotMgr; + protected ScheduledExecutorService _executor = null; protected int _expungeInterval; protected int _expungeDelay; @@ -813,7 +842,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use // permission check _accountMgr.checkAccess(caller, null, true, volume, vm); - // Check if volume is stored on secondary Storage. + //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. + //Check if volume is stored on secondary Storage. boolean isVolumeOnSec = false; VolumeHostVO volHostVO = _volumeHostDao.findByVolumeId(volume.getId()); if (volHostVO != null) { @@ -1106,8 +1140,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } + 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 = "detaching volume", async = true) + @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 @@ -1167,8 +1212,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use "Please specify a VM that is either running or stopped."); } - AsyncJobExecutor asyncExecutor = BaseAsyncJobExecutor - .getCurrentExecutor(); + // 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(); @@ -1314,12 +1361,25 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use 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()); @@ -1997,8 +2057,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } 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 1838ed2fbfa..bb8f8f6abd6 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -158,6 +158,10 @@ 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 extends ManagerBase implements VirtualMachineManager, Listener { @@ -227,6 +231,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac protected HypervisorGuruManager _hvGuruMgr; @Inject protected NetworkDao _networkDao; + @Inject + protected VMSnapshotDao _vmSnapshotDao; @Inject protected List _planners; @@ -236,6 +242,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject protected ResourceManager _resourceMgr; + + @Inject + protected VMSnapshotManager _vmSnapshotMgr = null; @Inject protected ConfigurationDao _configDao; @@ -590,7 +599,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 { @@ -1143,6 +1152,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Override public boolean stateTransitTo(VMInstanceVO vm, VirtualMachine.Event e, Long hostId) throws NoTransitionException { + // if there are active vm snapshots task, state change is not allowed + if(_vmSnapshotMgr.hasActiveVMSnapshotTasks(vm.getId())){ + s_logger.error("State transit with event: " + e + " failed due to: " + vm.getInstanceName() + " has active VM snapshots tasks"); + return false; + } + State oldState = vm.getState(); if (oldState == State.Starting) { if (e == Event.OperationSucceeded) { @@ -1177,7 +1192,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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); @@ -1626,6 +1646,19 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 vmSnapshotsInTrasientStates = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging,VMSnapshot.State.Reverting, VMSnapshot.State.Creating); + if(vmSnapshotsInTrasientStates.size() > 1){ + s_logger.info("Found vm " + vm.getInstanceName() + " with VM snapshots in transient states, needs to sync VM snapshot state"); + if(!_vmSnapshotMgr.syncVMSnapshot(vm, hostId)){ + s_logger.warn("Failed to sync VM in a transient snapshot related state: " + vm.getInstanceName()); + continue; + }else{ + s_logger.info("Successfully sync VM with transient snapshot: " + vm.getInstanceName()); + } + } + VMInstanceVO castedVm = null; if (info == null) { info = new AgentVmInfo(vm.getInstanceName(), getVmGuru(vm), vm, State.Stopped); @@ -1743,8 +1776,27 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 vmSnapshotsInExpungingStates = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging, VMSnapshot.State.Creating,VMSnapshot.State.Reverting); + if(vmSnapshotsInExpungingStates.size() > 0){ + s_logger.info("Found vm " + vm.getInstanceName() + " in state. " + vm.getState() + ", needs to sync VM snapshot state"); + Long hostId = null; + Host host = null; + if(info != null && info.getHostUuid() != null){ + host = _hostDao.findByGuid(info.getHostUuid()); + } + hostId = host == null ? (vm.getHostId() == null ? vm.getLastHostId() : vm.getHostId()) : host.getId(); + if(!_vmSnapshotMgr.syncVMSnapshot(vm, hostId)){ + s_logger.warn("Failed to sync VM with transient snapshot: " + vm.getInstanceName()); + continue; + }else{ + s_logger.info("Successfully sync VM with transient snapshot: " + vm.getInstanceName()); + } + } + + if ((info == null && (vm.getState() == State.Running || vm.getState() == State.Starting )) + || (info != null && (info.state == State.Running && vm.getState() == State.Starting))) { 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); @@ -1785,7 +1837,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } 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); @@ -2260,6 +2312,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac Long clusterId = agent.getClusterId(); long agentId = agent.getId(); + if (agent.getHypervisorType() == HypervisorType.XenServer) { // only for Xen StartupRoutingCommand startup = (StartupRoutingCommand) cmd; HashMap> allStates = startup.getClusterVMStateChanges(); @@ -2375,7 +2428,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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..c60900551ee --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/VMSnapshotManager.java @@ -0,0 +1,47 @@ +// 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 snapshot state when VM snapshot in reverting or snapshoting or expunging state + * Used for fullsync after agent connects + * + * @param vm, the VM in question + * @param hostId + * @return true if succeeds, false if fails + */ + boolean syncVMSnapshot(VMInstanceVO vm, Long hostId); + + boolean hasActiveVMSnapshotTasks(Long vmId); + +} 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..a0335634113 --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -0,0 +1,833 @@ +// 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.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +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.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.Ternary; +import com.cloud.utils.component.ManagerBase; +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; + +@Component +@Local(value = { VMSnapshotManager.class, VMSnapshotService.class }) +public class VMSnapshotManagerImpl extends ManagerBase implements VMSnapshotManager, VMSnapshotService { + 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 _vmSnapshottateMachine ; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + if (_configDao == null) { + throw new ConfigurationException( + "Unable to get the configuration dao."); + } + + _vmSnapshotMax = NumbersUtil.parseInt(_configDao.getValue("vmsnapshot.max"), VMSNAPSHOTMAX); + + _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(Long vmId, String vsDisplayName, String vsDescription, Boolean snapshotMemory) + throws ResourceAllocationException { + + 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"); + } + + // parameter length check + if(vsDisplayName != null && vsDisplayName.length()>255) + throw new InvalidParameterValueException("Creating VM snapshot failed due to length of VM snapshot vsDisplayName should not exceed 255"); + if(vsDescription != null && vsDescription.length()>255) + throw new InvalidParameterValueException("Creating VM snapshot failed due to length of VM snapshot vsDescription should not exceed 255"); + + // 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"); + } + + if(snapshotMemory && userVmVo.getState() == VirtualMachine.State.Stopped){ + throw new InvalidParameterValueException("Can not snapshot memory when VM is in 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.State.Creating, + Snapshot.State.CreatedOnPrimary, Snapshot.State.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 (hasActiveVMSnapshotTasks(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(Long vmId, Long vmSnapshotId) { + UserVmVO userVm = _userVMDao.findById(vmId); + if (userVm == null) { + throw new InvalidParameterValueException("Create vm to snapshot failed due to vm: " + vmId + " is not found"); + } + VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(vmSnapshotId); + if(vmSnapshot == null){ + throw new CloudRuntimeException("VM snapshot id: " + vmSnapshotId + " can not be found"); + } + Long hostId = pickRunningHost(vmId); + try { + vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.CreateRequested); + } catch (NoTransitionException e) { + throw new CloudRuntimeException(e.getMessage()); + } + 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); + 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); + vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed); + } + return vmSnapshot; + } catch (Exception e) { + if(e instanceof AgentUnavailableException){ + try { + vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed); + } catch (NoTransitionException e1) { + s_logger.error("Cannot set vm snapshot state due to: " + e1.getMessage()); + } + } + 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); + } + + protected boolean vmSnapshotStateTransitTo(VMSnapshotVO vsnp, VMSnapshot.Event event) throws NoTransitionException { + return _vmSnapshottateMachine.transitTo(vsnp, event, null, _vmSnapshotDao); + } + + @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()); + vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded); + } else if (as instanceof RevertToVMSnapshotAnswer) { + RevertToVMSnapshotAnswer answer = (RevertToVMSnapshotAnswer) as; + finalizeRevert(vmSnapshot, answer.getVolumeTOs()); + vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded); + } 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()); + if(previousCurrent != null){ + previousCurrent.setCurrent(false); + _vmSnapshotDao.persist(previousCurrent); + } + vmSnapshot.setCurrent(true); + _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; + } + + @Override + public boolean hasActiveVMSnapshotTasks(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 snapshots", async=true) + public boolean deleteVMSnapshot(Long vmSnapshotId) { + 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.Expunging != 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, or Expunging State"); + } + + // check if there are other active VM snapshot tasks + if (hasActiveVMSnapshotTasks(vmSnapshot.getVmId())) { + List expungingSnapshots = _vmSnapshotDao.listByInstanceId(vmSnapshot.getVmId(), VMSnapshot.State.Expunging); + if(expungingSnapshots.size() > 0 && expungingSnapshots.get(0).getId() == vmSnapshot.getId()) + s_logger.debug("Target VM snapshot already in expunging state, go on deleting it: " + vmSnapshot.getDisplayName()); + else + 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); + s_logger.debug("Delete VM snapshot " + vmSnapshot.getName() + " succeeded for vm: " + userVm.getInstanceName()); + 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 to VM snapshot", async = true) + public UserVm revertToSnapshot(Long vmSnapshotId) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException { + + // 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 (hasActiveVMSnapshotTasks(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 (hasActiveVMSnapshotTasks(userVm.getId())) { + throw new InvalidParameterValueException("There is other active vm snapshot tasks on the instance, please try again later"); + } + + userVm = _userVMDao.findById(userVm.getId()); + try { + vmSnapshotStateTransitTo(vmSnapshotVo, VMSnapshot.Event.RevertRequested); + } catch (NoTransitionException e) { + throw new CloudRuntimeException(e.getMessage()); + } + 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 != null && answer.getResult()) { + processAnswer(vmSnapshotVo, userVm, answer, hostId); + s_logger.debug("RevertTo " + vmSnapshotVo.getName() + " succeeded for vm: " + userVm.getInstanceName()); + } 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 + vmSnapshotStateTransitTo(vmSnapshotVo, VMSnapshot.Event.OperationFailed); + throw new CloudRuntimeException(errMsg); + } + } catch (Exception e) { + if(e instanceof AgentUnavailableException){ + try { + vmSnapshotStateTransitTo(vmSnapshotVo, VMSnapshot.Event.OperationFailed); + } catch (NoTransitionException e1) { + s_logger.error("Cannot set vm snapshot state due to: " + e1.getMessage()); + } + } + // for other exceptions, do not change VM snapshot 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; + } + + + @Override + public VMSnapshot getVMSnapshotById(Long id) { + VMSnapshotVO vmSnapshot = _vmSnapshotDao.findById(id); + return vmSnapshot; + } + + protected 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); + if(vmSnapshot == null){ + throw new InvalidParameterValueException("unable to find the vm snapshot with id " + 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) { + VMSnapshotVO target = _vmSnapshotDao.findById(snapshot.getId()); + if(type != null && target.getType() != type) + continue; + if (!deleteSnapshotInternal(target)) { + result = false; + break; + } + } + return result; + } + + @Override + public boolean syncVMSnapshot(VMInstanceVO vm, Long hostId) { + try{ + + UserVmVO userVm = _userVMDao.findById(vm.getId()); + if(userVm == null) + return false; + + List vmSnapshotsInExpungingStates = _vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging, VMSnapshot.State.Reverting, VMSnapshot.State.Creating); + for (VMSnapshotVO vmSnapshotVO : vmSnapshotsInExpungingStates) { + if(vmSnapshotVO.getState() == VMSnapshot.State.Expunging){ + return deleteSnapshotInternal(vmSnapshotVO); + }else if(vmSnapshotVO.getState() == VMSnapshot.State.Creating){ + return createVmSnapshotInternal(userVm, vmSnapshotVO, hostId) != null; + }else if(vmSnapshotVO.getState() == VMSnapshot.State.Reverting){ + return revertInternal(userVm, vmSnapshotVO, hostId) != null; + } + } + }catch (Exception e) { + s_logger.error(e.getMessage(),e); + if(_vmSnapshotDao.listByInstanceId(vm.getId(), VMSnapshot.State.Expunging).size() == 0) + return true; + else + 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..7d8ace7f82d --- /dev/null +++ b/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java @@ -0,0 +1,161 @@ +// 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 org.springframework.stereotype.Component; + +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; +@Component +@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..6fc6404e15f --- /dev/null +++ b/server/test/com/cloud/vm/snapshot/VMSnapshotManagerTest.java @@ -0,0 +1,186 @@ +// 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 static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +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.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.UserVmVO; +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; + + + +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; + + 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(_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(TEST_VM_ID,"","",true); + } + + // vm state not in [running, stopped] case + @Test(expected=InvalidParameterValueException.class) + public void testAllocVMSnapshotF2() throws ResourceAllocationException{ + when(vmMock.getState()).thenReturn(State.Starting); + _vmSnapshotMgr.allocVMSnapshot(TEST_VM_ID,"","",true); + } + + // VM in stopped state & snapshotmemory case + @Test(expected=InvalidParameterValueException.class) + public void testCreateVMSnapshotF3() throws AgentUnavailableException, OperationTimedoutException, ResourceAllocationException{ + when(vmMock.getState()).thenReturn(State.Stopped); + _vmSnapshotMgr.allocVMSnapshot(TEST_VM_ID,"","",true); + } + + // max snapshot limit case + @SuppressWarnings("unchecked") + @Test(expected=CloudRuntimeException.class) + public void testAllocVMSnapshotF4() throws ResourceAllocationException{ + List mockList = mock(List.class); + when(mockList.size()).thenReturn(10); + when(_vmSnapshotDao.findByVm(TEST_VM_ID)).thenReturn(mockList); + _vmSnapshotMgr.allocVMSnapshot(TEST_VM_ID,"","",true); + } + + // active volume snapshots case + @SuppressWarnings("unchecked") + @Test(expected=CloudRuntimeException.class) + public void testAllocVMSnapshotF5() throws ResourceAllocationException{ + List mockList = mock(List.class); + when(mockList.size()).thenReturn(1); + when(_snapshotDao.listByInstanceId(TEST_VM_ID,Snapshot.State.Creating, + Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp)).thenReturn(mockList); + _vmSnapshotMgr.allocVMSnapshot(TEST_VM_ID,"","",true); + } + + // successful creation case + @Test + public void testCreateVMSnapshot() throws AgentUnavailableException, OperationTimedoutException, ResourceAllocationException, NoTransitionException{ + when(vmMock.getState()).thenReturn(State.Running); + _vmSnapshotMgr.allocVMSnapshot(TEST_VM_ID,"","",true); + + 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)); + doNothing().when(_vmSnapshotMgr).processAnswer(any(VMSnapshotVO.class), + any(UserVmVO.class), any(Answer.class), anyLong()); + doReturn(true).when(_vmSnapshotMgr).vmSnapshotStateTransitTo(any(VMSnapshotVO.class),any(VMSnapshot.Event.class)); + _vmSnapshotMgr.createVmSnapshotInternal(vmMock, mock(VMSnapshotVO.class), 5L); + } + +} diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index f89c8851e8e..11ae26745e8 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -2625,6 +2625,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; + CREATE TABLE `cloud`.`user_ipv6_address` ( `id` bigint unsigned NOT NULL UNIQUE auto_increment, diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 6cb82f2ea25..42cd7836587 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 508135611e3..2abafaab3ce 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -1437,6 +1437,16 @@ dictionary = { 'label.resize.new.size': '', 'label.action.resize.volume': '', 'label.resize.new.offering.id': '', -'label.resize.shrink.ok': '' +'label.resize.shrink.ok': '', +'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 fdacd9e99d4..8af147816a0 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -1674,6 +1674,8 @@ under the License. + + diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 5b5ed1853e7..264b5a1cbbe 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -48,7 +48,7 @@ displayname: { label: 'label.display.name' }, zonename: { label: 'label.zone.name' }, state: { - label: 'label.state', + label: 'label.state', indicator: { 'Running': 'on', 'Stopped': 'off', @@ -153,6 +153,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 +419,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 +597,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 +1484,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 +1510,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();