// 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.storage.snapshot; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.agent.AgentManager; import com.cloud.agent.api.*; import com.cloud.agent.api.to.S3TO; import com.cloud.agent.api.to.SwiftTO; import com.cloud.alert.AlertManager; import com.cloud.api.commands.ListRecurringSnapshotScheduleCmd; import com.cloud.configuration.Config; import com.cloud.configuration.Resource.ResourceType; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.dao.DomainDao; import com.cloud.event.*; import com.cloud.event.dao.EventDao; import com.cloud.event.dao.UsageEventDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageUnavailableException; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.org.Grouping; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.resource.ResourceManager; import com.cloud.server.ResourceTag.TaggedResourceType; import com.cloud.storage.*; import com.cloud.storage.Snapshot.Type; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.dao.*; import com.cloud.storage.listener.SnapshotStateListener; import com.cloud.storage.s3.S3Manager; import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.storage.swift.SwiftManager; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.*; import com.cloud.user.dao.AccountDao; import com.cloud.utils.DateUtil; import com.cloud.utils.DateUtil.IntervalType; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.*; 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.dao.UserVmDao; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.log4j.Logger; import javax.ejb.Local; import javax.naming.ConfigurationException; import java.util.*; @Component @Local(value = { SnapshotManager.class, SnapshotService.class }) public class SnapshotManagerImpl extends ManagerBase implements SnapshotManager, SnapshotService { private static final Logger s_logger = Logger.getLogger(SnapshotManagerImpl.class); @Inject protected VMTemplateDao _templateDao; @Inject protected HostDao _hostDao; @Inject protected UserVmDao _vmDao; @Inject protected VolumeDao _volsDao; @Inject protected AccountDao _accountDao; @Inject protected DataCenterDao _dcDao; @Inject protected DiskOfferingDao _diskOfferingDao; @Inject protected SnapshotDao _snapshotDao; @Inject protected StoragePoolDao _storagePoolDao; @Inject protected EventDao _eventDao; @Inject protected SnapshotPolicyDao _snapshotPolicyDao = null; @Inject protected SnapshotScheduleDao _snapshotScheduleDao; @Inject protected DomainDao _domainDao; @Inject protected StorageManager _storageMgr; @Inject protected AgentManager _agentMgr; @Inject protected SnapshotScheduler _snapSchedMgr; @Inject protected AccountManager _accountMgr; @Inject private AlertManager _alertMgr; @Inject protected ClusterDao _clusterDao; @Inject private UsageEventDao _usageEventDao; @Inject private ResourceLimitService _resourceLimitMgr; @Inject private SwiftManager _swiftMgr; @Inject private S3Manager _s3Mgr; @Inject private SecondaryStorageVmManager _ssvmMgr; @Inject private ResourceManager _resourceMgr; @Inject private DomainManager _domainMgr; @Inject private VolumeDao _volumeDao; @Inject private ResourceTagDao _resourceTagDao; @Inject private ConfigurationDao _configDao; private int _totalRetries; private int _pauseInterval; private int _deltaSnapshotMax; private int _backupsnapshotwait; private StateMachine2 _snapshotFsm; protected SearchBuilder PolicySnapshotSearch; protected SearchBuilder PoliciesForSnapSearch; protected Answer sendToPool(Volume vol, Command cmd) { StoragePool pool = _storagePoolDao.findById(vol.getPoolId()); long[] hostIdsToTryFirst = null; Long vmHostId = getHostIdForSnapshotOperation(vol); if (vmHostId != null) { hostIdsToTryFirst = new long[] { vmHostId }; } List hostIdsToAvoid = new ArrayList(); for (int retry = _totalRetries; retry >= 0; retry--) { try { Pair result = _storageMgr.sendToPool(pool, hostIdsToTryFirst, hostIdsToAvoid, cmd); if (result.second().getResult()) { return result.second(); } if (s_logger.isDebugEnabled()) { s_logger.debug("The result for " + cmd.getClass().getName() + " is " + result.second().getDetails() + " through " + result.first()); } hostIdsToAvoid.add(result.first()); } catch (StorageUnavailableException e1) { s_logger.warn("Storage unavailable ", e1); return null; } try { Thread.sleep(_pauseInterval * 1000); } catch (InterruptedException e) { } s_logger.debug("Retrying..."); } s_logger.warn("After " + _totalRetries + " retries, the command " + cmd.getClass().getName() + " did not succeed."); return null; } @Override public Long getHostIdForSnapshotOperation(Volume vol) { VMInstanceVO vm = _vmDao.findById(vol.getInstanceId()); if (vm != null) { if(vm.getHostId() != null) { return vm.getHostId(); } else if(vm.getLastHostId() != null) { return vm.getLastHostId(); } } return null; } @Override public SnapshotVO createSnapshotOnPrimary(VolumeVO volume, Long policyId, Long snapshotId) { SnapshotVO snapshot = _snapshotDao.findById(snapshotId); if (snapshot == null) { throw new CloudRuntimeException("Can not find snapshot " + snapshotId); } try { stateTransitTo(snapshot, Snapshot.Event.CreateRequested); } catch (NoTransitionException nte) { s_logger.debug("Failed to update snapshot state due to " + nte.getMessage()); } // Send a ManageSnapshotCommand to the agent String vmName = _storageMgr.getVmNameOnVolume(volume); long volumeId = volume.getId(); long preId = _snapshotDao.getLastSnapshot(volumeId, snapshotId); String preSnapshotPath = null; SnapshotVO preSnapshotVO = null; if (preId != 0 && !(volume.getLastPoolId() != null && !volume.getLastPoolId().equals(volume.getPoolId()))) { preSnapshotVO = _snapshotDao.findByIdIncludingRemoved(preId); if (preSnapshotVO != null && preSnapshotVO.getBackupSnapshotId() != null) { preSnapshotPath = preSnapshotVO.getPath(); } } StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId()); // RBD volumes do not support snapshotting in the way CloudStack does it. // For now we leave the snapshot feature disabled for RBD volumes if (srcPool.getPoolType() == StoragePoolType.RBD) { throw new CloudRuntimeException("RBD volumes do not support snapshotting"); } ManageSnapshotCommand cmd = new ManageSnapshotCommand(snapshotId, volume.getPath(), srcPool, preSnapshotPath, snapshot.getName(), vmName); ManageSnapshotAnswer answer = (ManageSnapshotAnswer) sendToPool(volume, cmd); // Update the snapshot in the database if ((answer != null) && answer.getResult()) { // The snapshot was successfully created if (preSnapshotPath != null && preSnapshotPath.equals(answer.getSnapshotPath())) { // empty snapshot s_logger.debug("CreateSnapshot: this is empty snapshot "); try { snapshot.setPath(preSnapshotPath); snapshot.setBackupSnapshotId(preSnapshotVO.getBackupSnapshotId()); snapshot.setSwiftId(preSnapshotVO.getSwiftId()); snapshot.setPrevSnapshotId(preId); snapshot.setSecHostId(preSnapshotVO.getSecHostId()); stateTransitTo(snapshot, Snapshot.Event.OperationNotPerformed); } catch (NoTransitionException nte) { s_logger.debug("CreateSnapshot: failed to update state of snapshot due to " + nte.getMessage()); } } else { long preSnapshotId = 0; if (preSnapshotVO != null && preSnapshotVO.getBackupSnapshotId() != null) { preSnapshotId = preId; // default delta snap number is 16 int deltaSnap = _deltaSnapshotMax; int i; for (i = 1; i < deltaSnap; i++) { String prevBackupUuid = preSnapshotVO.getBackupSnapshotId(); // previous snapshot doesn't have backup, create a full snapshot if (prevBackupUuid == null) { preSnapshotId = 0; break; } long preSSId = preSnapshotVO.getPrevSnapshotId(); if (preSSId == 0) { break; } preSnapshotVO = _snapshotDao.findByIdIncludingRemoved(preSSId); } if (i >= deltaSnap) { preSnapshotId = 0; } } //If the volume is moved around, backup a full snapshot to secondary storage if (volume.getLastPoolId() != null && !volume.getLastPoolId().equals(volume.getPoolId())) { preSnapshotId = 0; volume.setLastPoolId(volume.getPoolId()); _volumeDao.update(volume.getId(), volume); } snapshot = updateDBOnCreate(snapshotId, answer.getSnapshotPath(), preSnapshotId); } // Get the snapshot_schedule table entry for this snapshot and // policy id. // Set the snapshotId to retrieve it back later. if (policyId != Snapshot.MANUAL_POLICY_ID) { SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, true); assert snapshotSchedule != null; snapshotSchedule.setSnapshotId(snapshotId); _snapshotScheduleDao.update(snapshotSchedule.getId(), snapshotSchedule); } } else { if (answer != null) { s_logger.error(answer.getDetails()); } try { stateTransitTo(snapshot, Snapshot.Event.OperationFailed); } catch (NoTransitionException nte) { s_logger.debug("Failed to update snapshot state due to " + nte.getMessage()); } throw new CloudRuntimeException("Creating snapshot for volume " + volumeId + " on primary storage failed."); } return snapshot; } public SnapshotVO createSnapshotImpl(long volumeId, long policyId) throws ResourceAllocationException { return null; } @Override @DB @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "creating snapshot", async = true) public SnapshotVO createSnapshot(Long volumeId, Long policyId, Long snapshotId, Account snapshotOwner) { VolumeVO volume = _volsDao.findById(volumeId); if (volume == null) { throw new InvalidParameterValueException("No such volume exist"); } if (volume.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("Volume is not in ready state"); } SnapshotVO snapshot = null; boolean backedUp = false; UserVmVO uservm = null; // does the caller have the authority to act on this volume _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume); try { Long poolId = volume.getPoolId(); if (poolId == null) { throw new CloudRuntimeException("You cannot take a snapshot of a volume until it has been attached to an instance"); } if (_volsDao.getHypervisorType(volume.getId()).equals(HypervisorType.KVM)) { uservm = _vmDao.findById(volume.getInstanceId()); if (uservm != null && uservm.getType() != VirtualMachine.Type.User) { throw new CloudRuntimeException("Can't take a snapshot on system vm "); } StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); ClusterVO cluster = _clusterDao.findById(storagePool.getClusterId()); List hosts = _resourceMgr.listAllHostsInCluster(cluster.getId()); if (hosts != null && !hosts.isEmpty()) { HostVO host = hosts.get(0); if (!hostSupportSnapsthot(host)) { throw new CloudRuntimeException("KVM Snapshot is not supported on cluster: " + host.getId()); } } } // if volume is attached to a vm in destroyed or expunging state; disallow if (volume.getInstanceId() != null) { UserVmVO userVm = _vmDao.findById(volume.getInstanceId()); if (userVm != null) { if (userVm.getState().equals(State.Destroyed) || userVm.getState().equals(State.Expunging)) { throw new CloudRuntimeException("Creating snapshot failed due to volume:" + volumeId + " is associated with vm:" + userVm.getInstanceName() + " is in " + userVm.getState().toString() + " state"); } if(userVm.getHypervisorType() == HypervisorType.VMware || userVm.getHypervisorType() == HypervisorType.KVM) { List activeSnapshots = _snapshotDao.listByInstanceId(volume.getInstanceId(), Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp); if(activeSnapshots.size() > 1) throw new CloudRuntimeException("There is other active snapshot tasks on the instance to which the volume is attached, please try again later"); } } } snapshot = createSnapshotOnPrimary(volume, policyId, snapshotId); if (snapshot != null) { if (snapshot.getState() == Snapshot.State.CreatedOnPrimary) { backedUp = backupSnapshotToSecondaryStorage(snapshot); } else if (snapshot.getState() == Snapshot.State.BackedUp) { // For empty snapshot we set status to BackedUp in createSnapshotOnPrimary backedUp = true; } else { throw new CloudRuntimeException("Failed to create snapshot: " + snapshot + " on primary storage"); } if (!backedUp) { throw new CloudRuntimeException("Created snapshot: " + snapshot + " on primary but failed to backup on secondary"); } } else { throw new CloudRuntimeException("Failed to create snapshot: " + snapshot + " on primary storage"); } } finally { // Cleanup jobs to do after the snapshot has been created; decrement resource count if (snapshot != null) { postCreateSnapshot(volumeId, snapshot.getId(), policyId, backedUp); //Check if the snapshot was removed while backingUp. If yes, do not log snapshot create usage event SnapshotVO freshSnapshot = _snapshotDao.findById(snapshot.getId()); if ((freshSnapshot != null) && backedUp) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_CREATE, snapshot.getAccountId(), snapshot.getDataCenterId(), snapshotId, snapshot.getName(), null, null, volume.getSize(), snapshot.getClass().getName(), snapshot.getUuid()); } if( !backedUp ) { } else { _resourceLimitMgr.incrementResourceCount(snapshotOwner.getId(), ResourceType.snapshot); } } /* try { _storageMgr.stateTransitTo(volume, Volume.Event.OperationSucceeded); } catch (NoTransitionException e) { s_logger.debug("Failed to transit volume state: " + e.toString()); }*/ } return snapshot; } private SnapshotVO updateDBOnCreate(Long id, String snapshotPath, long preSnapshotId) { SnapshotVO createdSnapshot = _snapshotDao.findByIdIncludingRemoved(id); createdSnapshot.setPath(snapshotPath); createdSnapshot.setPrevSnapshotId(preSnapshotId); try { stateTransitTo(createdSnapshot, Snapshot.Event.OperationSucceeded); } catch (NoTransitionException nte) { s_logger.debug("Faile to update state of snapshot due to " + nte.getMessage()); } return createdSnapshot; } private static void checkObjectStorageConfiguration(SwiftTO swift, S3TO s3) { if (swift != null && s3 != null) { throw new CloudRuntimeException( "Swift and S3 are not simultaneously supported for snapshot backup."); } } @Override public void deleteSnapshotsForVolume (String secondaryStoragePoolUrl, Long dcId, Long accountId, Long volumeId ){ SwiftTO swift = _swiftMgr.getSwiftTO(); S3TO s3 = _s3Mgr.getS3TO(); VolumeVO volume = _volumeDao.findById(volumeId); StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); checkObjectStorageConfiguration(swift, s3); DeleteSnapshotBackupCommand cmd = new DeleteSnapshotBackupCommand( pool, swift, s3, secondaryStoragePoolUrl, dcId, accountId, volumeId, null, true); try { Answer ans = _agentMgr.sendToSSVM(dcId, cmd); if ( ans == null || !ans.getResult() ) { s_logger.warn("DeleteSnapshotBackupCommand failed due to " + ans.getDetails() + " volume id: " + volumeId); } } catch (Exception e) { s_logger.warn("DeleteSnapshotBackupCommand failed due to" + e.toString() + " volume id: " + volumeId); } } @Override public void deleteSnapshotsDirForVolume(String secondaryStoragePoolUrl, Long dcId, Long accountId, Long volumeId) { DeleteSnapshotsDirCommand cmd = new DeleteSnapshotsDirCommand(secondaryStoragePoolUrl, dcId, accountId, volumeId); try { Answer ans = _agentMgr.sendToSSVM(dcId, cmd); if (ans == null || !ans.getResult()) { s_logger.warn("DeleteSnapshotsDirCommand failed due to " + ans.getDetails() + " volume id: " + volumeId); } } catch (Exception e) { s_logger.warn("DeleteSnapshotsDirCommand failed due to" + e.toString() + " volume id: " + volumeId); } } @Override public void downloadSnapshotsFromSwift(SnapshotVO ss) { long volumeId = ss.getVolumeId(); VolumeVO volume = _volsDao.findById(volumeId); Long dcId = volume.getDataCenterId(); Long accountId = volume.getAccountId(); HostVO secHost = _storageMgr.getSecondaryStorageHost(dcId); String secondaryStoragePoolUrl = secHost.getStorageUrl(); Long swiftId = ss.getSwiftId(); SwiftTO swift = _swiftMgr.getSwiftTO(swiftId); SnapshotVO tss = ss; List BackupUuids = new ArrayList(30); while (true) { BackupUuids.add(0, tss.getBackupSnapshotId()); if (tss.getPrevSnapshotId() == 0) break; Long id = tss.getPrevSnapshotId(); tss = _snapshotDao.findById(id); assert tss != null : " can not find snapshot " + id; } String parent = null; try { for (String backupUuid : BackupUuids) { downloadSnapshotFromSwiftCommand cmd = new downloadSnapshotFromSwiftCommand(swift, secondaryStoragePoolUrl, dcId, accountId, volumeId, parent, backupUuid, _backupsnapshotwait); Answer answer = _agentMgr.sendToSSVM(dcId, cmd); if ((answer == null) || !answer.getResult()) { throw new CloudRuntimeException("downloadSnapshotsFromSwift failed "); } parent = backupUuid; } } catch (Exception e) { throw new CloudRuntimeException("downloadSnapshotsFromSwift failed due to " + e.toString()); } } private List determineBackupUuids(final SnapshotVO snapshot) { final List backupUuids = new ArrayList(); backupUuids.add(0, snapshot.getBackupSnapshotId()); SnapshotVO tempSnapshot = snapshot; while (tempSnapshot.getPrevSnapshotId() != 0) { tempSnapshot = _snapshotDao.findById(tempSnapshot .getPrevSnapshotId()); backupUuids.add(0, tempSnapshot.getBackupSnapshotId()); } return Collections.unmodifiableList(backupUuids); } @Override public void downloadSnapshotsFromS3(final SnapshotVO snapshot) { final VolumeVO volume = _volsDao.findById(snapshot.getVolumeId()); final Long zoneId = volume.getDataCenterId(); final HostVO secHost = _storageMgr.getSecondaryStorageHost(zoneId); final S3TO s3 = _s3Mgr.getS3TO(snapshot.getS3Id()); final List backupUuids = determineBackupUuids(snapshot); try { String parent = null; for (final String backupUuid : backupUuids) { final DownloadSnapshotFromS3Command cmd = new DownloadSnapshotFromS3Command( s3, parent, secHost.getStorageUrl(), zoneId, volume.getAccountId(), volume.getId(), backupUuid, _backupsnapshotwait); final Answer answer = _agentMgr.sendToSSVM(zoneId, cmd); if ((answer == null) || !answer.getResult()) { throw new CloudRuntimeException(String.format( "S3 snapshot download failed due to %1$s.", answer != null ? answer.getDetails() : "unspecified error")); } parent = backupUuid; } } catch (Exception e) { throw new CloudRuntimeException( "Snapshot download from S3 failed due to " + e.toString(), e); } } @Override @DB public boolean backupSnapshotToSecondaryStorage(SnapshotVO ss) { long snapshotId = ss.getId(); SnapshotVO snapshot = _snapshotDao.acquireInLockTable(snapshotId); if (snapshot == null) { throw new CloudRuntimeException("Can not acquire lock for snapshot: " + ss); } try { try { stateTransitTo(snapshot, Snapshot.Event.BackupToSecondary); } catch (NoTransitionException nte) { s_logger.debug("Failed to update the state of snapshot while backing up snapshot"); } long volumeId = snapshot.getVolumeId(); VolumeVO volume = _volsDao.lockRow(volumeId, true); Long dcId = volume.getDataCenterId(); Long accountId = volume.getAccountId(); HostVO secHost = getSecHost(volumeId, volume.getDataCenterId()); String secondaryStoragePoolUrl = secHost.getStorageUrl(); String snapshotUuid = snapshot.getPath(); // In order to verify that the snapshot is not empty, // we check if the parent of the snapshot is not the same as the parent of the previous snapshot. // We pass the uuid of the previous snapshot to the plugin to verify this. SnapshotVO prevSnapshot = null; String prevSnapshotUuid = null; String prevBackupUuid = null; SwiftTO swift = _swiftMgr.getSwiftTO(); S3TO s3 = _s3Mgr.getS3TO(); checkObjectStorageConfiguration(swift, s3); long prevSnapshotId = snapshot.getPrevSnapshotId(); if (prevSnapshotId > 0) { prevSnapshot = _snapshotDao.findByIdIncludingRemoved(prevSnapshotId); if ( prevSnapshot.getBackupSnapshotId() != null && swift == null) { if (prevSnapshot.getVersion() != null && prevSnapshot.getVersion().equals("2.2")) { prevBackupUuid = prevSnapshot.getBackupSnapshotId(); prevSnapshotUuid = prevSnapshot.getPath(); } } else if ((prevSnapshot.getSwiftId() != null && swift != null) || (prevSnapshot.getS3Id() != null && s3 != null)) { prevBackupUuid = prevSnapshot.getBackupSnapshotId(); prevSnapshotUuid = prevSnapshot.getPath(); } } boolean isVolumeInactive = _storageMgr.volumeInactive(volume); String vmName = _storageMgr.getVmNameOnVolume(volume); StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId()); BackupSnapshotCommand backupSnapshotCommand = new BackupSnapshotCommand(secondaryStoragePoolUrl, dcId, accountId, volumeId, snapshot.getId(), volume.getPath(), srcPool, snapshotUuid, snapshot.getName(), prevSnapshotUuid, prevBackupUuid, isVolumeInactive, vmName, _backupsnapshotwait); if ( swift != null ) { backupSnapshotCommand.setSwift(swift); } else if (s3 != null) { backupSnapshotCommand.setS3(s3); } String backedUpSnapshotUuid = null; // By default, assume failed. boolean backedUp = false; BackupSnapshotAnswer answer = (BackupSnapshotAnswer) sendToPool(volume, backupSnapshotCommand); if (answer != null && answer.getResult()) { backedUpSnapshotUuid = answer.getBackupSnapshotName(); if (backedUpSnapshotUuid != null) { backedUp = true; } } else if (answer != null) { s_logger.error(answer.getDetails()); } // Update the status in all cases. Transaction txn = Transaction.currentTxn(); txn.start(); if (backedUp) { if (backupSnapshotCommand.getSwift() != null ) { snapshot.setSwiftId(swift.getId()); snapshot.setBackupSnapshotId(backedUpSnapshotUuid); } else if (backupSnapshotCommand.getS3() != null) { snapshot.setS3Id(s3.getId()); snapshot.setBackupSnapshotId(backedUpSnapshotUuid); } else { snapshot.setSecHostId(secHost.getId()); snapshot.setBackupSnapshotId(backedUpSnapshotUuid); } if (answer.isFull()) { snapshot.setPrevSnapshotId(0); } try { stateTransitTo(snapshot, Snapshot.Event.OperationSucceeded); } catch (NoTransitionException nte) { s_logger.debug("Failed to update the state of snapshot while backing up snapshot"); } } else { try { stateTransitTo(snapshot, Snapshot.Event.OperationFailed); } catch (NoTransitionException nte) { s_logger.debug("Failed to update the state of snapshot while backing up snapshot"); } s_logger.warn("Failed to back up snapshot on secondary storage, deleting the record from the DB"); _snapshotDao.remove(snapshotId); } txn.commit(); return backedUp; } finally { if (snapshot != null) { _snapshotDao.releaseFromLockTable(snapshotId); } } } private HostVO getSecHost(long volumeId, long dcId) { Long id = _snapshotDao.getSecHostId(volumeId); if ( id != null) { return _hostDao.findById(id); } return _storageMgr.getSecondaryStorageHost(dcId); } private Long getSnapshotUserId() { Long userId = UserContext.current().getCallerUserId(); if (userId == null) { return User.UID_SYSTEM; } return userId; } @Override @DB public void postCreateSnapshot(Long volumeId, Long snapshotId, Long policyId, boolean backedUp) { Long userId = getSnapshotUserId(); SnapshotVO snapshot = _snapshotDao.findById(snapshotId); if (snapshot != null && snapshot.isRecursive()) { postCreateRecurringSnapshotForPolicy(userId, volumeId, snapshotId, policyId); } } private void postCreateRecurringSnapshotForPolicy(long userId, long volumeId, long snapshotId, long policyId) { // Use count query SnapshotVO spstVO = _snapshotDao.findById(snapshotId); Type type = spstVO.getType(); int maxSnaps = type.getMax(); List snaps = listSnapsforVolumeType(volumeId, type); SnapshotPolicyVO policy = _snapshotPolicyDao.findById(policyId); if (policy != null && policy.getMaxSnaps() < maxSnaps) { maxSnaps = policy.getMaxSnaps(); } while (snaps.size() > maxSnaps && snaps.size() > 1) { SnapshotVO oldestSnapshot = snaps.get(0); long oldSnapId = oldestSnapshot.getId(); s_logger.debug("Max snaps: " + policy.getMaxSnaps() + " exceeded for snapshot policy with Id: " + policyId + ". Deleting oldest snapshot: " + oldSnapId); if(deleteSnapshotInternal(oldSnapId)){ //log Snapshot delete event ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, oldestSnapshot.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_SNAPSHOT_DELETE, "Successfully deleted oldest snapshot: " + oldSnapId, 0); } snaps.remove(oldestSnapshot); } } @Override @DB @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_DELETE, eventDescription = "deleting snapshot", async = true) public boolean deleteSnapshot(long snapshotId) { Account caller = UserContext.current().getCaller(); // Verify parameters Snapshot snapshotCheck = _snapshotDao.findById(snapshotId); if (snapshotCheck == null) { throw new InvalidParameterValueException("unable to find a snapshot with id " + snapshotId); } _accountMgr.checkAccess(caller, null, true, snapshotCheck); if( !Snapshot.State.BackedUp.equals(snapshotCheck.getState() ) ) { throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is not in BackedUp Status"); } return deleteSnapshotInternal(snapshotId); } @DB private boolean deleteSnapshotInternal(Long snapshotId) { if (s_logger.isDebugEnabled()) { s_logger.debug("Calling deleteSnapshot for snapshotId: " + snapshotId); } SnapshotVO lastSnapshot = null; SnapshotVO snapshot = _snapshotDao.findById(snapshotId); if (snapshot.getBackupSnapshotId() != null) { List snaps = _snapshotDao.listByBackupUuid(snapshot.getVolumeId(), snapshot.getBackupSnapshotId()); if (snaps != null && snaps.size() > 1) { snapshot.setBackupSnapshotId(null); _snapshotDao.update(snapshot.getId(), snapshot); } } Transaction txn = Transaction.currentTxn(); txn.start(); _snapshotDao.remove(snapshotId); if (snapshot.getState() == Snapshot.State.BackedUp) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshot.getAccountId(), snapshot.getDataCenterId(), snapshotId, snapshot.getName(), null, null, 0L, snapshot.getClass().getName(), snapshot.getUuid()); } _resourceLimitMgr.decrementResourceCount(snapshot.getAccountId(), ResourceType.snapshot); txn.commit(); long lastId = snapshotId; boolean destroy = false; while (true) { lastSnapshot = _snapshotDao.findNextSnapshot(lastId); if (lastSnapshot == null) { // if all snapshots after this snapshot in this chain are removed, remove those snapshots. destroy = true; break; } if (lastSnapshot.getRemoved() == null) { // if there is one child not removed, then can not remove back up snapshot. break; } lastId = lastSnapshot.getId(); } if (destroy) { lastSnapshot = _snapshotDao.findByIdIncludingRemoved(lastId); while (lastSnapshot.getRemoved() != null) { String BackupSnapshotId = lastSnapshot.getBackupSnapshotId(); if (BackupSnapshotId != null) { List snaps = _snapshotDao.listByBackupUuid(lastSnapshot.getVolumeId(), BackupSnapshotId); if (snaps != null && snaps.size() > 1) { lastSnapshot.setBackupSnapshotId(null); _snapshotDao.update(lastSnapshot.getId(), lastSnapshot); } else { if (destroySnapshotBackUp(lastId)) { } else { s_logger.debug("Destroying snapshot backup failed " + lastSnapshot); break; } } } lastId = lastSnapshot.getPrevSnapshotId(); if (lastId == 0) { break; } lastSnapshot = _snapshotDao.findByIdIncludingRemoved(lastId); } } return true; } @Override @DB public boolean destroySnapshot(long userId, long snapshotId, long policyId) { return true; } @Override public HostVO getSecondaryStorageHost(SnapshotVO snapshot) { HostVO secHost = null; if( snapshot.getSwiftId() == null || snapshot.getSwiftId() == 0) { secHost = _hostDao.findById(snapshot.getSecHostId()); } else { Long dcId = snapshot.getDataCenterId(); secHost = _storageMgr.getSecondaryStorageHost(dcId); } return secHost; } @Override public String getSecondaryStorageURL(SnapshotVO snapshot) { HostVO secHost = getSecondaryStorageHost(snapshot); if (secHost != null) { return secHost.getStorageUrl(); } throw new CloudRuntimeException("Can not find secondary storage"); } @Override @DB public boolean destroySnapshotBackUp(long snapshotId) { boolean success = false; String details; SnapshotVO snapshot = _snapshotDao.findByIdIncludingRemoved(snapshotId); if (snapshot == null) { throw new CloudRuntimeException("Destroying snapshot " + snapshotId + " backup failed due to unable to find snapshot "); } String secondaryStoragePoolUrl = getSecondaryStorageURL(snapshot); Long dcId = snapshot.getDataCenterId(); Long accountId = snapshot.getAccountId(); Long volumeId = snapshot.getVolumeId(); String backupOfSnapshot = snapshot.getBackupSnapshotId(); if (backupOfSnapshot == null) { return true; } SwiftTO swift = _swiftMgr.getSwiftTO(snapshot.getSwiftId()); S3TO s3 = _s3Mgr.getS3TO(); checkObjectStorageConfiguration(swift, s3); VolumeVO volume = _volumeDao.findById(volumeId); StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); DeleteSnapshotBackupCommand cmd = new DeleteSnapshotBackupCommand( pool, swift, s3, secondaryStoragePoolUrl, dcId, accountId, volumeId, backupOfSnapshot, false); Answer answer = _agentMgr.sendToSSVM(dcId, cmd); if ((answer != null) && answer.getResult()) { snapshot.setBackupSnapshotId(null); _snapshotDao.update(snapshotId, snapshot); success = true; details = "Successfully deleted snapshot " + snapshotId + " for volumeId: " + volumeId; s_logger.debug(details); } else if (answer != null) { details = "Failed to destroy snapshot id:" + snapshotId + " for volume: " + volumeId + " due to "; if (answer.getDetails() != null) { details += answer.getDetails(); } s_logger.error(details); } return success; } @Override public Pair, Integer> listSnapshots(ListSnapshotsCmd cmd) { Long volumeId = cmd.getVolumeId(); String name = cmd.getSnapshotName(); Long id = cmd.getId(); String keyword = cmd.getKeyword(); String snapshotTypeStr = cmd.getSnapshotType(); String intervalTypeStr = cmd.getIntervalType(); Map tags = cmd.getTags(); Account caller = UserContext.current().getCaller(); List permittedAccounts = new ArrayList(); // Verify parameters if (volumeId != null) { VolumeVO volume = _volsDao.findById(volumeId); if (volume != null) { _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume); } } Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); Long domainId = domainIdRecursiveListProject.first(); Boolean isRecursive = domainIdRecursiveListProject.second(); ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); Filter searchFilter = new Filter(SnapshotVO.class, "created", false, cmd.getStartIndex(), cmd.getPageSizeVal()); SearchBuilder sb = _snapshotDao.createSearchBuilder(); _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); sb.and("status", sb.entity().getState(), SearchCriteria.Op.EQ); sb.and("volumeId", sb.entity().getVolumeId(), SearchCriteria.Op.EQ); sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE); sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); sb.and("snapshotTypeEQ", sb.entity().getsnapshotType(), SearchCriteria.Op.IN); sb.and("snapshotTypeNEQ", sb.entity().getsnapshotType(), SearchCriteria.Op.NEQ); if (tags != null && !tags.isEmpty()) { SearchBuilder tagSearch = _resourceTagDao.createSearchBuilder(); for (int count=0; count < tags.size(); count++) { tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ); tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ); tagSearch.cp(); } tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ); sb.groupBy(sb.entity().getId()); sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER); } SearchCriteria sc = sb.create(); _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); if (volumeId != null) { sc.setParameters("volumeId", volumeId); } if (tags != null && !tags.isEmpty()) { int count = 0; sc.setJoinParameters("tagSearch", "resourceType", TaggedResourceType.Snapshot.toString()); for (String key : tags.keySet()) { sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key); sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key)); count++; } } if (name != null) { sc.setParameters("name", "%" + name + "%"); } if (id != null) { sc.setParameters("id", id); } if (keyword != null) { SearchCriteria ssc = _snapshotDao.createSearchCriteria(); ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); sc.addAnd("name", SearchCriteria.Op.SC, ssc); } if (snapshotTypeStr != null) { Type snapshotType = SnapshotVO.getSnapshotType((String) snapshotTypeStr); if (snapshotType == null) { throw new InvalidParameterValueException("Unsupported snapshot type " + snapshotTypeStr); } if (snapshotType == Type.RECURRING) { sc.setParameters("snapshotTypeEQ", Type.HOURLY.ordinal(), Type.DAILY.ordinal(), Type.WEEKLY.ordinal(), Type.MONTHLY.ordinal()); } else { sc.setParameters("snapshotTypeEQ", snapshotType.ordinal()); } } else if (intervalTypeStr != null && volumeId != null) { Type type = SnapshotVO.getSnapshotType((String) intervalTypeStr); if (type == null) { throw new InvalidParameterValueException("Unsupported snapstho interval type " + intervalTypeStr); } sc.setParameters("snapshotTypeEQ", type.ordinal()); } else { // Show only MANUAL and RECURRING snapshot types sc.setParameters("snapshotTypeNEQ", Snapshot.Type.TEMPLATE.ordinal()); } Pair, Integer> result = _snapshotDao.searchAndCount(sc, searchFilter); return new Pair, Integer>(result.first(), result.second()); } @Override public boolean deleteSnapshotDirsForAccount(long accountId) { List volumes = _volsDao.findByAccount(accountId); // The above call will list only non-destroyed volumes. // So call this method before marking the volumes as destroyed. // i.e Call them before the VMs for those volumes are destroyed. boolean success = true; for (VolumeVO volume : volumes) { if (volume.getPoolId() == null) { continue; } Long volumeId = volume.getId(); Long dcId = volume.getDataCenterId(); if (_snapshotDao.listByVolumeIdIncludingRemoved(volumeId).isEmpty()) { // This volume doesn't have any snapshots. Nothing do delete. continue; } List ssHosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(dcId); SwiftTO swift = _swiftMgr.getSwiftTO(); S3TO s3 = _s3Mgr.getS3TO(); checkObjectStorageConfiguration(swift, s3); StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); if (swift == null && s3 == null) { for (HostVO ssHost : ssHosts) { DeleteSnapshotBackupCommand cmd = new DeleteSnapshotBackupCommand( pool, null, null, ssHost.getStorageUrl(), dcId, accountId, volumeId, "", true); Answer answer = null; try { answer = _agentMgr.sendToSSVM(dcId, cmd); } catch (Exception e) { s_logger.warn("Failed to delete all snapshot for volume " + volumeId + " on secondary storage " + ssHost.getStorageUrl()); } if ((answer != null) && answer.getResult()) { s_logger.debug("Deleted all snapshots for volume: " + volumeId + " under account: " + accountId); } else { success = false; if (answer != null) { s_logger.error(answer.getDetails()); } } } } else { DeleteSnapshotBackupCommand cmd = new DeleteSnapshotBackupCommand( pool, swift, s3, "", dcId, accountId, volumeId, "", true); Answer answer = null; try { answer = _agentMgr.sendToSSVM(dcId, cmd); } catch (Exception e) { final String storeType = s3 != null ? "S3" : "swift"; s_logger.warn("Failed to delete all snapshot for volume " + volumeId + " on " + storeType); } if ((answer != null) && answer.getResult()) { s_logger.debug("Deleted all snapshots for volume: " + volumeId + " under account: " + accountId); } else { success = false; if (answer != null) { s_logger.error(answer.getDetails()); } } } // Either way delete the snapshots for this volume. List snapshots = listSnapsforVolume(volumeId); for (SnapshotVO snapshot : snapshots) { if (_snapshotDao.expunge(snapshot.getId())) { if (snapshot.getType() == Type.MANUAL) { _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.snapshot); } // Log event after successful deletion UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshot.getAccountId(), volume.getDataCenterId(), snapshot.getId(), snapshot.getName(), null, null, volume.getSize(), snapshot.getClass().getName(), snapshot.getUuid()); } } } // Returns true if snapshotsDir has been deleted for all volumes. return success; } @Override @DB public SnapshotPolicyVO createPolicy(CreateSnapshotPolicyCmd cmd, Account policyOwner) { Long volumeId = cmd.getVolumeId(); VolumeVO volume = _volsDao.findById(cmd.getVolumeId()); if (volume == null) { throw new InvalidParameterValueException("Failed to create snapshot policy, unable to find a volume with id " + volumeId); } _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume); if (volume.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot."); } if (volume.getTemplateId() != null ) { VMTemplateVO template = _templateDao.findById(volume.getTemplateId()); if( template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM ) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported"); } } AccountVO owner = _accountDao.findById(volume.getAccountId()); Long instanceId = volume.getInstanceId(); if (instanceId != null) { // It is not detached, but attached to a VM if (_vmDao.findById(instanceId) == null) { // It is not a UserVM but a SystemVM or DomR throw new InvalidParameterValueException("Failed to create snapshot policy, snapshots of volumes attached to System or router VM are not allowed"); } } IntervalType intvType = DateUtil.IntervalType.getIntervalType(cmd.getIntervalType()); if (intvType == null) { throw new InvalidParameterValueException("Unsupported interval type " + cmd.getIntervalType()); } Type type = getSnapshotType(intvType); TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone()); String timezoneId = timeZone.getID(); if (!timezoneId.equals(cmd.getTimezone())) { s_logger.warn("Using timezone: " + timezoneId + " for running this snapshot policy as an equivalent of " + cmd.getTimezone()); } try { DateUtil.getNextRunTime(intvType, cmd.getSchedule(), timezoneId, null); } catch (Exception e) { throw new InvalidParameterValueException("Invalid schedule: " + cmd.getSchedule() + " for interval type: " + cmd.getIntervalType()); } if (cmd.getMaxSnaps() <= 0) { throw new InvalidParameterValueException("maxSnaps should be greater than 0"); } int intervalMaxSnaps = type.getMax(); if (cmd.getMaxSnaps() > intervalMaxSnaps) { throw new InvalidParameterValueException("maxSnaps exceeds limit: " + intervalMaxSnaps + " for interval type: " + cmd.getIntervalType()); } // Verify that max doesn't exceed domain and account snapshot limits long accountLimit = _resourceLimitMgr.findCorrectResourceLimitForAccount(owner, ResourceType.snapshot); long domainLimit = _resourceLimitMgr.findCorrectResourceLimitForDomain(_domainMgr.getDomain(owner.getDomainId()), ResourceType.snapshot); int max = cmd.getMaxSnaps().intValue(); if (owner.getType() != Account.ACCOUNT_TYPE_ADMIN && ((accountLimit != -1 && max > accountLimit) || (domainLimit != -1 && max > domainLimit))) { String message = "domain/account"; if (owner.getType() == Account.ACCOUNT_TYPE_PROJECT) { message = "domain/project"; } throw new InvalidParameterValueException("Max number of snapshots shouldn't exceed the " + message + " level snapshot limit"); } SnapshotPolicyVO policy = _snapshotPolicyDao.findOneByVolumeInterval(volumeId, intvType); if (policy == null) { policy = new SnapshotPolicyVO(volumeId, cmd.getSchedule(), timezoneId, intvType, cmd.getMaxSnaps()); policy = _snapshotPolicyDao.persist(policy); _snapSchedMgr.scheduleNextSnapshotJob(policy); } else { try { policy = _snapshotPolicyDao.acquireInLockTable(policy.getId()); policy.setSchedule(cmd.getSchedule()); policy.setTimezone(timezoneId); policy.setInterval((short) intvType.ordinal()); policy.setMaxSnaps(cmd.getMaxSnaps()); policy.setActive(true); _snapshotPolicyDao.update(policy.getId(), policy); } finally { if (policy != null) { _snapshotPolicyDao.releaseFromLockTable(policy.getId()); } } } return policy; } @Override public boolean deletePolicy(long userId, Long policyId) { SnapshotPolicyVO snapshotPolicy = _snapshotPolicyDao.findById(policyId); _snapSchedMgr.removeSchedule(snapshotPolicy.getVolumeId(), snapshotPolicy.getId()); return _snapshotPolicyDao.remove(policyId); } @Override public Pair, Integer> listPoliciesforVolume(ListSnapshotPoliciesCmd cmd) { Long volumeId = cmd.getVolumeId(); VolumeVO volume = _volsDao.findById(volumeId); if (volume == null) { throw new InvalidParameterValueException("Unable to find a volume with id " + volumeId); } _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume); Pair, Integer> result = _snapshotPolicyDao.listAndCountByVolumeId(volumeId); return new Pair, Integer>(result.first(), result.second()); } @Override public List listPoliciesforVolume(long volumeId) { return _snapshotPolicyDao.listByVolumeId(volumeId); } @Override public List listPoliciesforSnapshot(long snapshotId) { SearchCriteria sc = PoliciesForSnapSearch.create(); sc.setJoinParameters("policyRef", "snapshotId", snapshotId); return _snapshotPolicyDao.search(sc, null); } @Override public List listSnapsforPolicy(long policyId, Filter filter) { SearchCriteria sc = PolicySnapshotSearch.create(); sc.setJoinParameters("policy", "policyId", policyId); return _snapshotDao.search(sc, filter); } @Override public List listSnapsforVolume(long volumeId) { return _snapshotDao.listByVolumeId(volumeId); } public List listSnapsforVolumeType(long volumeId, Type type) { return _snapshotDao.listByVolumeIdType(volumeId, type); } @Override public void deletePoliciesForVolume(Long volumeId) { List policyInstances = listPoliciesforVolume(volumeId); for (SnapshotPolicyVO policyInstance : policyInstances) { Long policyId = policyInstance.getId(); deletePolicy(1L, policyId); } // We also want to delete the manual snapshots scheduled for this volume // We can only delete the schedules in the future, not the ones which are already executing. SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, Snapshot.MANUAL_POLICY_ID, false); if (snapshotSchedule != null) { _snapshotScheduleDao.expunge(snapshotSchedule.getId()); } } /** * {@inheritDoc} */ @Override public List findRecurringSnapshotSchedule(ListRecurringSnapshotScheduleCmd cmd) { Long volumeId = cmd.getVolumeId(); Long policyId = cmd.getSnapshotPolicyId(); Account account = UserContext.current().getCaller(); // Verify parameters VolumeVO volume = _volsDao.findById(volumeId); if (volume == null) { throw new InvalidParameterValueException("Failed to list snapshot schedule, unable to find a volume with id " + volumeId); } if (account != null) { long volAcctId = volume.getAccountId(); if (_accountMgr.isAdmin(account.getType())) { Account userAccount = _accountDao.findById(Long.valueOf(volAcctId)); if (!_domainDao.isChildDomain(account.getDomainId(), userAccount.getDomainId())) { throw new PermissionDeniedException("Unable to list snapshot schedule for volume " + volumeId + ", permission denied."); } } else if (account.getId() != volAcctId) { throw new PermissionDeniedException("Unable to list snapshot schedule, account " + account.getAccountName() + " does not own volume id " + volAcctId); } } // List only future schedules, not past ones. List snapshotSchedules = new ArrayList(); if (policyId == null) { List policyInstances = listPoliciesforVolume(volumeId); for (SnapshotPolicyVO policyInstance : policyInstances) { SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyInstance.getId(), false); snapshotSchedules.add(snapshotSchedule); } } else { snapshotSchedules.add(_snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, false)); } return snapshotSchedules; } @Override public SnapshotPolicyVO getPolicyForVolume(long volumeId) { return _snapshotPolicyDao.findOneByVolume(volumeId); } public Type getSnapshotType(Long policyId) { if (policyId.equals(Snapshot.MANUAL_POLICY_ID)) { return Type.MANUAL; } else { SnapshotPolicyVO spstPolicyVO = _snapshotPolicyDao.findById(policyId); IntervalType intvType = DateUtil.getIntervalType(spstPolicyVO.getInterval()); return getSnapshotType(intvType); } } public Type getSnapshotType(IntervalType intvType) { if (intvType.equals(IntervalType.HOURLY)) { return Type.HOURLY; } else if (intvType.equals(IntervalType.DAILY)) { return Type.DAILY; } else if (intvType.equals(IntervalType.WEEKLY)) { return Type.WEEKLY; } else if (intvType.equals(IntervalType.MONTHLY)) { return Type.MONTHLY; } return null; } @Override public SnapshotVO allocSnapshot(Long volumeId, Long policyId) throws ResourceAllocationException { Account caller = UserContext.current().getCaller(); VolumeVO volume = _volsDao.findById(volumeId); if (volume == null) { throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist"); } DataCenter zone = _dcDao.findById(volume.getDataCenterId()); if (zone == null) { throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId()); } if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getType())) { throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getName()); } if (volume.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot."); } if ( volume.getTemplateId() != null ) { VMTemplateVO template = _templateDao.findById(volume.getTemplateId()); if( template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM ) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported"); } } StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId()); if (storagePoolVO == null) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it"); } ClusterVO cluster = _clusterDao.findById(storagePoolVO.getClusterId()); if (cluster != null && cluster.getHypervisorType() == HypervisorType.Ovm) { throw new InvalidParameterValueException("Ovm won't support taking snapshot"); } // Verify permissions _accountMgr.checkAccess(caller, null, true, volume); Type snapshotType = getSnapshotType(policyId); Account owner = _accountMgr.getAccount(volume.getAccountId()); try{ _resourceLimitMgr.checkResourceLimit(owner, ResourceType.snapshot); } catch (ResourceAllocationException e){ if (snapshotType != Type.MANUAL){ String msg = "Snapshot resource limit exceeded for account id : " + owner.getId() + ". Failed to create recurring snapshots"; s_logger.warn(msg); _alertMgr.sendAlert(AlertManager.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Snapshot resource limit exceeded for account id : " + owner.getId() + ". Failed to create recurring snapshots; please use updateResourceLimit to increase the limit"); } throw e; } // Determine the name for this snapshot // Snapshot Name: VMInstancename + volumeName + timeString String timeString = DateUtil.getDateDisplayString(DateUtil.GMT_TIMEZONE, new Date(), DateUtil.YYYYMMDD_FORMAT); VMInstanceVO vmInstance = _vmDao.findById(volume.getInstanceId()); String vmDisplayName = "detached"; if (vmInstance != null) { vmDisplayName = vmInstance.getHostName(); } String snapshotName = vmDisplayName + "_" + volume.getName() + "_" + timeString; // Create the Snapshot object and save it so we can return it to the // user HypervisorType hypervisorType = this._volsDao.getHypervisorType(volumeId); SnapshotVO snapshotVO = new SnapshotVO(volume.getDataCenterId(), volume.getAccountId(), volume.getDomainId(), volume.getId(), volume.getDiskOfferingId(), null, snapshotName, (short) snapshotType.ordinal(), snapshotType.name(), volume.getSize(), hypervisorType); SnapshotVO snapshot = _snapshotDao.persist(snapshotVO); if (snapshot == null) { throw new CloudRuntimeException("Failed to create snapshot for volume: "+volumeId); } return snapshot; } @Override public boolean configure(String name, Map params) throws ConfigurationException { String value = _configDao.getValue(Config.BackupSnapshotWait.toString()); _backupsnapshotwait = NumbersUtil.parseInt(value, Integer.parseInt(Config.BackupSnapshotWait.getDefaultValue())); Type.HOURLY.setMax(NumbersUtil.parseInt(_configDao.getValue("snapshot.max.hourly"), HOURLYMAX)); Type.DAILY.setMax(NumbersUtil.parseInt(_configDao.getValue("snapshot.max.daily"), DAILYMAX)); Type.WEEKLY.setMax(NumbersUtil.parseInt(_configDao.getValue("snapshot.max.weekly"), WEEKLYMAX)); Type.MONTHLY.setMax(NumbersUtil.parseInt(_configDao.getValue("snapshot.max.monthly"), MONTHLYMAX)); _deltaSnapshotMax = NumbersUtil.parseInt(_configDao.getValue("snapshot.delta.max"), DELTAMAX); _totalRetries = NumbersUtil.parseInt(_configDao.getValue("total.retries"), 4); _pauseInterval = 2 * NumbersUtil.parseInt(_configDao.getValue("ping.interval"), 60); s_logger.info("Snapshot Manager is configured."); _snapshotFsm = Snapshot.State.getStateMachine(); _snapshotFsm.registerListener(new SnapshotStateListener()); return true; } @Override public boolean start() { return true; } @Override public boolean stop() { return true; } @Override public boolean deleteSnapshotPolicies(DeleteSnapshotPoliciesCmd cmd) { Long policyId = cmd.getId(); List policyIds = cmd.getIds(); Long userId = getSnapshotUserId(); if ((policyId == null) && (policyIds == null)) { throw new InvalidParameterValueException("No policy id (or list of ids) specified."); } if (policyIds == null) { policyIds = new ArrayList(); policyIds.add(policyId); } else if (policyIds.size() <= 0) { // Not even sure how this is even possible throw new InvalidParameterValueException("There are no policy ids"); } for (Long policy : policyIds) { SnapshotPolicyVO snapshotPolicyVO = _snapshotPolicyDao.findById(policy); if (snapshotPolicyVO == null) { throw new InvalidParameterValueException("Policy id given: " + policy + " does not exist"); } VolumeVO volume = _volsDao.findById(snapshotPolicyVO.getVolumeId()); if (volume == null) { throw new InvalidParameterValueException("Policy id given: " + policy + " does not belong to a valid volume"); } _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume); } boolean success = true; if (policyIds.contains(Snapshot.MANUAL_POLICY_ID)) { throw new InvalidParameterValueException("Invalid Policy id given: " + Snapshot.MANUAL_POLICY_ID); } for (Long pId : policyIds) { if (!deletePolicy(userId, pId)) { success = false; s_logger.warn("Failed to delete snapshot policy with Id: " + policyId); return success; } } return success; } private boolean hostSupportSnapsthot(HostVO host) { if (host.getHypervisorType() != HypervisorType.KVM) { return true; } // Determine host capabilities String caps = host.getCapabilities(); if (caps != null) { String[] tokens = caps.split(","); for (String token : tokens) { if (token.contains("snapshot")) { return true; } } } return false; } @Override public boolean canOperateOnVolume(VolumeVO volume) { List snapshots = _snapshotDao.listByStatus(volume.getId(), Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp); if (snapshots.size() > 0) { return false; } return true; } protected boolean stateTransitTo(Snapshot snapshot, Snapshot.Event e) throws NoTransitionException { return _snapshotFsm.transitTo(snapshot, e, null, _snapshotDao); } }