/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.storage.snapshot;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.BackupSnapshotAnswer;
import com.cloud.agent.api.BackupSnapshotCommand;
import com.cloud.agent.api.CreateVolumeFromSnapshotAnswer;
import com.cloud.agent.api.CreateVolumeFromSnapshotCommand;
import com.cloud.agent.api.DeleteSnapshotBackupCommand;
import com.cloud.agent.api.DeleteSnapshotsDirCommand;
import com.cloud.agent.api.ManageSnapshotAnswer;
import com.cloud.agent.api.ManageSnapshotCommand;
import com.cloud.agent.api.ValidateSnapshotAnswer;
import com.cloud.agent.api.ValidateSnapshotCommand;
import com.cloud.api.BaseCmd;
import com.cloud.api.commands.CreateSnapshotCmd;
import com.cloud.api.commands.CreateVolumeCmd;
import com.cloud.async.AsyncInstanceCreateStatus;
import com.cloud.async.AsyncJobExecutor;
import com.cloud.async.AsyncJobManager;
import com.cloud.async.AsyncJobVO;
import com.cloud.async.BaseAsyncJobExecutor;
import com.cloud.async.executor.SnapshotOperationParam;
import com.cloud.configuration.ResourceCount.ResourceType;
import com.cloud.configuration.dao.ConfigurationDao;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.event.EventState;
import com.cloud.event.EventTypes;
import com.cloud.event.EventVO;
import com.cloud.event.dao.EventDao;
import com.cloud.exception.InternalErrorException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.host.HostVO;
import com.cloud.host.dao.DetailsDao;
import com.cloud.host.dao.HostDao;
import com.cloud.serializer.GsonHelper;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotPolicyRefVO;
import com.cloud.storage.SnapshotPolicyVO;
import com.cloud.storage.SnapshotScheduleVO;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolVO;
import com.cloud.storage.VMTemplateHostVO;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.Snapshot.SnapshotType;
import com.cloud.storage.Snapshot.Status;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Volume.MirrorState;
import com.cloud.storage.Volume.StorageResourceType;
import com.cloud.storage.Volume.VolumeType;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotPolicyDao;
import com.cloud.storage.dao.SnapshotPolicyRefDao;
import com.cloud.storage.dao.SnapshotScheduleDao;
import com.cloud.storage.dao.StoragePoolDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplateHostDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.component.ComponentLocator;
import com.cloud.utils.component.Inject;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
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.vm.VMInstanceVO;
import com.cloud.vm.dao.UserVmDao;
import com.google.gson.Gson;
@Local(value={SnapshotManager.class})
public class SnapshotManagerImpl implements SnapshotManager {
private static final Logger s_logger = Logger.getLogger(SnapshotManagerImpl.class);
@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 UserDao _userDao;
@Inject protected SnapshotDao _snapshotDao;
@Inject protected StoragePoolDao _storagePoolDao;
@Inject protected EventDao _eventDao;
@Inject protected SnapshotPolicyDao _snapshotPolicyDao = null;
@Inject protected SnapshotPolicyRefDao _snapPolicyRefDao = null;
@Inject protected SnapshotScheduleDao _snapshotScheduleDao;
@Inject protected DetailsDao _detailsDao;
@Inject protected VMTemplateDao _templateDao;
@Inject protected VMTemplatePoolDao _templatePoolDao;
@Inject protected VMTemplateHostDao _templateHostDao;
@Inject protected StorageManager _storageMgr;
@Inject protected AgentManager _agentMgr;
@Inject protected SnapshotScheduler _snapSchedMgr;
@Inject protected AsyncJobManager _asyncMgr;
@Inject protected AccountManager _accountMgr;
String _name;
private int _totalRetries;
private int _pauseInterval;
protected SearchBuilder<SnapshotVO> PolicySnapshotSearch;
protected SearchBuilder<SnapshotPolicyVO> PoliciesForSnapSearch;
private final boolean _shouldBeSnapshotCapable = true; // all methods here should be snapshot capable.
@Override @DB
public long createSnapshotAsync(long userId, long volumeId, List<Long> policies) {
VolumeVO volume = _volsDao.findById(volumeId);
SnapshotOperationParam param = new SnapshotOperationParam(userId, volume.getAccountId(), volumeId, policies);
Gson gson = GsonHelper.getBuilder().create();
AsyncJobVO job = new AsyncJobVO();
job.setUserId(userId);
job.setAccountId(volume.getAccountId());
job.setCmd("CreateSnapshot");
job.setCmdInfo(gson.toJson(param));
job.setCmdOriginator(CreateSnapshotCmd.getResultObjectName());
return _asyncMgr.submitAsyncJob(job, true);
}
private boolean isVolumeDirty(long volumeId, List<Long> policies) {
VolumeVO volume = _volsDao.findById(volumeId);
boolean runSnap = true;
if (volume.getInstanceId() == null) {
long lastSnapId = _snapshotDao.getLastSnapshot(volumeId, 0);
SnapshotVO lastSnap = _snapshotDao.findById(lastSnapId);
if (lastSnap != null) {
Date lastSnapTime = lastSnap.getCreated();
if (lastSnapTime.after(volume.getUpdated())){
runSnap = false;
s_logger.debug("Volume: "+ volumeId +" is detached and last snap time is after Volume detach time. Skip snapshot for recurring policy");
}
}
} else if (_storageMgr.volumeInactive(volume)) {
// Volume is attached to a VM which is in Stopped state.
long lastSnapId = _snapshotDao.getLastSnapshot(volumeId, 0);
SnapshotVO lastSnap = _snapshotDao.findById(lastSnapId);
if (lastSnap != null) {
Date lastSnapTime = lastSnap.getCreated();
VMInstanceVO vmInstance = _vmDao.findById(volume.getInstanceId());
if (vmInstance !=null ) {
if (lastSnapTime.after(vmInstance.getUpdateTime())){
runSnap = false;
s_logger.debug("Volume: "+ volumeId +" is inactive and last snap time is after VM update time. Skip snapshot for recurring policy");
}
}
}
}
if (!runSnap){
if (policies.contains(Snapshot.MANUAL_POLICY_ID)) {
// Take a snapshot, but only for the manual policy
policies = new ArrayList<Long>();
policies.add(Snapshot.MANUAL_POLICY_ID);
runSnap = true;
s_logger.debug("Volume: "+ volumeId +" is detached/inactive. Executing snapshot for manual policy");
}
}
if (volume.getDestroyed() || volume.getRemoved() != null) {
s_logger.debug("Volume: " + volumeId + " is destroyed/removed. Not taking snapshot");
runSnap = false;
}
return runSnap;
}
@Override
public ImageFormat getImageFormat(Long volumeId) {
ImageFormat format = null;
VolumeVO volume = _volsDao.findById(volumeId);
Long templateId = volume.getTemplateId();
if (templateId != null) {
VMTemplateVO template = _templateDao.findById(templateId);
format = template.getFormat();
}
return format;
}
private boolean shouldRunSnapshot(long userId, VolumeVO volume, List<Long> policies) throws InvalidParameterValueException, ResourceAllocationException {
boolean runSnap = isVolumeDirty(volume.getId(), policies);
ImageFormat format = getImageFormat(volume.getId());
if (format != null) {
if (!(format == ImageFormat.VHD || format == ImageFormat.ISO)) {
// We only create snapshots for root disks created from templates or ISOs.
s_logger.error("Currently, a snapshot can be taken from a Root Disk only if it is created from a 1) template in VHD format or 2) from an ISO.");
runSnap = false;
}
}
if (runSnap) {
List<Long> policiesToBeRemoved = new ArrayList<Long>();
for (Long policyId : policies) {
// If it's a manual policy then it won't be there in the volume_snap_policy_ref table.
// We need to run the snapshot
if (policyId == Snapshot.MANUAL_POLICY_ID) {
// Check if the resource limit for snapshots has been exceeded
//UserVO user = _userDao.findById(userId);
//AccountVO account = _accountDao.findById(user.getAccountId());
AccountVO account = _accountDao.findById(volume.getAccountId());
if (_accountMgr.resourceLimitExceeded(account, ResourceType.snapshot)) {
throw new ResourceAllocationException("The maximum number of snapshots for account " + account.getAccountName() + " has been exceeded.");
}
}
else {
// Does volume have this policy assigned still
SnapshotPolicyVO volPolicy = _snapshotPolicyDao.findById(policyId);
if(volPolicy == null || !volPolicy.isActive()) {
// The policy has been removed for the volume. Don't run the snapshot for this policy
s_logger.debug("Policy " + policyId + " has been removed for the volume " + volume.getId() + ". Not running snapshot for this policy");
// Don't remove while iterating
policiesToBeRemoved.add(policyId);
}
}
}
// Remove the unnecessary policies out of the iterator.
policies.removeAll(policiesToBeRemoved);
if (policies.size() == 0) {
// There are no valid policies left for the snapshot. Don't execute it.
runSnap = false;
}
}
if (!runSnap) {
s_logger.warn("Snapshot for volume " + volume.getId() + " not created. No policy assigned currently.");
}
return runSnap;
}
@Override @DB
public SnapshotVO createSnapshot(long userId, long volumeId, List<Long> policyIds) throws InvalidParameterValueException, ResourceAllocationException {
// Get the async job id from the context.
Long jobId = null;
AsyncJobExecutor asyncExecutor = BaseAsyncJobExecutor.getCurrentExecutor();
if(asyncExecutor != null) {
// createSnapshot is always async. Hence asyncExecutor is always not null.
AsyncJobVO job = asyncExecutor.getJob();
jobId = job.getId();
}
Transaction txn = Transaction.currentTxn();
txn.start();
// set the async_job_id for this in the schedule queue so that it doesn't get scheduled again and block others.
// mark each of the coinciding schedules as executing in the job queue.
for (Long policyId : policyIds) {
SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, false);
assert snapshotSchedule != null;
snapshotSchedule.setAsyncJobId(jobId);
_snapshotScheduleDao.update(snapshotSchedule.getId(), snapshotSchedule);
}
txn.commit();
VolumeVO volume = _volsDao.findById(volumeId);
if (!shouldRunSnapshot(userId, volume, policyIds)) {
// A null snapshot is interpreted as snapshot creation failed which is what we want to indicate
return null;
}
// Gets the most recent snapshot taken. Could return 'removed' snapshots too.
long lastSnapshotId = _snapshotDao.getLastSnapshot(volumeId, -1);
Status snapshotStatus = Status.BackedUp;
if (lastSnapshotId != 0) {
// There was a previous snapshot.
SnapshotVO prevSnapshot = _snapshotDao.findById(lastSnapshotId);
snapshotStatus = prevSnapshot.getStatus();
if (prevSnapshot.getRemoved() != null && snapshotStatus != Status.BackedUp) {
// The snapshot was deleted and it was deleted not manually but because backing up failed.
// Try to back it up again.
boolean backedUp = backupSnapshotToSecondaryStorage(userId, prevSnapshot);
if (!backedUp) {
// If we can't backup this snapshot, there's not much chance that we can't take another one and back it up again.
return null;
}
}
}
SnapshotVO createdSnapshot = null;
Long id = null;
// 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.getDisplayName();
}
String snapshotName = vmDisplayName + "_" + volume.getName() + "_" + timeString;
// Create the Snapshot object and save it so we can return it to the user
SnapshotType snapshotType = SnapshotVO.getSnapshotType(policyIds);
SnapshotVO snapshotVO = new SnapshotVO(volume.getAccountId(), volume.getId(), null, snapshotName, (short)snapshotType.ordinal(), snapshotType.name());
txn = Transaction.currentTxn();
txn.start();
snapshotVO = _snapshotDao.persist(snapshotVO);
id = snapshotVO.getId();
assert id != null;
for (Long policyId : policyIds) {
// Get the snapshot_schedule table entry for this snapshot and policy id.
// Set the snapshotId to retrieve it back later.
SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, true);
assert snapshotSchedule != null;
snapshotSchedule.setSnapshotId(id);
_snapshotScheduleDao.update(snapshotSchedule.getId(), snapshotSchedule);
}
txn.commit();
// get previous snapshot Path
long preId = _snapshotDao.getLastSnapshot(volumeId, id);
String preSnapshotPath = null;
SnapshotVO preSnapshotVO = null;
if( preId != 0) {
preSnapshotVO = _snapshotDao.findById(preId);
preSnapshotPath = preSnapshotVO.getPath();
}
// Send a ManageSnapshotCommand to the agent
ManageSnapshotCommand cmd = new ManageSnapshotCommand(ManageSnapshotCommand.CREATE_SNAPSHOT, id, volume.getPath(), preSnapshotPath, snapshotName);
String basicErrMsg = "Failed to create snapshot for volume: " + volume.getId();
ManageSnapshotAnswer answer = (ManageSnapshotAnswer) _storageMgr.sendToHostsOnStoragePool(volume.getPoolId(), cmd, basicErrMsg, _totalRetries, _pauseInterval, _shouldBeSnapshotCapable);
txn = Transaction.currentTxn();
txn.start();
try {
// Update the snapshot in the database
if ((answer != null) && answer.getResult()) {
// The snapshot was successfully created
if (preSnapshotPath != null && preSnapshotPath == answer.getSnapshotPath()) {
if( preSnapshotVO.getRemoved() != null ) {
String backupPath = preSnapshotVO.getBackupSnapshotId();
_snapshotDao.delete(preId);
createdSnapshot = updateDBOnCreateDuplicate(id, answer.getSnapshotPath(), backupPath);
} else {
// empty snapshot
s_logger.debug("CreateSnapshot: this is empty snapshot, remove it ");
// delete from the snapshots table
_snapshotDao.delete(id);
throw new CloudRuntimeException(
" There is no change since last snapshot, please use last snapshot " + preSnapshotPath);
}
} else {
createdSnapshot = updateDBOnCreate(id, answer.getSnapshotPath());
}
} else {
String msg = "createSnapshotCommand returns null";
if (answer != null) {
msg = answer.getDetails();
s_logger.error(msg);
}
// delete from the snapshots table
_snapshotDao.delete(id);
throw new CloudRuntimeException(" CreateSnapshot failed due to " + msg);
}
// Update async status after snapshot creation and before backup
if (asyncExecutor != null) {
AsyncJobVO job = asyncExecutor.getJob();
if (s_logger.isDebugEnabled())
s_logger.debug("CreateSnapshot created a new instance " + id + ", update async job-" + job.getId()
+ " progress status");
_asyncMgr.updateAsyncJobAttachment(job.getId(), "snapshot", id);
_asyncMgr.updateAsyncJobStatus(job.getId(), BaseCmd.PROGRESS_INSTANCE_CREATED, id);
}
} finally {
txn.commit();
}
return createdSnapshot;
}
private SnapshotVO updateDBOnCreate(Long id, String snapshotPath) {
SnapshotVO createdSnapshot = _snapshotDao.findById(id);
Long volumeId = createdSnapshot.getVolumeId();
createdSnapshot.setPath(snapshotPath);
createdSnapshot.setStatus(Snapshot.Status.CreatedOnPrimary);
createdSnapshot.setPrevSnapshotId(_snapshotDao.getLastSnapshot(volumeId, id));
_snapshotDao.update(id, createdSnapshot);
return createdSnapshot;
}
private SnapshotVO updateDBOnCreateDuplicate(Long id, String snapshotPath, String backUpSnapshotPath) {
SnapshotVO createdSnapshot = _snapshotDao.findById(id);
Long volumeId = createdSnapshot.getVolumeId();
createdSnapshot.setPath(snapshotPath);
createdSnapshot.setBackupSnapshotId(backUpSnapshotPath);
createdSnapshot.setStatus(Snapshot.Status.BackedUp);
createdSnapshot.setPrevSnapshotId(_snapshotDao.getLastSnapshot(volumeId, id));
_snapshotDao.update(id, createdSnapshot);
return createdSnapshot;
}
@Override
@DB
@SuppressWarnings("fallthrough")
public void validateSnapshot(Long userId, SnapshotVO snapshot) {
assert snapshot != null;
Long id = snapshot.getId();
Status status = snapshot.getStatus();
s_logger.debug("Snapshot scheduler found a snapshot whose actual status is not clear. Snapshot id:" + id + " with DB status: " + status);
switch (status) {
case Creating:
// String snapshotUUID = snapshot.getPath();
// Long prevSnapshotId = snapshot.getPrevSnapshotId();
// All these will be null.
// But there the ManageSnapshotCommand might have succeeded on the primary without updating the database
// Run validatePreviousSnapshotBackup to check if that is the case.
// Depending on the result, complete the remaining part of the createSnapshot method.
// Call backupSnapshotToSecondaryStorage, if snapshot was actually taken on primary.
s_logger.debug("Management Server crashed before the ManageSnapshotCommand returned. Checking if snapshot was created on primary");
Long volumeId = snapshot.getVolumeId();
assert volumeId != null;
VolumeVO volume = _volsDao.findById(volumeId);
// By default, assume failure
String actualSnapshotUuid = null;
boolean createdOnPrimary = false;
ValidateSnapshotAnswer answer = getLastSnapshotDetails(volume, snapshot.getPath());
if (answer != null) {
if (answer.getResult()) {
// The expected snapshot details on the primary is the same as it would be if this snapshot was never taken at all
// Just delete the entry in the table
_snapshotDao.delete(id);
// Create an event saying the snapshot failed. ??
// An event is not generated when validatePreviousSnapshotBackup fails. So not generating it here too.
}
else {
// The answer is different from expected.
// This may be because the snapshot given was actually taken on primary, but DB update didn't happen.
// Now update the DB to denote that snapshot was created on primary and
// fall through to the next case.
actualSnapshotUuid = answer.getActualSnapshotUuid();
if (actualSnapshotUuid != null && !actualSnapshotUuid.isEmpty()) {
s_logger.debug("The snapshot " + id + " was actually created on the primary. Updating the DB record and backing it up to secondary");
updateDBOnCreate(id, actualSnapshotUuid);
createdOnPrimary = true;
}
}
}
if (!createdOnPrimary) {
break;
}
// else continue to the next case.
case CreatedOnPrimary:
// The snapshot has been created on the primary and the DB has been updated.
// However, it hasn't entered the backupSnapshotToSecondaryStorage, else
// status would have been backing up.
// So call backupSnapshotToSecondaryStorage without any fear.
case BackingUp:
// It has entered backupSnapshotToSecondaryStorage.
// But we have no idea whether it was backed up or not.
// So call backupSnapshotToSecondaryStorage again.
backupSnapshotToSecondaryStorage(userId, snapshot);
break;
case BackedUp:
// No need to do anything as snapshot has already been backed up.
}
}
@Override
@DB
public boolean backupSnapshotToSecondaryStorage(long userId, SnapshotVO snapshot) {
long id = snapshot.getId();
snapshot.setStatus(Snapshot.Status.BackingUp);
_snapshotDao.update(snapshot.getId(), snapshot);
long volumeId = snapshot.getVolumeId();
VolumeVO volume = _volsDao.findById(volumeId);
String primaryStoragePoolNameLabel = _storageMgr.getPrimaryStorageNameLabel(volume);
Long dcId = volume.getDataCenterId();
Long accountId = volume.getAccountId();
String secondaryStoragePoolUrl = _storageMgr.getSecondaryStorageURL(volume.getDataCenterId());
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;
boolean isFirstSnapshotOfRootVolume = false;
long prevSnapshotId = snapshot.getPrevSnapshotId();
if (prevSnapshotId > 0) {
prevSnapshot = _snapshotDao.findById(prevSnapshotId);
prevSnapshotUuid = prevSnapshot.getPath();
prevBackupUuid = prevSnapshot.getBackupSnapshotId();
}
else {
// This is the first snapshot of the volume.
if (volume.getVolumeType() == VolumeType.ROOT && getImageFormat(volumeId) != ImageFormat.ISO && volume.getTemplateId() != null) {
isFirstSnapshotOfRootVolume = true;
// If the first snapshot of the root volume is empty, it's parent will point to the base template.
// So pass the template uuid as the fake previous snapshot.
Long templateId = volume.getTemplateId();
// ROOT disks are created off templates have templateIds
assert templateId != null;
Long poolId = volume.getPoolId();
if (templateId != null && poolId != null) {
VMTemplateStoragePoolVO vmTemplateStoragePoolVO =
_templatePoolDao.findByPoolTemplate(poolId, templateId);
if (vmTemplateStoragePoolVO != null) {
prevSnapshotUuid = vmTemplateStoragePoolVO.getInstallPath();
}
else {
s_logger.warn("Volume id: " + volumeId +
" in pool id: " + poolId +
" based off template id: " + templateId +
" doesn't have an entry in the template_spool_ref table." +
" Using null as the template.");
}
}
}
}
String firstBackupUuid = volume.getFirstSnapshotBackupUuid();
boolean isVolumeInactive = _storageMgr.volumeInactive(volume);
BackupSnapshotCommand backupSnapshotCommand =
new BackupSnapshotCommand(primaryStoragePoolNameLabel,
secondaryStoragePoolUrl,
dcId,
accountId,
volumeId,
snapshotUuid,
prevSnapshotUuid,
prevBackupUuid,
firstBackupUuid,
isFirstSnapshotOfRootVolume,
isVolumeInactive);
String backedUpSnapshotUuid = null;
// By default, assume failed.
String basicErrMsg = "Failed to backup snapshot id " + snapshot.getId() + " to secondary storage for volume: " + volumeId;
boolean backedUp = false;
BackupSnapshotAnswer answer = (BackupSnapshotAnswer) _storageMgr.sendToHostsOnStoragePool(volume.getPoolId(),
backupSnapshotCommand,
basicErrMsg,
_totalRetries,
_pauseInterval,
_shouldBeSnapshotCapable);
if (answer != null && answer.getResult()) {
backedUpSnapshotUuid = answer.getBackupSnapshotName();
if (backedUpSnapshotUuid != null) {
backedUp = true;
// is there a snap to be deleted?
// clean now
if(prevSnapshot != null && backedUpSnapshotUuid.equalsIgnoreCase(prevSnapshot.getBackupSnapshotId())) {
//if new snapshot is same as previous snapshot , delete previous snapshot
s_logger.debug("Delete duplicate Snapshot id: " + prevSnapshotId);
long pprevSnapshotId = prevSnapshot.getPrevSnapshotId();
snapshot.setPrevSnapshotId(pprevSnapshotId);
_snapshotDao.update(snapshot.getId(), snapshot);
_snapshotDao.delete(prevSnapshot.getId());
EventVO event = new EventVO();
String eventParams = "id=" + prevSnapshot.getId() + "\nssName=" + prevSnapshot.getName();
event.setType(EventTypes.EVENT_SNAPSHOT_DELETE);
event.setState(EventState.Completed);
event.setDescription("Delete snapshot id: " + prevSnapshot.getId() + " due to new snapshot is same as this one");
event.setLevel(EventVO.LEVEL_INFO);
event.setParameters(eventParams);
_eventDao.persist(event);
prevSnapshotId = pprevSnapshotId;
if( prevSnapshotId == 0 ) {
prevSnapshot = null;
prevSnapshotUuid = null;
prevBackupUuid = null;
} else {
prevSnapshot = _snapshotDao.findById(prevSnapshotId);
prevSnapshotUuid = prevSnapshot.getPath();
prevBackupUuid = prevSnapshot.getBackupSnapshotId();
}
}
// if previous snapshot is marked as Removed, remove it now
if(prevSnapshot != null && prevSnapshot.getRemoved() != null) {
s_logger.debug("Snapshot id: " + prevSnapshotId + " was marked as removed. Deleting it from the primary/secondary/DB now.");
// Get the prevSnapshotId of the snapshot to be deleted.
// This will become the prevSnapshotId of the current snapshot
long prevValidSnapshotId = prevSnapshot.getPrevSnapshotId();
String prevValidSnapshotBackupUuid = null;
if (prevValidSnapshotId > 0) {
prevValidSnapshotBackupUuid = _snapshotDao.findById(prevValidSnapshotId).getBackupSnapshotId();
}
snapshot.setPrevSnapshotId(prevValidSnapshotId);
_snapshotDao.update(id, snapshot);
backedUp = destroyLastSnapshot(prevValidSnapshotBackupUuid, prevSnapshot, backedUpSnapshotUuid);
if (!backedUp) {
s_logger.debug("Error while deleting last snapshot id: " + prevSnapshotId + " for volume " + volumeId);
}
}
}
}
else if (answer != null) {
s_logger.error(answer.getDetails());
}
// Update the status in all cases.
Transaction txn = Transaction.currentTxn();
txn.start();
SnapshotVO snapshotVO = _snapshotDao.findById(id);
snapshotVO.setBackupSnapshotId(backedUpSnapshotUuid);
if (volume.getFirstSnapshotBackupUuid() == null) {
// This is the first ever snapshot taken for the volume.
// Set the first snapshot backup uuid once and for all.
// XXX: This will get set to non-null only if we were able to backup the first snapshot
// successfully. If we didn't backup the first snapshot, the volume is essentially
// screwed as far as snapshots are concerned.
volume.setFirstSnapshotBackupUuid(backedUpSnapshotUuid);
_volsDao.update(volumeId, volume);
}
// Create an event
EventVO event = new EventVO();
event.setUserId(userId);
event.setAccountId(volume.getAccountId());
event.setType(EventTypes.EVENT_SNAPSHOT_CREATE);
String snapshotName = snapshotVO.getName();
if (backedUp) {
snapshotVO.setStatus(Snapshot.Status.BackedUp);
String eventParams = "id=" + id + "\nssName=" + snapshotName +"\nsize=" + volume.getSize()+"\ndcId=" + volume.getDataCenterId();
event.setDescription("Backed up snapshot id: " + id + " to secondary for volume " + volumeId);
event.setLevel(EventVO.LEVEL_INFO);
event.setParameters(eventParams);
_snapshotDao.update(id, snapshotVO);
}
else {
// Just mark it as removed in the database. When the next snapshot it taken,
// validate previous snapshot will fix the state.
// It will
// 1) Call backupSnapshotToSecondaryStorage and try again.
// 2) Create the next Snapshot pretending this is a valid snapshot.
// 3) backupSnapshotToSecondaryStorage of the next snapshot
// will take care of cleaning up the state of this snapshot
_snapshotDao.remove(id);
event.setLevel(EventVO.LEVEL_ERROR);
event.setDescription("Failed to backup snapshot id: " + id + " to secondary for volume " + volumeId);
}
// Save the event
_eventDao.persist(event);
txn.commit();
return backedUp;
}
@Override
@DB
public void postCreateSnapshot(long userId, long volumeId, long snapshotId, List<Long> policyIds, boolean backedUp) {
// Update the snapshot_policy_ref table with the created snapshot
// Get the list of policies for this snapshot
Transaction txn = Transaction.currentTxn();
txn.start();
for (long policyId : policyIds) {
if (backedUp) {
// create an entry in snap_policy_ref table
SnapshotPolicyRefVO snapPolicyRef = new SnapshotPolicyRefVO(snapshotId, volumeId, policyId);
_snapPolicyRefDao.persist(snapPolicyRef);
// This is a manual create, so increment the count of snapshots for this account
if (policyId == Snapshot.MANUAL_POLICY_ID) {
Snapshot snapshot = _snapshotDao.findById(snapshotId);
_accountMgr.incrementResourceCount(snapshot.getAccountId(), ResourceType.snapshot);
}
}
// Even if the current snapshot failed, we should schedule the next recurring snapshot for this policy.
if (policyId != Snapshot.MANUAL_POLICY_ID) {
postCreateRecurringSnapshotForPolicy(userId, volumeId, snapshotId, policyId);
}
else {
// Delete the entry from the snapshot_schedule table so that the
// next manual snapshot can be taken.
// Get the schedule of this snapshot
SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, true);
if (snapshotSchedule != null) {
// We should lock the row before deleting it as it is also being deleted by the scheduler.
_snapshotScheduleDao.delete(snapshotSchedule.getId());
}
}
}
txn.commit();
}
private ValidateSnapshotAnswer getLastSnapshotDetails(VolumeVO volume, String previousSnapshotUuid) {
// Validate the VDI parent structure for the volume on the primary storage
Long volumeId = volume.getId();
String primaryStoragePoolNameLabel = _storageMgr.getPrimaryStorageNameLabel(volume);
String volumeUuid = volume.getPath();
Long poolId = volume.getPoolId();
String firstSnapshotBackupUuid = volume.getFirstSnapshotBackupUuid();
String templateUuid = null;
String details = null;
if (firstSnapshotBackupUuid == null && volume.getVolumeType() == VolumeType.ROOT) {
Long templateId = volume.getTemplateId();
VMTemplateVO template = _templateDao.findById(templateId);
if(template == null) {
details = "Unable find template id: " + templateId + " for root disk volumeId: " + volumeId;
s_logger.error(details);
}
else if (template.getFormat() == ImageFormat.VHD) {
// We support creating snapshots of Root Disk created from template only in VHD format.
VMTemplateStoragePoolVO templateStoragePoolVO = _templatePoolDao.findByPoolTemplate(volume.getPoolId(), templateId);
if (templateStoragePoolVO != null) {
templateUuid = templateStoragePoolVO.getInstallPath();
}
else {
details = "Template id: " + templateId + " is not present in on the primary storage pool id: " + volume.getPoolId() +
" according to the template_spool_ref table";
s_logger.error(details);
}
}
}
ValidateSnapshotAnswer answer = null;
if (details == null) {
// EverythingBackup is fine until now. Proceed with command.
ValidateSnapshotCommand cmd =
new ValidateSnapshotCommand(primaryStoragePoolNameLabel, volumeUuid, firstSnapshotBackupUuid, previousSnapshotUuid, templateUuid);
String basicErrMsg = "Failed to validate VDI structure for volumeId: " + volume.getId() + " with UUID: " + volumeUuid;
answer = (ValidateSnapshotAnswer) _storageMgr.sendToHostsOnStoragePool(poolId,
cmd,
basicErrMsg,
_totalRetries,
_pauseInterval,
_shouldBeSnapshotCapable);
}
return answer;
}
private void postCreateRecurringSnapshotForPolicy(long userId, long volumeId, long snapshotId, long policyId) {
//Use count query
Filter searchFilter = new Filter(SnapshotVO.class, GenericDaoBase.CREATED_COLUMN, true, null, null);
List<SnapshotVO> snaps = listSnapsforPolicy(policyId, searchFilter);
SnapshotPolicyVO policy = _snapshotPolicyDao.findById(policyId);
while(snaps.size() > policy.getMaxSnaps() && snaps.size() > 1) {
//Delete the oldest snap ref in snap_policy_ref
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);
// Excess snapshot. delete it asynchronously
destroySnapshotAsync(userId, volumeId, oldSnapId, policyId);
snaps.remove(oldestSnapshot);
}
}
@Override @DB
public boolean deleteSnapshot(long userId, long snapshotId, long policyId) {
s_logger.debug("Calling deleteSnapshot for snapshotId: " + snapshotId + " and policyId " + policyId);
long prevSnapshotId = 0;
SnapshotVO nextSnapshot = null;
boolean deleted = true;
boolean actuallyDelete = false;
List<SnapshotPolicyRefVO> snapPolicyRefs = _snapPolicyRefDao.listBySnapshotId(snapshotId);
// Destroy snapshot if its not part of any policy other than the given one.
if(snapPolicyRefs.size() == 1 && (snapPolicyRefs.get(0).getPolicyId() == policyId)) {
SnapshotVO currentSnapshot = _snapshotDao.findById(snapshotId);
String backupOfSnapshot = currentSnapshot.getBackupSnapshotId();
nextSnapshot = _snapshotDao.findNextSnapshot(snapshotId);
String backupOfNextSnapshot = null;
if (nextSnapshot != null) {
backupOfNextSnapshot = nextSnapshot.getBackupSnapshotId();
}
prevSnapshotId = currentSnapshot.getPrevSnapshotId();
String backupOfPreviousSnapshot = null;
if (prevSnapshotId > 0) {
SnapshotVO prevSnapshot = _snapshotDao.findById(prevSnapshotId);
backupOfPreviousSnapshot = prevSnapshot.getBackupSnapshotId();
}
if (backupOfSnapshot != null) {
if (backupOfNextSnapshot != null && backupOfSnapshot.equals(backupOfNextSnapshot)) {
// Both the snapshots point to the same backup VHD file.
// There is no difference in the data between them.
// We don't want to delete the backup of the older snapshot
// as it means that we delete the next snapshot too
}
else if (backupOfPreviousSnapshot != null && backupOfSnapshot.equals(backupOfPreviousSnapshot)) {
// If we delete the current snapshot, the user will not
// be able to recover from the previous snapshot
// So don't delete anything
}
else {
actuallyDelete = true;
deleted = destroySnapshot(userId, snapshotId, policyId);
}
if (!actuallyDelete) {
// Don't actually delete the snapshot backup but delete the entry
// from both snapshots and snapshot_policy_ref table
boolean isLastSnap = (nextSnapshot == null);
postDeleteSnapshot(snapshotId, policyId, isLastSnap);
// create the event
String eventParams = "id=" + snapshotId;
EventVO event = new EventVO();
event.setUserId(userId);
event.setAccountId((currentSnapshot != null) ? currentSnapshot.getAccountId() : 0);
event.setType(EventTypes.EVENT_SNAPSHOT_DELETE);
event.setDescription("Successfully deleted snapshot " + snapshotId + " for volumeId: " + currentSnapshot.getVolumeId() + " and policyId " + policyId);
event.setParameters(eventParams);
event.setLevel(EventVO.LEVEL_INFO);
_eventDao.persist(event);
}
}
}
else {
// Just delete the entry from the snapshot_policy_ref table
Transaction txn = Transaction.currentTxn();
txn.start();
_snapPolicyRefDao.removeSnapPolicy(snapshotId, policyId);
txn.commit();
}
return deleted;
}
@Override @DB
public long destroySnapshotAsync(long userId, long volumeId, long snapshotId, long policyId) {
VolumeVO volume = _volsDao.findById(volumeId);
SnapshotOperationParam param = new SnapshotOperationParam(userId, volume.getAccountId(), volumeId, snapshotId, policyId);
Gson gson = GsonHelper.getBuilder().create();
AsyncJobVO job = new AsyncJobVO();
job.setUserId(userId);
job.setAccountId(volume.getAccountId());
job.setCmd("DeleteSnapshot");
job.setCmdInfo(gson.toJson(param));
job.setCmdOriginator(CreateSnapshotCmd.getResultObjectName());
return _asyncMgr.submitAsyncJob(job, true);
}
@Override @DB
public boolean destroySnapshot(long userId, long snapshotId, long policyId) {
boolean success = false;
String details = null;
SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
if (snapshot != null) {
SnapshotVO nextSnapshot = _snapshotDao.findNextSnapshot(snapshotId);
if(nextSnapshot == null){
// This is last snapshot.
// Destroy this snapshot after creation of next snapshot. Only mark as removed in DB
details = "Successfully deleted snapshot " + snapshotId + " for volumeId: " + snapshot.getVolumeId() + " and policyId " + policyId;
s_logger.debug("This is last snapshot for volume. Not destroying now: " + snapshot.getId());
postDeleteSnapshot(snapshotId, policyId, true);
success = true;
} else {
VolumeVO volume = _volsDao.findById(snapshot.getVolumeId());
String primaryStoragePoolNameLabel = _storageMgr.getPrimaryStorageNameLabel(volume);
String secondaryStoragePoolUrl = _storageMgr.getSecondaryStorageURL(volume.getDataCenterId());
Long dcId = volume.getDataCenterId();
Long accountId = volume.getAccountId();
Long volumeId = volume.getId();
String backupOfSnapshot = snapshot.getBackupSnapshotId();
String backupOfNextSnapshot = null;
if (nextSnapshot != null) {
backupOfNextSnapshot = nextSnapshot.getBackupSnapshotId();
}
DeleteSnapshotBackupCommand cmd =
new DeleteSnapshotBackupCommand(primaryStoragePoolNameLabel,
secondaryStoragePoolUrl,
dcId,
accountId,
volumeId,
backupOfSnapshot,
backupOfNextSnapshot);
details = "Failed to destroy snapshot id:" + snapshotId + " for volume: " + volume.getId();
Answer answer = _storageMgr.sendToHostsOnStoragePool(volume.getPoolId(),
cmd,
details,
_totalRetries,
_pauseInterval,
_shouldBeSnapshotCapable);
if ((answer != null) && answer.getResult()) {
// This is not the last snapshot.
postDeleteSnapshot(snapshotId, policyId, false);
success = true;
details = "Successfully deleted snapshot " + snapshotId + " for volumeId: " + volumeId + " and policyId " + policyId;
s_logger.debug(details);
}
else if (answer != null) {
if (answer.getDetails() != null) {
details = answer.getDetails();
}
s_logger.error(details);
}
}
}
// create the event
String eventParams = "id=" + snapshotId;
EventVO event = new EventVO();
event.setUserId(userId);
event.setAccountId((snapshot != null) ? snapshot.getAccountId() : 0);
event.setType(EventTypes.EVENT_SNAPSHOT_DELETE);
event.setDescription(details);
event.setParameters(eventParams);
event.setLevel(success ? EventVO.LEVEL_INFO : EventVO.LEVEL_ERROR);
_eventDao.persist(event);
return success;
}
@DB
protected void postDeleteSnapshot(long snapshotId, long policyId, boolean isLastSnap) {
// Remove the snapshot from the snapshots table and the snap_policy_ref table.
Transaction txn = Transaction.currentTxn();
txn.start();
SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
if(isLastSnap){
_snapshotDao.remove(snapshotId);
} else {
_snapshotDao.delete(snapshotId);
// In the snapshots table,
// the last_snapshot_id field of the next snapshot becomes the last_snapshot_id of the deleted snapshot
long prevSnapshotId = snapshot.getPrevSnapshotId();
SnapshotVO nextSnapshot = _snapshotDao.findNextSnapshot(snapshotId);
assert nextSnapshot != null; // That is how lastSnap is decided.
nextSnapshot.setPrevSnapshotId(prevSnapshotId);
_snapshotDao.update(nextSnapshot.getId(), nextSnapshot);
}
_snapPolicyRefDao.removeSnapPolicy(snapshotId, policyId);
// If this is a manual delete, decrement the count of snapshots for this account
if (policyId == Snapshot.MANUAL_POLICY_ID) {
_accountMgr.decrementResourceCount(snapshot.getAccountId(), ResourceType.snapshot);
}
txn.commit();
}
@Override
public long createVolumeFromSnapshotAsync(long userId, long accountId, long snapshotId, String volumeName) throws InternalErrorException {
// Precondition the snapshot is valid
SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
VolumeVO volume = _volsDao.findById(snapshot.getVolumeId());
EventVO event = new EventVO();
event.setUserId(userId);
event.setAccountId(accountId);
event.setType(EventTypes.EVENT_VOLUME_CREATE);
event.setState(EventState.Scheduled);
event.setDescription("Scheduled async job for creating volume from snapshot with id: "+snapshotId);
event = _eventDao.persist(event);
SnapshotOperationParam param = new SnapshotOperationParam(userId, accountId, volume.getId(), snapshotId, volumeName);
param.setEventId(event.getId());
Gson gson = GsonHelper.getBuilder().create();
AsyncJobVO job = new AsyncJobVO();
job.setUserId(userId);
job.setAccountId(snapshot.getAccountId());
job.setCmd("CreateVolumeFromSnapshot");
job.setCmdInfo(gson.toJson(param));
job.setCmdOriginator(CreateVolumeCmd.getResultObjectName());
return _asyncMgr.submitAsyncJob(job);
}
@Override
public boolean deleteSnapshotDirsForAccount(long accountId) {
List<VolumeVO> 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();
String secondaryStoragePoolURL = _storageMgr.getSecondaryStorageURL(dcId);
String primaryStoragePoolNameLabel = _storageMgr.getPrimaryStorageNameLabel(volume);
long mostRecentSnapshotId = _snapshotDao.getLastSnapshot(volumeId, -1L);
if (mostRecentSnapshotId == 0L) {
// This volume doesn't have any snapshots. Nothing do delete.
continue;
}
SnapshotVO mostRecentSnapshot = _snapshotDao.findById(mostRecentSnapshotId);
if (mostRecentSnapshot == null) {
// Huh. The code should never reach here.
s_logger.error("Volume Id's mostRecentSnapshot with id: " + mostRecentSnapshotId + " turns out to be null");
}
// even if mostRecentSnapshot.removed() != null, we still have to explicitly remove it from the primary storage.
// Then deleting the volume VDI will GC the base copy and nothing will be left on primary storage.
String mostRecentSnapshotUuid = mostRecentSnapshot.getPath();
DeleteSnapshotsDirCommand cmd = new DeleteSnapshotsDirCommand(primaryStoragePoolNameLabel, secondaryStoragePoolURL, dcId, accountId, volumeId, mostRecentSnapshotUuid);
String basicErrMsg = "Failed to destroy snapshotsDir for: " + volume.getId() + " under account: " + accountId;
Answer answer = null;
Long poolId = volume.getPoolId();
if (poolId != null) {
// Retry only once for this command. There's low chance of failure because of a connection problem.
answer = _storageMgr.sendToHostsOnStoragePool(poolId, cmd, basicErrMsg, 1, _pauseInterval, _shouldBeSnapshotCapable);
}
else {
s_logger.info("Pool id for volume id: " + volumeId + " belonging to account id: " + accountId + " is null. Assuming the snapshotsDir for the account has already been deleted");
}
if (success) {
// SnapshotsDir has been deleted for the volumes so far.
success = (answer != null) && answer.getResult();
if (success) {
s_logger.debug("Deleted snapshotsDir for volume: " + volumeId + " under account: " + accountId);
}
else if (answer != null) {
s_logger.error(answer.getDetails());
}
}
// Either way delete the snapshots for this volume.
List<SnapshotVO> snapshots = listSnapsforVolume(volumeId);
for (SnapshotVO snapshot: snapshots) {
if(_snapshotDao.delete(snapshot.getId())){
_accountMgr.decrementResourceCount(accountId, ResourceType.snapshot);
//Log event after successful deletion
String eventParams = "id=" + snapshot.getId();
EventVO event = new EventVO();
event.setUserId(1L);
event.setAccountId(snapshot.getAccountId());
event.setType(EventTypes.EVENT_SNAPSHOT_DELETE);
event.setDescription("Successfully deleted snapshot " + snapshot.getId() + " for volumeId: " + snapshot.getVolumeId());
event.setParameters(eventParams);
event.setLevel(EventVO.LEVEL_INFO);
_eventDao.persist(event);
}
}
}
// Returns true if snapshotsDir has been deleted for all volumes.
return success;
}
@Override
@DB
public SnapshotPolicyVO createPolicy(long userId, long accountId, long volumeId, String schedule, short interval, int maxSnaps, String timezone) {
Long policyId = null;
SnapshotPolicyVO policy = getPolicyForVolumeByInterval(volumeId, (interval));
Transaction txn = Transaction.currentTxn();
txn.start();
// Create an event
EventVO event = new EventVO();
event.setAccountId(accountId);
event.setUserId(userId);
if( policy != null){
s_logger.debug("Policy for specified interval already exists. Updating policy to new schedule");
policyId = policy.getId();
// By default, assume failure.
event.setType(EventTypes.EVENT_SNAPSHOT_POLICY_UPDATE);
event.setDescription("Failed to update schedule for Snapshot policy with id: "+policyId);
event.setLevel(EventVO.LEVEL_ERROR);
// Check if there are any recurring snapshots being currently executed. Race condition.
SnapshotScheduleVO snapshotSchedule = _snapshotScheduleDao.getCurrentSchedule(volumeId, policyId, true);
if (snapshotSchedule != null) {
Date scheduledTimestamp = snapshotSchedule.getScheduledTimestamp();
String dateDisplay = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
s_logger.debug("Cannot update the policy now. Wait until the current snapshot scheduled at " + dateDisplay + " finishes");
policyId = null;
policy = null;
}
else {
_snapSchedMgr.removeSchedule(volumeId, policyId);
policy.setSchedule(schedule);
policy.setTimezone(timezone);
policy.setMaxSnaps(maxSnaps);
policy.setActive(true);
if(_snapshotPolicyDao.update(policy.getId(), policy)){
event.setLevel(EventVO.LEVEL_INFO);
event.setDescription("Successfully updated snapshot policy with Id: "+policyId);
}
}
} else {
policy = new SnapshotPolicyVO(volumeId, schedule, timezone, interval, maxSnaps);
policy = _snapshotPolicyDao.persist(policy);
policyId = policy.getId();
event.setType(EventTypes.EVENT_SNAPSHOT_POLICY_CREATE);
event.setDescription("Successfully created snapshot policy with Id: "+policyId);
}
_eventDao.persist(event);
if (policyId != null) {
_snapSchedMgr.scheduleNextSnapshotJob(policy);
}
else {
s_logger.debug("Failed to update schedule for Snapshot policy with id: " + policyId);
}
txn.commit();
return policy;
}
@Override
public boolean deletePolicy(long userId, long policyId) {
SnapshotPolicyVO snapshotPolicy = _snapshotPolicyDao.findById(policyId);
VolumeVO volume = _volsDao.findById(snapshotPolicy.getVolumeId());
snapshotPolicy.setActive(false);
_snapSchedMgr.removeSchedule(snapshotPolicy.getVolumeId(), snapshotPolicy.getId());
EventVO event = new EventVO();
event.setAccountId(volume.getAccountId());
event.setUserId(userId);
event.setType(EventTypes.EVENT_SNAPSHOT_POLICY_DELETE);
boolean success = _snapshotPolicyDao.update(policyId, snapshotPolicy);
if(success){
event.setLevel(EventVO.LEVEL_INFO);
event.setDescription("Successfully deleted snapshot policy with Id: "+policyId);
} else {
event.setLevel(EventVO.LEVEL_ERROR);
event.setDescription("Failed to delete snapshot policy with Id: "+policyId);
}
_eventDao.persist(event);
return success;
}
@Override
public List<SnapshotPolicyVO> listPoliciesforVolume(long volumeId) {
return _snapshotPolicyDao.listByVolumeId(volumeId);
}
@Override
public List<SnapshotPolicyVO> listPoliciesforSnapshot(long snapshotId) {
SearchCriteria sc = PoliciesForSnapSearch.create();
sc.setJoinParameters("policyRef", "snapshotId", snapshotId);
return _snapshotPolicyDao.search(sc, null);
}
@Override
public List<SnapshotVO> listSnapsforPolicy(long policyId, Filter filter) {
SearchCriteria sc = PolicySnapshotSearch.create();
sc.setJoinParameters("policy", "policyId", policyId);
return _snapshotDao.search(sc, filter);
}
@Override
public List<SnapshotVO> listSnapsforVolume(long volumeId) {
return _snapshotDao.listByVolumeId(volumeId);
}
@Override
public void deletePoliciesForVolume(Long volumeId) {
List<SnapshotPolicyVO> 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.delete(snapshotSchedule.getId());
}
}
private boolean destroyLastSnapshot(String backupOfPreviousSnapshot, SnapshotVO snapshot, String backupOfNextSnapshot) {
boolean success = false;
long snapshotId = snapshot.getId();
if (snapshot != null) {
String backupOfSnapshot = snapshot.getBackupSnapshotId();
if (backupOfSnapshot != null) {
if (backupOfNextSnapshot != null && backupOfSnapshot.equals(backupOfNextSnapshot)) {
// Both the snapshots point to the same backup VHD file.
// There is no difference in the data between them.
// We don't want to delete the backup of the older snapshot
// as it means that we delete the next snapshot too
success = true;
s_logger.debug("Removed snapshot " + snapshotId +
" is not being destroyed from secondary as " +
"it is the same as the current snapshot uuid: " + backupOfNextSnapshot);
}
else if (backupOfPreviousSnapshot != null && backupOfSnapshot.equals(backupOfPreviousSnapshot)) {
// If we delete the current snapshot, the user will not
// be able to recover from the previous snapshot
// So don't delete anything
success = true;
s_logger.debug("Removed snapshot " + snapshotId +
" is not being destroyed from secondary as " +
"it is the same as it's previous snapshot with uuid: " + backupOfPreviousSnapshot);
} else {
VolumeVO volume = _volsDao.findById(snapshot.getVolumeId());
String primaryStoragePoolNameLabel = _storageMgr.getPrimaryStorageNameLabel(volume);
String secondaryStoragePoolUrl = _storageMgr.getSecondaryStorageURL(volume.getDataCenterId());
Long dcId = volume.getDataCenterId();
Long accountId = volume.getAccountId();
Long volumeId = volume.getId();
DeleteSnapshotBackupCommand cmd =
new DeleteSnapshotBackupCommand(primaryStoragePoolNameLabel,
secondaryStoragePoolUrl,
dcId,
accountId,
volumeId,
backupOfSnapshot,
backupOfNextSnapshot);
String basicErrMsg = "Failed to destroy snapshot id: " + snapshotId + " for volume id: " + volumeId;
Answer answer = _storageMgr.sendToHostsOnStoragePool(volume.getPoolId(), cmd, basicErrMsg, _totalRetries, _pauseInterval, _shouldBeSnapshotCapable);
if ((answer != null) && answer.getResult()) {
success = true;
s_logger.debug("Successfully deleted last snapshot: " + snapshotId + " for volume id: " + volumeId);
}
else if (answer != null) {
s_logger.error(answer.getDetails());
}
}
}
}
if(success){
_snapshotDao.delete(snapshot.getId());
}
return success;
}
/**
* {@inheritDoc}
*/
@Override
public List<SnapshotScheduleVO> findRecurringSnapshotSchedule(Long volumeId, Long policyId) {
// List only future schedules, not past ones.
List<SnapshotScheduleVO> snapshotSchedules = new ArrayList<SnapshotScheduleVO>();
if (policyId == null) {
List<SnapshotPolicyVO> 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 getPolicyForVolumeByInterval(long volumeId,
short interval) {
return _snapshotPolicyDao.findOneByVolumeInterval(volumeId, interval);
}
@Override
public boolean configure(String name, Map<String, Object> params)
throws ConfigurationException {
_name = name;
ComponentLocator locator = ComponentLocator.getCurrentLocator();
ConfigurationDao configDao = locator.getDao(ConfigurationDao.class);
if (configDao == null) {
throw new ConfigurationException("Unable to get the configuration dao.");
}
DateUtil.IntervalType.HOURLY.setMax(NumbersUtil.parseInt(configDao.getValue("snapshot.max.hourly"), HOURLYMAX));
DateUtil.IntervalType.DAILY.setMax(NumbersUtil.parseInt(configDao.getValue("snapshot.max.daily"), DAILYMAX));
DateUtil.IntervalType.WEEKLY.setMax(NumbersUtil.parseInt(configDao.getValue("snapshot.max.weekly"), WEEKLYMAX));
DateUtil.IntervalType.MONTHLY.setMax(NumbersUtil.parseInt(configDao.getValue("snapshot.max.monthly"), MONTHLYMAX));
_totalRetries = NumbersUtil.parseInt(configDao.getValue("total.retries"), 4);
_pauseInterval = 2*NumbersUtil.parseInt(configDao.getValue("ping.interval"), 60);
SearchBuilder<SnapshotPolicyRefVO> policySearch = _snapPolicyRefDao.createSearchBuilder();
policySearch.and("policyId", policySearch.entity().getPolicyId(), SearchCriteria.Op.EQ);
PolicySnapshotSearch = _snapshotDao.createSearchBuilder();
PolicySnapshotSearch.join("policy", policySearch, policySearch.entity().getSnapshotId(), PolicySnapshotSearch.entity().getId());
policySearch.done();
PolicySnapshotSearch.done();
PoliciesForSnapSearch = _snapshotPolicyDao.createSearchBuilder();
SearchBuilder<SnapshotPolicyRefVO> policyRefSearch = _snapPolicyRefDao.createSearchBuilder();
policyRefSearch.and("snapshotId", policyRefSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
PoliciesForSnapSearch.join("policyRef", policyRefSearch, policyRefSearch.entity().getPolicyId(), PoliciesForSnapSearch.entity().getId());
policyRefSearch.done();
PoliciesForSnapSearch.done();
s_logger.info("Snapshot Manager is configured.");
return true;
}
@Override
public String getName() {
return _name;
}
@Override
public boolean start() {
return true;
}
@Override
public boolean stop() {
return true;
}
}