package org.ovirt.engine.core.bll;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.snapshots.SnapshotVmConfigurationHelper;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.action.HotUnplugMemoryParameters;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.errors.EngineFault;
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.utils.VmDeviceCommonUtils;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.VmDeviceDao;
import org.ovirt.engine.core.utils.ReplacementUtils;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.ovirt.engine.core.vdsbroker.HotUnplugMemoryVDSCommand;
/**
* It tries to hot unplug memory device of a VM.
*/
@NonTransactiveCommandAttribute
public class HotUnplugMemoryCommand<P extends HotUnplugMemoryParameters> extends VmCommand<P> {
private static final String AUDIT_LOG_VAR_VM_NAME = "vmName";
private static final String AUDIT_LOG_VAR_MEMORY_SIZE_MB = "memorySizeMb";
private static final String AUDIT_LOG_VAR_ERROR_MESSAGE = "errorMessage";
private static final String AUDIT_LOG_VAR_DEVICE_ID = "deviceId";
protected VmDevice deviceToHotUnplug;
@Inject
private VmDeviceDao vmDeviceDao;
@Inject
private VmHandler vmHandler;
@Inject
private SnapshotVmConfigurationHelper snapshotVmConfigurationHelper;
@Inject
private SnapshotDao snapshotDao;
public HotUnplugMemoryCommand(P parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
protected VmDevice getDeviceToHotUnplug() {
if (deviceToHotUnplug == null) {
deviceToHotUnplug = vmDeviceDao.get(new VmDeviceId(getParameters().getDeviceId(), getVmId()));
}
return deviceToHotUnplug;
}
protected int getUnpluggedDeviceSize() {
return VmDeviceCommonUtils.getSizeOfMemoryDeviceMb(getDeviceToHotUnplug());
}
@Override
protected boolean validate() {
if (!super.validate()) {
return false;
}
if (!FeatureSupported.hotUnplugMemory(getVm().getCompatibilityVersion(), getVm().getClusterArch())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_MEMORY_HOT_UNPLUG_NOT_SUPPORTED_FOR_COMPAT_VERSION_AND_ARCH,
ReplacementUtils.createSetVariableString(
"compatibilityVersion", getVm().getCompatibilityVersion()),
ReplacementUtils.createSetVariableString(
"architecture", getVm().getClusterArch()));
}
if (getVm().getStatus() != VMStatus.Up) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_UP);
}
if (getDeviceToHotUnplug() == null) {
return failValidation(
EngineMessage.ACTION_TYPE_FAILED_VM_MEMORY_DEVICE_DOESNT_EXIST,
ReplacementUtils.createSetVariableString(
"deviceId", getParameters().getDeviceId()));
}
return true;
}
@Override
protected void executeCommand() {
addCustomValue(AUDIT_LOG_VAR_VM_NAME, getVmName());
addCustomValue(AUDIT_LOG_VAR_MEMORY_SIZE_MB, String.valueOf(getUnpluggedDeviceSize()));
addCustomValue(AUDIT_LOG_VAR_DEVICE_ID, getParameters().getDeviceId().toString());
final VDSReturnValue vdsReturnValue = runVdsCommand(
VDSCommandType.HotUnplugMemory,
new HotUnplugMemoryVDSCommand.Params(getVm().getRunOnVds(), getDeviceToHotUnplug()));
if (!vdsReturnValue.getSucceeded()) {
addCustomValue(AUDIT_LOG_VAR_ERROR_MESSAGE, vdsReturnValue.getVdsError().getMessage());
setReturnValueFailure(vdsReturnValue);
return;
}
if (getVm().getMemSizeMb() - getUnpluggedDeviceSize() >= getVm().getMinAllocatedMem()) {
/*
* If memory hot unplug fails on guest with a delay (not in synchronous way when calling
* HotUnplugMemoryVDSCommand), we can get out of sync between what vm devices are reported and
* value of getVm().getMemSizeMb(). This `if` branch tries to mitigate such situations. It uses
* the fact that getVm().getMinAllocatedMem() can't be changed when VM is running.
* Example:
* | getVm().getMemorySizeMb() | getVm().getMinAllocatedMem() | real VM memory |
* +---------------------------+------------------------------+----------------+
* | 8 GB | 8 GB | 8 BG |
* successful hot plug of 2GB
* | 10 GB | 8 GB | 10 BG |
* asynchronously unsuccessful hot unplug of 2GB
* | 8 GB | 8 GB | 10 BG |
* second try to hot unplug 2GB, successful, getVm().getMemorySizeMb() not decrease because
* of this `if`
* | 8 GB | 8 GB | 8 BG |
*/
updateVm();
}
setSucceeded(true);
}
private void updateVm() {
updateNextRunConfiguration();
updateCurrentConfiguration();
}
private void updateCurrentConfiguration() {
final VmStatic updatedVmStatic = new VmStatic(getVm().getStaticData());
updatedVmStatic.setMemSizeMb(updatedVmStatic.getMemSizeMb() - getUnpluggedDeviceSize());
getVmManager().update(updatedVmStatic);
}
private void updateNextRunConfiguration() {
final VmStatic nextRunConfigurationStatic = getNextRunConfiguration();
if (nextRunConfigurationStatic == null) {
return;
}
final int newMemorySize = nextRunConfigurationStatic.getMemSizeMb() - getUnpluggedDeviceSize();
if (newMemorySize <= 0) {
return;
}
// Update next run snapshot only if old memory state matches old next run snapshot state
if (getVm().getMemSizeMb() != nextRunConfigurationStatic.getMemSizeMb()) {
return;
}
nextRunConfigurationStatic.setMemSizeMb(newMemorySize);
if (newMemorySize < nextRunConfigurationStatic.getMinAllocatedMem()) {
nextRunConfigurationStatic.setMinAllocatedMem(newMemorySize);
}
TransactionSupport.executeInNewTransaction(() -> {
vmHandler.createNextRunSnapshot(
getVm(), nextRunConfigurationStatic, null, getCompensationContext());
return null;
});
}
private VmStatic getNextRunConfiguration() {
final Snapshot snapshot = snapshotDao.get(getVmId(), Snapshot.SnapshotType.NEXT_RUN);
if (snapshot == null) {
return null;
}
final VM vm = snapshotVmConfigurationHelper.getVmFromConfiguration(
snapshot.getVmConfiguration(), snapshot.getVmId(), snapshot.getId());
return vm.getStaticData();
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
final Map<String, Pair<String, String>> result = new HashMap<>(super.getExclusiveLocks());
result.put(getParameters().getDeviceId().toString(),
LockMessagesMatchUtil.makeLockingPair(
LockingGroup.VM_DEVICE,
new LockMessage(EngineMessage.ACTION_TYPE_FAILED_MEMORY_DEVICE_IS_BEING_HOT_UNPLUGGED)
.with("deviceId", getParameters().getDeviceId().toString())
.with("vmId", getParameters().getVmId().toString())));
return result;
}
@Override
public AuditLogType getAuditLogTypeValue() {
return getReturnValue().getSucceeded()
? AuditLogType.MEMORY_HOT_UNPLUG_SUCCESSFULLY_REQUESTED
: AuditLogType.MEMORY_HOT_UNPLUG_FAILED;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__HOT_UNPLUG);
addValidationMessage(EngineMessage.VAR__TYPE__MEMORY_DEVICE);
}
public void setReturnValueFailure(VDSReturnValue returnValueFailure) {
final EngineFault engineFault = new EngineFault();
engineFault.setError(returnValueFailure.getVdsError().getCode());
engineFault.setMessage(returnValueFailure.getVdsError().getMessage());
getReturnValue().setFault(engineFault);
}
}