package org.ovirt.engine.core.bll;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.memory.MemoryStorageHandler;
import org.ovirt.engine.core.bll.memory.MemoryUtils;
import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter;
import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil;
import org.ovirt.engine.core.bll.utils.VmOverheadCalculator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.AddDiskParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.action.VmOperationParameterBase;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
import org.ovirt.engine.core.common.asynctasks.EntityInfo;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.HibernateVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.DiskDao;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@DisableInPrepareMode
@NonTransactiveCommandAttribute
public class HibernateVmCommand<T extends VmOperationParameterBase> extends VmOperationCommandBase<T> {
private static final Logger log = LoggerFactory.getLogger(HibernateVmCommand.class);
private boolean hibernateVdsProblematic;
private Guid cachedStorageDomainId;
@Inject
private VmOverheadCalculator vmOverheadCalculator;
@Inject
private MemoryStorageHandler memoryStorageHandler;
@Inject
private DiskDao diskDao;
@Inject
private SnapshotDao snapshotDao;
/**
* Constructor for command creation when compensation is applied on startup
*/
protected HibernateVmCommand(Guid commandId) {
super(commandId);
}
public HibernateVmCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
@Override
protected void init() {
super.init();
if (getVm() != null) {
setStoragePoolId(getVm().getStoragePoolId());
getParameters().setEntityInfo(new EntityInfo(VdcObjectType.VM, getVm().getId()));
}
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
/**
* Finds an active data/master storage domain which has enough space to store the hibernation volumes
*
* @return storage domain id or null if no suitable storage domain exists
*/
@Override
public Guid getStorageDomainId() {
if (cachedStorageDomainId == null) {
List<DiskImage> diskDummiesForMemSize = MemoryUtils.createDiskDummies(
vmOverheadCalculator.getSnapshotMemorySizeInBytes(getVm()),
MemoryUtils.METADATA_SIZE_IN_BYTES);
StorageDomain storageDomain = memoryStorageHandler.findStorageDomainForMemory(
getStoragePoolId(), diskDummiesForMemSize,
DisksFilter.filterImageDisks(diskDao.getAllForVm(getVmId())), getVm());
if (storageDomain != null) {
cachedStorageDomainId = storageDomain.getId();
}
}
return cachedStorageDomainId;
}
@Override
protected void perform() {
addMemoryDisk();
addMetadataDisk();
setSucceeded(true);
}
private void addMetadataDisk() {
DiskImage metaDataDisk = MemoryUtils.createHibernationMetadataDisk(getVm());
addDisk(metaDataDisk);
}
private void addMemoryDisk() {
DiskImage memoryDisk = MemoryUtils.createHibernationMemoryDisk(getVm(),
getStorageDomain().getStorageType(), vmOverheadCalculator);
addDisk(memoryDisk);
}
private void addDisk(DiskImage disk) {
VdcReturnValueBase returnValue = runInternalActionWithTasksContext(
VdcActionType.AddDisk,
buildAddDiskParameters(disk));
if (!returnValue.getSucceeded()) {
throw new EngineException(returnValue.getFault().getError(),
String.format("Failed to create disk! %s", disk.getDiskAlias()));
}
getTaskIdList().addAll(returnValue.getInternalVdsmTaskIdList());
}
private AddDiskParameters buildAddDiskParameters(DiskImage disk) {
AddDiskParameters parameters = new AddDiskParameters(disk);
parameters.setStorageDomainId(getStorageDomainId());
parameters.setParentCommand(getActionType());
parameters.setParentParameters(getParameters());
parameters.setShouldBeLogged(false);
return parameters;
}
@Override
protected AsyncTaskType getTaskType() {
return AsyncTaskType.createVolume;
}
@Override
public AuditLogType getAuditLogTypeValue() {
switch (getActionState()) {
case EXECUTE:
return getSucceeded() ? AuditLogType.USER_SUSPEND_VM : AuditLogType.USER_FAILED_SUSPEND_VM;
case END_SUCCESS:
if (getSucceeded()) {
// no event should be displayed if the command ended successfully, the monitoring will log it
return AuditLogType.UNASSIGNED;
}
case END_FAILURE:
default:
return hibernateVdsProblematic ? AuditLogType.USER_SUSPEND_VM_FINISH_FAILURE_WILL_TRY_AGAIN
: AuditLogType.USER_SUSPEND_VM_FINISH_FAILURE;
}
}
@Override
protected boolean validate() {
if (getVm() == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND);
}
if (!FeatureSupported.isSuspendSupportedByArchitecture(
getVm().getClusterArch(),
getVm().getCompatibilityVersion())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_SUSPEND_NOT_SUPPORTED);
}
if (!canRunActionOnNonManagedVm()) {
return false;
}
VMStatus vmStatus = getVm().getStatus();
if (vmStatus == VMStatus.WaitForLaunch || vmStatus == VMStatus.NotResponding) {
return failVmStatusIllegal();
}
if (vmStatus != VMStatus.Up) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_UP);
}
if (getCluster().isInUpgradeMode()) {
return failValidation(EngineMessage.VM_CANNOT_SUSPEND_CLUSTER_UPGRADING);
}
if (CommandCoordinatorUtil.entityHasTasks(getVmId())) {
return failValidation(EngineMessage.VM_CANNOT_SUSPENDE_HAS_RUNNING_TASKS);
}
if (getVm().getVmPoolId() != null) {
return failValidation(EngineMessage.VM_CANNOT_SUSPEND_VM_FROM_POOL);
}
// check if vm has stateless images in db in case vm was run once as stateless
// (then isStateless is false)
if (getVm().isStateless() || snapshotDao.exists(getVmId(), SnapshotType.STATELESS)) {
return failValidation(EngineMessage.VM_CANNOT_SUSPEND_STATELESS_VM);
}
if (getStorageDomainId() == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_NO_SUITABLE_DOMAIN_FOUND);
}
return true;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__TYPE__VM);
addValidationMessage(EngineMessage.VAR__ACTION__HIBERNATE);
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
return Collections.singletonMap(getVmId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM,
new LockMessage(EngineMessage.ACTION_TYPE_FAILED_VM_IS_HIBERNATING)
.withOptional("VmName", getVmName())));
}
@Override
protected void endSuccessfully() {
if (getVm().getStatus() != VMStatus.Up) {
log.warn("VM '{}' is not up, cannot Hibernate.", getVm().getName());
endWithFailure();
return;
}
List<VdcReturnValueBase> addDiskReturnValues = endActionOnDisks();
DiskImage dumpDisk = getMemoryDumpDisk(addDiskReturnValues);
DiskImage metadataDisk = getMemoryMetadataDisk(addDiskReturnValues);
String hiberVol = MemoryUtils.createMemoryStateString(
getStorageDomainId(), getStoragePoolId(),
dumpDisk.getId(), dumpDisk.getImageId(), metadataDisk.getId(), metadataDisk.getImageId());
try {
runVdsCommand(VDSCommandType.Hibernate,
new HibernateVDSCommandParameters(getVm().getRunOnVds(), getVmId(), hiberVol));
snapshotDao.updateHibernationMemory(getVmId(), dumpDisk.getId(), metadataDisk.getId(), hiberVol);
} catch (EngineException e) {
hibernateVdsProblematic = true;
endWithFailure();
return;
}
setSucceeded(true);
}
private DiskImage getMemoryDumpDisk(List<VdcReturnValueBase> returnValues) {
for (VdcReturnValueBase returnValue : returnValues) {
DiskImage disk = returnValue.getActionReturnValue();
if (disk.getSize() != MemoryUtils.METADATA_SIZE_IN_BYTES) {
return disk;
}
}
return null;
}
private DiskImage getMemoryMetadataDisk(List<VdcReturnValueBase> returnValues) {
for (VdcReturnValueBase returnValue : returnValues) {
DiskImage disk = returnValue.getActionReturnValue();
if (disk.getSize() == MemoryUtils.METADATA_SIZE_IN_BYTES) {
return disk;
}
}
return null;
}
@Override
protected void endWithFailure() {
endActionOnDisks();
snapshotDao.removeMemoryFromActiveSnapshot(getVmId());
revertTasks();
setSucceeded(true);
}
}