blob: b6549740a4adf6a1ed36b5b0c160d6534c6a0926 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.storage.vmsnapshot;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotOptions;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.CreateVMSnapshotAnswer;
import com.cloud.agent.api.CreateVMSnapshotCommand;
import com.cloud.agent.api.DeleteVMSnapshotAnswer;
import com.cloud.agent.api.DeleteVMSnapshotCommand;
import com.cloud.agent.api.FreezeThawVMAnswer;
import com.cloud.agent.api.FreezeThawVMCommand;
import com.cloud.agent.api.RevertToVMSnapshotAnswer;
import com.cloud.agent.api.RevertToVMSnapshotCommand;
import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.event.EventTypes;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.snapshot.SnapshotApiService;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.user.AccountService;
import com.cloud.uservm.UserVm;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.VMSnapshotDetailsVO;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy {
private static final Logger s_logger = Logger.getLogger(StorageVMSnapshotStrategy.class);
@Inject
VolumeApiService volumeService;
@Inject
AccountService accountService;
@Inject
VolumeDataFactory volumeDataFactory;
@Inject
SnapshotDao snapshotDao;
@Inject
StorageStrategyFactory storageStrategyFactory;
@Inject
SnapshotDataFactory snapshotDataFactory;
@Inject
PrimaryDataStoreDao storagePool;
@Inject
DataStoreProviderManager dataStoreProviderMgr;
@Inject
SnapshotApiService snapshotApiService;
@Inject
VMSnapshotDetailsDao vmSnapshotDetailsDao;
private static final String STORAGE_SNAPSHOT = "kvmStorageSnapshot";
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
return super.configure(name, params);
}
@Override
public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
Long hostId = vmSnapshotHelper.pickRunningHost(vmSnapshot.getVmId());
UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());
VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
CreateVMSnapshotAnswer answer = null;
FreezeThawVMCommand freezeCommand = null;
FreezeThawVMAnswer freezeAnswer = null;
FreezeThawVMCommand thawCmd = null;
FreezeThawVMAnswer thawAnswer = null;
List<SnapshotInfo> forRollback = new ArrayList<>();
long startFreeze = 0;
try {
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.CreateRequested);
} catch (NoTransitionException e) {
throw new CloudRuntimeException(e.getMessage());
}
boolean result = false;
try {
GuestOSVO guestOS = guestOSDao.findById(userVm.getGuestOSId());
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
long prev_chain_size = 0;
long virtual_size = 0;
VMSnapshotTO current = null;
VMSnapshotVO currentSnapshot = vmSnapshotDao.findCurrentSnapshotByVmId(userVm.getId());
if (currentSnapshot != null) {
current = vmSnapshotHelper.getSnapshotWithParents(currentSnapshot);
}
VMSnapshotOptions options = ((VMSnapshotVO) vmSnapshot).getOptions();
boolean quiescevm = true;
if (options != null) {
quiescevm = options.needQuiesceVM();
}
VMSnapshotTO target = new VMSnapshotTO(vmSnapshot.getId(), vmSnapshot.getName(), vmSnapshot.getType(), null, vmSnapshot.getDescription(), false, current, quiescevm);
if (current == null) {
vmSnapshotVO.setParent(null);
} else {
vmSnapshotVO.setParent(current.getId());
}
CreateVMSnapshotCommand ccmd = new CreateVMSnapshotCommand(userVm.getInstanceName(), userVm.getUuid(), target, volumeTOs, guestOS.getDisplayName());
s_logger.info("Creating VM snapshot for KVM hypervisor without memory");
List<VolumeInfo> vinfos = new ArrayList<>();
for (VolumeObjectTO volumeObjectTO : volumeTOs) {
vinfos.add(volumeDataFactory.getVolume(volumeObjectTO.getId()));
virtual_size += volumeObjectTO.getSize();
VolumeVO volumeVO = volumeDao.findById(volumeObjectTO.getId());
prev_chain_size += volumeVO.getVmSnapshotChainSize() == null ? 0 : volumeVO.getVmSnapshotChainSize();
}
freezeCommand = new FreezeThawVMCommand(userVm.getInstanceName());
freezeCommand.setOption(FreezeThawVMCommand.FREEZE);
freezeAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, freezeCommand);
startFreeze = System.nanoTime();
thawCmd = new FreezeThawVMCommand(userVm.getInstanceName());
thawCmd.setOption(FreezeThawVMCommand.THAW);
if (freezeAnswer != null && freezeAnswer.getResult()) {
s_logger.info("The virtual machine is frozen");
for (VolumeInfo vol : vinfos) {
long startSnapshtot = System.nanoTime();
SnapshotInfo snapInfo = createDiskSnapshot(vmSnapshot, forRollback, vol);
if (snapInfo == null) {
thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
throw new CloudRuntimeException("Could not take snapshot for volume with id=" + vol.getId());
}
s_logger.info(String.format("Snapshot with id=%s, took %s miliseconds", snapInfo.getId(),
TimeUnit.MILLISECONDS.convert(elapsedTime(startSnapshtot), TimeUnit.NANOSECONDS)));
}
answer = new CreateVMSnapshotAnswer(ccmd, true, "");
answer.setVolumeTOs(volumeTOs);
thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
if (thawAnswer != null && thawAnswer.getResult()) {
s_logger.info(String.format(
"Virtual machne is thawed. The freeze of virtual machine took %s miliseconds.",
TimeUnit.MILLISECONDS.convert(elapsedTime(startFreeze), TimeUnit.NANOSECONDS)));
}
} else {
throw new CloudRuntimeException("Could not freeze VM." + freezeAnswer.getDetails());
}
if (answer != null && answer.getResult()) {
processAnswer(vmSnapshotVO, userVm, answer, null);
s_logger.debug("Create vm snapshot " + vmSnapshot.getName() + " succeeded for vm: " + userVm.getInstanceName());
long new_chain_size = 0;
for (VolumeObjectTO volumeTo : answer.getVolumeTOs()) {
publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_CREATE, vmSnapshot, userVm, volumeTo);
new_chain_size += volumeTo.getSize();
}
publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_ON_PRIMARY, vmSnapshot, userVm, new_chain_size - prev_chain_size, virtual_size);
result = true;
return vmSnapshot;
} else {
String errMsg = "Creating VM snapshot: " + vmSnapshot.getName() + " failed";
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
} catch (OperationTimedoutException e) {
s_logger.debug("Creating VM snapshot: " + vmSnapshot.getName() + " failed: " + e.toString());
throw new CloudRuntimeException(
"Creating VM snapshot: " + vmSnapshot.getName() + " failed: " + e.toString());
} catch (AgentUnavailableException e) {
s_logger.debug("Creating VM snapshot: " + vmSnapshot.getName() + " failed", e);
throw new CloudRuntimeException(
"Creating VM snapshot: " + vmSnapshot.getName() + " failed: " + e.toString());
} catch (CloudRuntimeException e) {
throw new CloudRuntimeException(e.getMessage());
} finally {
if (thawAnswer == null && freezeAnswer != null) {
s_logger.info(String.format("Freeze of virtual machine took %s miliseconds.", TimeUnit.MILLISECONDS
.convert(elapsedTime(startFreeze), TimeUnit.NANOSECONDS)));
try {
thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
} catch (AgentUnavailableException | OperationTimedoutException e) {
s_logger.debug("Could not unfreeze the VM due to " + e);
}
}
if (!result) {
for (SnapshotInfo snapshotInfo : forRollback) {
rollbackDiskSnapshot(snapshotInfo);
}
try {
List<VMSnapshotDetailsVO> vmSnapshotDetails = vmSnapshotDetailsDao.listDetails(vmSnapshot.getId());
for (VMSnapshotDetailsVO vmSnapshotDetailsVO : vmSnapshotDetails) {
if (vmSnapshotDetailsVO.getName().equals(STORAGE_SNAPSHOT)) {
vmSnapshotDetailsDao.remove(vmSnapshotDetailsVO.getId());
}
}
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
} catch (NoTransitionException e1) {
s_logger.error("Cannot set vm snapshot state due to: " + e1.getMessage());
}
}
}
}
@Override
public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) {
UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
try {
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.ExpungeRequested);
} catch (NoTransitionException e) {
s_logger.debug("Failed to change vm snapshot state with event ExpungeRequested");
throw new CloudRuntimeException(
"Failed to change vm snapshot state with event ExpungeRequested: " + e.getMessage());
}
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
String vmInstanceName = userVm.getInstanceName();
VMSnapshotTO parent = vmSnapshotHelper.getSnapshotWithParents(vmSnapshotVO).getParent();
VMSnapshotTO vmSnapshotTO = new VMSnapshotTO(vmSnapshot.getId(), vmSnapshot.getName(), vmSnapshot.getType(),
vmSnapshot.getCreated().getTime(), vmSnapshot.getDescription(), vmSnapshot.getCurrent(), parent, true);
GuestOSVO guestOS = guestOSDao.findById(userVm.getGuestOSId());
DeleteVMSnapshotCommand deleteSnapshotCommand = new DeleteVMSnapshotCommand(vmInstanceName, vmSnapshotTO,
volumeTOs, guestOS.getDisplayName());
try {
deleteDiskSnapshot(vmSnapshot);
processAnswer(vmSnapshotVO, userVm, new DeleteVMSnapshotAnswer(deleteSnapshotCommand, volumeTOs), null);
long full_chain_size = 0;
for (VolumeObjectTO volumeTo : volumeTOs) {
publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo);
full_chain_size += volumeTo.getSize();
}
publishUsageEvent(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L);
return true;
} catch (CloudRuntimeException err) {
//In case of failure all volume's snapshots will be visible for delete/revert separately, because they won't be consistent
List<VMSnapshotDetailsVO> listSnapshots = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), STORAGE_SNAPSHOT);
for (VMSnapshotDetailsVO vmSnapshotDetailsVO : listSnapshots) {
SnapshotVO snapshot = snapshotDao.findById(Long.parseLong(vmSnapshotDetailsVO.getValue()));
if (snapshot != null) {
snapshot.setSnapshotType((short) Snapshot.Type.MANUAL.ordinal());
snapshot.setTypeDescription("MANUAL");
snapshotDao.update(snapshot.getId(), snapshot);
vmSnapshotDetailsDao.remove(vmSnapshotDetailsVO.getId());
}
}
String errMsg = String.format("Delete of VM snapshot [%s] of VM [%s] failed due to [%s]", vmSnapshot.getName(), userVm.getUserId(), err);
s_logger.error(errMsg, err);
throw new CloudRuntimeException(errMsg, err);
}
}
@Override
public boolean revertVMSnapshot(VMSnapshot vmSnapshot) {
VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
try {
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.RevertRequested);
} catch (NoTransitionException e) {
throw new CloudRuntimeException(e.getMessage());
}
boolean result = false;
try {
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
String vmInstanceName = userVm.getInstanceName();
VMSnapshotTO parent = vmSnapshotHelper.getSnapshotWithParents(vmSnapshotVO).getParent();
VMSnapshotTO vmSnapshotTO = new VMSnapshotTO(vmSnapshotVO.getId(), vmSnapshotVO.getName(), vmSnapshotVO.getType(),
vmSnapshotVO.getCreated().getTime(), vmSnapshotVO.getDescription(), vmSnapshotVO.getCurrent(), parent, true);
GuestOSVO guestOS = guestOSDao.findById(userVm.getGuestOSId());
RevertToVMSnapshotCommand revertToSnapshotCommand = new RevertToVMSnapshotCommand(vmInstanceName,
userVm.getUuid(), vmSnapshotTO, volumeTOs, guestOS.getDisplayName());
List<VolumeInfo> volumeInfos = new ArrayList<>();
for (VolumeObjectTO volumeObjectTO : volumeTOs) {
volumeInfos.add(volumeDataFactory.getVolume(volumeObjectTO.getId()));
}
revertDiskSnapshot(vmSnapshot);
RevertToVMSnapshotAnswer answer = new RevertToVMSnapshotAnswer(revertToSnapshotCommand, true, "");
answer.setVolumeTOs(volumeTOs);
processAnswer(vmSnapshotVO, userVm, answer, null);
result = true;
} catch (CloudRuntimeException e) {
s_logger.error(e);
throw new CloudRuntimeException(e);
} finally {
if (!result) {
try {
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
} catch (NoTransitionException e1) {
s_logger.error("Cannot set vm snapshot state due to: " + e1.getMessage());
}
}
}
return result;
}
@Override
public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
if (!VMSnapshot.State.Allocated.equals(vmSnapshot.getState())) {
List<VMSnapshotDetailsVO> vmSnapshotDetails = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), STORAGE_SNAPSHOT);
if (CollectionUtils.isEmpty(vmSnapshotDetails)) {
return StrategyPriority.CANT_HANDLE;
}
}
if ( SnapshotManager.VmStorageSnapshotKvm.value() && userVm.getHypervisorType() == Hypervisor.HypervisorType.KVM
&& vmSnapshot.getType() == VMSnapshot.Type.Disk) {
return StrategyPriority.HYPERVISOR;
}
return StrategyPriority.CANT_HANDLE;
}
@Override
public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
if (SnapshotManager.VmStorageSnapshotKvm.value() && !snapshotMemory) {
UserVmVO vm = userVmDao.findById(vmId);
if (vm.getState() == VirtualMachine.State.Running) {
return StrategyPriority.HYPERVISOR;
}
}
return StrategyPriority.CANT_HANDLE;
}
@Override
public boolean deleteVMSnapshotFromDB(VMSnapshot vmSnapshot, boolean unmanage) {
return super.deleteVMSnapshotFromDB(vmSnapshot, unmanage);
}
private long elapsedTime(long startTime) {
long endTime = System.nanoTime();
return endTime - startTime;
}
//Rollback if one of disks snapshot fails
protected void rollbackDiskSnapshot(SnapshotInfo snapshotInfo) {
Long snapshotID = snapshotInfo.getId();
SnapshotVO snapshot = snapshotDao.findById(snapshotID);
deleteSnapshotByStrategy(snapshot);
s_logger.debug("Rollback is executed: deleting snapshot with id:" + snapshotID);
}
protected void deleteSnapshotByStrategy(SnapshotVO snapshot) {
//The snapshot could not be deleted separately, that's why we set snapshot state to BackedUp for operation delete VM snapshots and rollback
SnapshotStrategy strategy = storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.DELETE);
if (strategy != null) {
boolean snapshotForDelete = strategy.deleteSnapshot(snapshot.getId());
if (!snapshotForDelete) {
throw new CloudRuntimeException("Failed to delete snapshot");
}
} else {
throw new CloudRuntimeException("Could not find the primary storage of the snapshot");
}
}
protected void deleteDiskSnapshot(VMSnapshot vmSnapshot) {
//we can find disks snapshots related to vmSnapshot in vm_snapshot_details table
List<VMSnapshotDetailsVO> listSnapshots = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), STORAGE_SNAPSHOT);
for (VMSnapshotDetailsVO vmSnapshotDetailsVO : listSnapshots) {
SnapshotVO snapshot = snapshotDao.findById(Long.parseLong(vmSnapshotDetailsVO.getValue()));
if (snapshot == null) {
throw new CloudRuntimeException("Could not find snapshot for VM snapshot");
}
deleteSnapshotByStrategy(snapshot);
vmSnapshotDetailsDao.remove(vmSnapshotDetailsVO.getId());
}
}
protected void revertDiskSnapshot(VMSnapshot vmSnapshot) {
List<VMSnapshotDetailsVO> listSnapshots = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), STORAGE_SNAPSHOT);
for (VMSnapshotDetailsVO vmSnapshotDetailsVO : listSnapshots) {
SnapshotInfo sInfo = snapshotDataFactory.getSnapshot(Long.parseLong(vmSnapshotDetailsVO.getValue()), DataStoreRole.Primary);
SnapshotStrategy snapshotStrategy = storageStrategyFactory.getSnapshotStrategy(sInfo, SnapshotOperation.REVERT);
if (snapshotStrategy == null) {
throw new CloudRuntimeException(String.format("Could not find strategy for snapshot uuid [%s]", sInfo.getId()));
}
if (!snapshotStrategy.revertSnapshot(sInfo)) {
throw new CloudRuntimeException("Failed to revert snapshot");
}
}
}
protected SnapshotInfo createDiskSnapshot(VMSnapshot vmSnapshot, List<SnapshotInfo> forRollback, VolumeInfo vol) {
String snapshotName = vmSnapshot.getId() + "_" + vol.getUuid();
SnapshotVO snapshot = new SnapshotVO(vol.getDataCenterId(), vol.getAccountId(), vol.getDomainId(), vol.getId(), vol.getDiskOfferingId(),
snapshotName, (short) Snapshot.Type.GROUP.ordinal(), Snapshot.Type.GROUP.name(), vol.getSize(), vol.getMinIops(), vol.getMaxIops(), Hypervisor.HypervisorType.KVM, null);
snapshot = snapshotDao.persist(snapshot);
vol.addPayload(setPayload(vol, snapshot));
SnapshotInfo snapshotInfo = snapshotDataFactory.getSnapshot(snapshot.getId(), vol.getDataStore());
snapshotInfo.addPayload(vol.getpayload());
SnapshotStrategy snapshotStrategy = storageStrategyFactory.getSnapshotStrategy(snapshotInfo, SnapshotOperation.TAKE);
if (snapshotStrategy == null) {
throw new CloudRuntimeException("Could not find strategy for snapshot uuid:" + snapshotInfo.getUuid());
}
snapshotInfo = snapshotStrategy.takeSnapshot(snapshotInfo);
if (snapshotInfo == null) {
throw new CloudRuntimeException("Failed to create snapshot");
} else {
forRollback.add(snapshotInfo);
}
vmSnapshotDetailsDao.persist(new VMSnapshotDetailsVO(vmSnapshot.getId(), STORAGE_SNAPSHOT, String.valueOf(snapshot.getId()), true));
snapshotInfo.markBackedUp();
return snapshotInfo;
}
protected CreateSnapshotPayload setPayload(VolumeInfo vol, SnapshotVO snapshotCreate) {
CreateSnapshotPayload payload = new CreateSnapshotPayload();
payload.setSnapshotId(snapshotCreate.getId());
payload.setSnapshotPolicyId(SnapshotVO.MANUAL_POLICY_ID);
payload.setLocationType(snapshotCreate.getLocationType());
payload.setAccount(accountService.getAccount(vol.getAccountId()));
payload.setAsyncBackup(false);
payload.setQuiescevm(false);
return payload;
}
}