// 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.log4j.Logger; import org.springframework.stereotype.Component; import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; 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.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; 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.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.Snapshot; import com.cloud.storage.SnapshotVO; import com.cloud.storage.StoragePool; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; import com.cloud.uservm.UserVm; import com.cloud.utils.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.db.TransactionCallback; import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionCallbackWithException; import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; import com.cloud.utils.db.TransactionStatus; 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 PrimaryDataStoreDao _storagePoolDao; @Inject SnapshotDao _snapshotDao; @Inject VirtualMachineManager _itMgr; @Inject DataStoreManager dataStoreMgr; @Inject ConfigurationDao _configDao; @Inject HypervisorCapabilitiesDao _hypervisorCapabilitiesDao; @Inject DiskOfferingDao _diskOfferingDao; @Inject ServiceOfferingDao _serviceOfferingDao; int _vmSnapshotMax; int _wait; 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); String value = _configDao.getValue("vmsnapshot.create.wait"); _wait = NumbersUtil.parseInt(value, 1800); _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 CallContext.current().getCallingAccount(); } @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"); } // check hypervisor capabilities if(!_hypervisorCapabilitiesDao.isVmSnapshotEnabled(userVmVo.getHypervisorType(), "default")) throw new InvalidParameterValueException("VM snapshot is not enabled for hypervisor type: " + userVmVo.getHypervisorType()); // 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) { CreateVMSnapshotAnswer answer = null; try { 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()); ccmd.setWait(_wait); 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 = "Creating VM snapshot: " + vmSnapshot.getName() + " failed"; if(answer != null && answer.getDetails() != null) errMsg = errMsg + " due to " + answer.getDetails(); s_logger.error(errMsg); vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed); throw new CloudRuntimeException(errMsg); } 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()); } if(vmSnapshot.getState() == VMSnapshot.State.Ready && answer != null){ for (VolumeTO volumeTo : answer.getVolumeTOs()){ publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_CREATE,vmSnapshot,userVm,volumeTo); } } } } private void publishUsageEvent(String type, VMSnapshot vmSnapshot, UserVm userVm, VolumeTO volumeTo){ VolumeVO volume = _volumeDao.findById(volumeTo.getId()); Long diskOfferingId = volume.getDiskOfferingId(); Long offeringId = null; if (diskOfferingId != null) { DiskOfferingVO offering = _diskOfferingDao.findById(diskOfferingId); if (offering != null && (offering.getType() == DiskOfferingVO.Type.Disk)) { offeringId = offering.getId(); } } UsageEventUtils.publishUsageEvent( type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(), vmSnapshot.getName(), offeringId, volume.getId(), // save volume's id into templateId field volumeTo.getChainSize(), VMSnapshot.class.getName(), vmSnapshot.getUuid()); } protected List getVolumeTOList(Long vmId) { List volumeTOs = new ArrayList(); List volumeVos = _volumeDao.findByInstance(vmId); for (VolumeVO volume : volumeVos) { StoragePool pool = (StoragePool)dataStoreMgr.getPrimaryDataStore(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(final VMSnapshotVO vmSnapshot, UserVmVO userVm, final Answer as, Long hostId) { try { Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) throws NoTransitionException { 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()); } } }); } catch (Exception e) { String errMsg = "Error while process answer: " + as.getClass() + " due to " + e.getMessage(); s_logger.error(errMsg, e); throw new CloudRuntimeException(errMsg); } } 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()); volumeVO.setVmSnapshotChainSize(volume.getChainSize()); _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()); DeleteVMSnapshotAnswer answer = null; 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()); 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()); } finally{ if(answer != null && answer.getResult()){ for (VolumeTO volumeTo : answer.getVolumeTOs()){ publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_DELETE,vmSnapshot,userVm,volumeTo); } } } } @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."); } UserVmVO vm = null; Long hostId = null; // 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 { _itMgr.advanceStart(userVm.getUuid(), new HashMap()); vm = _userVMDao.findById(userVm.getId()); 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.getUuid(), true); } 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; } }