You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ed...@apache.org on 2013/02/14 02:11:27 UTC
[14/50] [abbrv] CLOUDSTACK-684 support vm snapshot
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java
index 1838ed2..bb8f8f6 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<DeploymentPlanner> _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 extends VMInstanceVO> T advanceStart(T vm, Map<VirtualMachineProfile.Param, Object> 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<VMSnapshotVO> 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<VMSnapshotVO> 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<String, Pair<String, State>> 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());
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/server/src/com/cloud/vm/snapshot/VMSnapshotManager.java
----------------------------------------------------------------------
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 0000000..c609005
--- /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);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java
----------------------------------------------------------------------
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 0000000..a033563
--- /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<VMSnapshot.State, VMSnapshot.Event, VMSnapshot> _vmSnapshottateMachine ;
+
+ @Override
+ public boolean configure(String name, Map<String, Object> 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<VMSnapshotVO> listVMSnapshots(ListVMSnapshotCmd cmd) {
+ Account caller = getCaller();
+ List<Long> permittedAccounts = new ArrayList<Long>();
+
+ 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<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(
+ 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<VMSnapshotVO> 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<VMSnapshotVO> 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<VMSnapshotVO> 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<VolumeVO> listVolumes = _volumeDao.findByInstance(vmId);
+ for (VolumeVO volume : listVolumes) {
+ List<SnapshotVO> 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<VolumeTO> 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<VolumeTO> getVolumeTOList(Long vmId) {
+ List<VolumeTO> volumeTOs = new ArrayList<VolumeTO>();
+ List<VolumeVO> 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<Long, VMSnapshotVO> snapshotMap = new HashMap<Long, VMSnapshotVO>();
+ List<VMSnapshotVO> allSnapshots = _vmSnapshotDao.findByVm(snapshot.getVmId());
+ for (VMSnapshotVO vmSnapshotVO : allSnapshots) {
+ snapshotMap.put(vmSnapshotVO.getId(), vmSnapshotVO);
+ }
+
+ VMSnapshotTO currentTO = convert2VMSnapshotTO(snapshot);
+ VMSnapshotTO result = currentTO;
+ VMSnapshotVO current = snapshot;
+ while (current.getParent() != null) {
+ VMSnapshotVO parent = snapshotMap.get(current.getParent());
+ 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<VolumeTO> VolumeTOs) {
+ // update volumes path
+ updateVolumePath(VolumeTOs);
+
+ // update children's parent snapshots
+ List<VMSnapshotVO> 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<VolumeTO> 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<VolumeTO> 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<VolumeTO> 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<VMSnapshotVO> 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<VMSnapshotVO> 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<VolumeTO> 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<VirtualMachineProfile.Param, Object>(), 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<VolumeTO> 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<VolumeVO> 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<HostVO> 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<VMSnapshotVO> 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<VMSnapshotVO> 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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDao.java
----------------------------------------------------------------------
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 0000000..7532edf
--- /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<VMSnapshotVO, Long>, StateDao<VMSnapshot.State, VMSnapshot.Event, VMSnapshot> {
+
+ List<VMSnapshotVO> findByVm(Long vmId);
+
+ List<VMSnapshotVO> listExpungingSnapshot();
+
+ List<VMSnapshotVO> listByInstanceId(Long vmId, VMSnapshot.State... status);
+
+ VMSnapshotVO findCurrentSnapshotByVmId(Long vmId);
+
+ List<VMSnapshotVO> listByParent(Long vmSnapshotId);
+
+ VMSnapshotVO findByName(Long vm_id, String name);
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/server/src/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java
----------------------------------------------------------------------
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 0000000..7d8ace7
--- /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<VMSnapshotVO, Long>
+ implements VMSnapshotDao {
+ private static final Logger s_logger = Logger.getLogger(VMSnapshotDaoImpl.class);
+ private final SearchBuilder<VMSnapshotVO> SnapshotSearch;
+ private final SearchBuilder<VMSnapshotVO> ExpungingSnapshotSearch;
+ private final SearchBuilder<VMSnapshotVO> SnapshotStatusSearch;
+ private final SearchBuilder<VMSnapshotVO> 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<VMSnapshotVO> findByVm(Long vmId) {
+ SearchCriteria<VMSnapshotVO> sc = SnapshotSearch.create();
+ sc.setParameters("vm_id", vmId);
+ return listBy(sc, null);
+ }
+
+ @Override
+ public List<VMSnapshotVO> listExpungingSnapshot() {
+ SearchCriteria<VMSnapshotVO> sc = ExpungingSnapshotSearch.create();
+ sc.setParameters("state", State.Expunging);
+ return listBy(sc, null);
+ }
+
+ @Override
+ public List<VMSnapshotVO> listByInstanceId(Long vmId, State... status) {
+ SearchCriteria<VMSnapshotVO> sc = SnapshotStatusSearch.create();
+ sc.setParameters("vm_id", vmId);
+ sc.setParameters("state", (Object[]) status);
+ return listBy(sc, null);
+ }
+
+ @Override
+ public VMSnapshotVO findCurrentSnapshotByVmId(Long vmId) {
+ SearchCriteria<VMSnapshotVO> sc = AllFieldsSearch.create();
+ sc.setParameters("vm_id", vmId);
+ sc.setParameters("current", 1);
+ return findOneBy(sc);
+ }
+
+ @Override
+ public List<VMSnapshotVO> listByParent(Long vmSnapshotId) {
+ SearchCriteria<VMSnapshotVO> 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<VMSnapshotVO> 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<VMSnapshotVO> 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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/server/test/com/cloud/vm/snapshot/VMSnapshotManagerTest.java
----------------------------------------------------------------------
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 0000000..6fc6404
--- /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<VMSnapshotVO>());
+
+ List<VolumeVO> mockVolumeList = new ArrayList<VolumeVO>();
+ 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<VMSnapshotVO> 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<SnapshotVO> 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<VolumeTO>()).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);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/setup/db/create-schema.sql
----------------------------------------------------------------------
diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql
index f89c885..11ae267 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,
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/ui/css/cloudstack3.css
----------------------------------------------------------------------
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 6cb82f2..42cd783 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;
}
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/ui/dictionary.jsp
----------------------------------------------------------------------
diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp
index 5081356..2abafaa 100644
--- a/ui/dictionary.jsp
+++ b/ui/dictionary.jsp
@@ -1437,6 +1437,16 @@ dictionary = {
'label.resize.new.size': '<fmt:message key="label.resize.new.size" />',
'label.action.resize.volume': '<fmt:message key="label.action.resize.volume" />',
'label.resize.new.offering.id': '<fmt:message key="label.resize.new.offering.id" />',
-'label.resize.shrink.ok': '<fmt:message key="label.resize.shrink.ok" />'
+'label.resize.shrink.ok': '<fmt:message key="label.resize.shrink.ok" />',
+'label.vmsnapshot.current': '<fmt:message key="label.vmsnapshot.current" />',
+'label.vmsnapshot.parentname': '<fmt:message key="label.vmsnapshot.parentname" />',
+'label.vmsnapshot.type': '<fmt:message key="label.vmsnapshot.type" />',
+'label.vmsnapshot.memory': '<fmt:message key="label.vmsnapshot.memory" />',
+'label.vmsnapshot': '<fmt:message key="label.vmsnapshot" />',
+'label.action.vmsnapshot.create': '<fmt:message key="label.action.vmsnapshot.create" />',
+'label.action.vmsnapshot.delete': '<fmt:message key="label.action.vmsnapshot.delete" />',
+'label.action.vmsnapshot.revert': '<fmt:message key="label.action.vmsnapshot.revert" />',
+'message.action.vmsnapshot.delete': '<fmt:message key="message.action.vmsnapshot.delete" />',
+'message.action.vmsnapshot.revert': '<fmt:message key="message.action.vmsnapshot.revert" />'
};
</script>
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/ui/index.jsp
----------------------------------------------------------------------
diff --git a/ui/index.jsp b/ui/index.jsp
index fdacd9e..8af1478 100644
--- a/ui/index.jsp
+++ b/ui/index.jsp
@@ -1674,6 +1674,8 @@ under the License.
<script type="text/javascript" src="scripts/system.js?t=<%=now%>"></script>
<script type="text/javascript" src="scripts/domains.js?t=<%=now%>"></script>
<script type="text/javascript" src="scripts/docs.js?t=<%=now%>"></script>
+ <script type="text/javascript" src="scripts/domains.js?t=<%=now%>"></script>
+ <script type="text/javascript" src="scripts/vm_snapshots.js?t=<%=now%>"></script>
</body>
</html>
<jsp:include page="dictionary.jsp" />
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/ui/scripts/instances.js
----------------------------------------------------------------------
diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js
index 5b5ed18..264b5a1 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");
http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/9a12756a/ui/scripts/ui/widgets/detailView.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui/widgets/detailView.js b/ui/scripts/ui/widgets/detailView.js
index 4b4fbd5..5f0c0f0 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){
$('<div>')
.addClass('view-all')
.append(
$('<a>')
.attr({ href: '#' })
- .data('detail-view-link-view-all', detailViewArgs.viewAll)
+ .css('padding','0 1px')
+ .data('detail-view-link-view-all', view)
.append(
- $('<span>').html(_l('label.view') + ' ' + _l(detailViewArgs.viewAll.label))
+ $('<span>').html(_l('label.view') + ' ' + _l(view.label))
)
)
.append(
@@ -908,9 +914,10 @@
.appendTo(
$('<td>')
.addClass('view-all')
+ .css('padding','9px 3px 8px 0')
.appendTo($actions.find('tr'))
);
-
+ });
}
}