package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.List;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.quota.QuotaClusterConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaVdsDependent;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.action.HotSetNumberOfCpusParameters;
import org.ovirt.engine.core.common.action.PlugAction;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.EngineFault;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.vdsbroker.SetNumberOfCpusVDSCommand;
/**
* Set the number of CPU of a running VM also called hot plug or hot unplug, hot add, hot remove.
* This command behaviour varies between OS implementation. To that matter, the parameter of the desired
* number of CPUs will manifest as a hot plug or unplug, depending on the current cpu count at the Guest level.
*
* The execute will never throw an exception. it will rather wrap a return value in case of failure.
*/
@NonTransactiveCommandAttribute
public class HotSetNumberOfCpusCommand<T extends HotSetNumberOfCpusParameters> extends VmManagementCommandBase<T> implements QuotaVdsDependent {
public static final String LOGABLE_FIELD_NUMBER_OF_CPUS = "numberOfCpus";
public static final String LOGABLE_FIELD_PREVIOUS_NUMBER_OF_CPUS = "previousNumberOfCpus";
public static final String LOGABLE_FIELD_ERROR_MESSAGE = "ErrorMessage";
public HotSetNumberOfCpusCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
@Override
protected boolean validate() {
boolean valid = true;
if (getVm() == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND);
}
if (getVm().getStatus() != VMStatus.Up) {
valid = failVmStatusIllegal();
}
if (getParameters().getVm().getCpuPerSocket() >
Config.<Integer>getValue(
ConfigValues.MaxNumOfCpuPerSocket,
getVm().getCompatibilityVersion().getValue())) {
valid = failValidation(EngineMessage.ACTION_TYPE_FAILED_MAX_CPU_PER_SOCKET);
}
if (getParameters().getVm().getThreadsPerCpu() >
Config.<Integer>getValue(
ConfigValues.MaxNumOfThreadsPerCpu,
getVm().getClusterCompatibilityVersion().getValue())) {
valid = failValidation(EngineMessage.ACTION_TYPE_FAILED_MAX_THREADS_PER_CPU);
}
if (getParameters().getVm().getNumOfSockets() >
Config.<Integer>getValue(
ConfigValues.MaxNumOfVmSockets,
getVm().getCompatibilityVersion().getValue())) {
valid = failValidation(EngineMessage.ACTION_TYPE_FAILED_MAX_NUM_SOCKETS);
}
if (getParameters().getPlugAction() == PlugAction.PLUG) {
if (!FeatureSupported.hotPlugCpu(getVm().getCompatibilityVersion(), getVm().getClusterArch())) {
valid = failValidation(EngineMessage.HOT_PLUG_CPU_IS_NOT_SUPPORTED);
} else if (!osRepository.isCpuHotplugSupported(getVm().getVmOsId())) {
valid = failValidation(
EngineMessage.HOT_PLUG_CPU_IS_NOT_SUPPORTED_FOR_GUEST_OS,
String.format("$guestOS %1$s", osRepository.getOsName(getVm().getVmOsId())),
String.format("$architecture %1$s", getVm().getClusterArch()));
}
} else if (!FeatureSupported.hotUnplugCpu(getVm().getCompatibilityVersion(), getVm().getClusterArch())) {
valid = failValidation(EngineMessage.HOT_UNPLUG_CPU_IS_NOT_SUPPORTED);
} else if (!osRepository.isCpuHotunplugSupported(getVm().getVmOsId())) {
valid = failValidation(
EngineMessage.HOT_UNPLUG_CPU_IS_NOT_SUPPORTED_FOR_GUEST_OS,
String.format("$guestOS %1$s", osRepository.getOsName(getVm().getVmOsId())),
String.format("$architecture %1$s", getVm().getClusterArch()));
}
return valid;
}
/**
* Execution shall perform a call to VDSM to set the number of CPUs.
* The guest OS will plug/unplug CPUs if the current guest configuration is lower/higher than
* the requested number respectively.
*/
@Override
protected void executeCommand() {
VDSReturnValue vdsReturnValue = runVdsCommand(VDSCommandType.SetNumberOfCpus,
new SetNumberOfCpusVDSCommand.Params(
getVm().getRunOnVds(),
getVm().getId(),
getParameters().getVm().getNumOfCpus()));
if (vdsReturnValue.getSucceeded()) {
setSucceeded(true);
} else {
EngineFault fault = new EngineFault();
fault.setError(vdsReturnValue.getVdsError().getCode());
fault.setMessage(vdsReturnValue.getVdsError().getMessage());
getReturnValue().setFault(fault);
}
}
@Override
public AuditLogType getAuditLogTypeValue() {
addCustomValue(LOGABLE_FIELD_NUMBER_OF_CPUS, String.valueOf(getParameters().getVm().getNumOfCpus()));
addCustomValue(LOGABLE_FIELD_PREVIOUS_NUMBER_OF_CPUS, String.valueOf(getVm().getNumOfCpus()));
if (getSucceeded()) {
return AuditLogType.HOT_SET_NUMBER_OF_CPUS;
} else {
addCustomValue(LOGABLE_FIELD_ERROR_MESSAGE, getReturnValue().getFault().getMessage());
return AuditLogType.FAILED_HOT_SET_NUMBER_OF_CPUS;
}
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__HOT_SET_CPUS);
addValidationMessage(EngineMessage.VAR__TYPE__VM);
addValidationMessageVariable("clusterVersion", getVm().getCompatibilityVersion());
addValidationMessageVariable("architecture", getVm().getClusterArch());
}
@Override
public List<QuotaConsumptionParameter> getQuotaVdsConsumptionParameters() {
List<QuotaConsumptionParameter> list = new ArrayList<>();
// Calculate the change in CPU consumption, result above Zero means we add CPUs to
// the VM
// result bellow Zero means we subtracted CPUs from the VM
int cpuToConsume =
getParameters().getVm().getNumOfCpus() - getVm().getNumOfCpus();
if (cpuToConsume > 0) {
// Consume CPU quota
list.add(new QuotaClusterConsumptionParameter(getVm().getQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.CONSUME,
getVm().getClusterId(),
getVm().getCpuPerSocket() * getVm().getThreadsPerCpu() * cpuToConsume,
0));
} else if (cpuToConsume < 0) {
// Release CPU quota
list.add(new QuotaClusterConsumptionParameter(getVm().getQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.RELEASE,
getVm().getClusterId(),
getVm().getCpuPerSocket() * getVm().getThreadsPerCpu() * Math.abs(cpuToConsume),
0));
}
return list;
}
}