package org.ovirt.engine.core.bll;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.architecture.HasMaximumNumberOfDisks;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.hostdev.HostDeviceManager;
import org.ovirt.engine.core.bll.job.ExecutionContext;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.bll.network.cluster.NetworkHelper;
import org.ovirt.engine.core.bll.provider.ProviderProxyFactory;
import org.ovirt.engine.core.bll.provider.network.NetworkProviderProxy;
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.bll.scheduling.VdsFreeMemoryChecker;
import org.ovirt.engine.core.bll.storage.domain.IsoDomainListSynchronizer;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.RunVmValidator;
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.CreateAllSnapshotsFromVmParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.ProcessDownVmParameters;
import org.ovirt.engine.core.common.action.RunVmParams;
import org.ovirt.engine.core.common.action.RunVmParams.RunVmFlow;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.asynctasks.EntityInfo;
import org.ovirt.engine.core.common.businessentities.BootSequence;
import org.ovirt.engine.core.common.businessentities.Entities;
import org.ovirt.engine.core.common.businessentities.GraphicsInfo;
import org.ovirt.engine.core.common.businessentities.GraphicsType;
import org.ovirt.engine.core.common.businessentities.InitializationType;
import org.ovirt.engine.core.common.businessentities.Provider;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
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.VmDeviceGeneralType;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.VmPayload;
import org.ovirt.engine.core.common.businessentities.VmPool;
import org.ovirt.engine.core.common.businessentities.VmPoolType;
import org.ovirt.engine.core.common.businessentities.VmRngDevice;
import org.ovirt.engine.core.common.businessentities.network.Network;
import org.ovirt.engine.core.common.businessentities.network.VmNic;
import org.ovirt.engine.core.common.businessentities.network.VnicProfile;
import org.ovirt.engine.core.common.businessentities.storage.Disk;
import org.ovirt.engine.core.common.businessentities.storage.ImageFileType;
import org.ovirt.engine.core.common.businessentities.storage.RepoImage;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.job.Job;
import org.ovirt.engine.core.common.job.Step;
import org.ovirt.engine.core.common.job.StepEnum;
import org.ovirt.engine.core.common.utils.VmDeviceType;
import org.ovirt.engine.core.common.validation.group.StartEntity;
import org.ovirt.engine.core.common.vdscommands.CreateVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.IrsBaseVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.ResumeVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.dal.job.ExecutionMessageDirector;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.VmDeviceDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
import org.ovirt.engine.core.dao.VmPoolDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.dao.network.VnicProfileDao;
import org.ovirt.engine.core.dao.provider.ProviderDao;
import org.ovirt.engine.core.di.Injector;
import org.ovirt.engine.core.utils.RngUtils;
import org.ovirt.engine.core.utils.archstrategy.ArchStrategyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@NonTransactiveCommandAttribute
public class RunVmCommand<T extends RunVmParams> extends RunVmCommandBase<T>
implements QuotaVdsDependent {
/** Note: this field should not be used directly, use {@link #isStatelessSnapshotExistsForVm()} instead */
private Boolean cachedStatelessSnapshotExistsForVm;
/** Indicates whether there is a possibility that the active snapshot's memory was already restored */
private boolean memoryFromSnapshotUsed;
private Guid cachedActiveIsoDomainId;
private boolean needsHostDevices = false;
private InitializationType initializationType;
protected VmPayload vmPayload;
public static final String ISO_PREFIX = "iso://";
public static final String STATELESS_SNAPSHOT_DESCRIPTION = "stateless snapshot";
private static final Logger log = LoggerFactory.getLogger(RunVmCommand.class);
@Inject
private HostDeviceManager hostDeviceManager;
@Inject
private IsoDomainListSynchronizer isoDomainListSynchronizer;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private VmDeviceDao vmDeviceDao;
@Inject
private VnicProfileDao vnicProfileDao;
@Inject
private ProviderDao providerDao;
@Inject
private VmPoolDao vmPoolDao;
@Inject
private VmDynamicDao vmDynamicDao;
protected RunVmCommand(Guid commandId) {
super(commandId);
}
public RunVmCommand(T runVmParams, CommandContext commandContext) {
super(runVmParams, commandContext);
getParameters().setEntityInfo(new EntityInfo(VdcObjectType.VM, runVmParams.getVmId()));
}
@Override
protected void init() {
if (getVm() == null) {
return;
}
super.init();
setStoragePoolId(getVm().getStoragePoolId());
loadPayload();
needsHostDevices = hostDeviceManager.checkVmNeedsDirectPassthrough(getVm());
loadVmInit();
fetchVmDisksFromDb();
getVm().setBootSequence(getVm().getDefaultBootSequence());
getVm().setRunOnce(false);
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Execution);
}
protected List<Guid> getPredefinedVdsIdListToRunOn() {
// This API needs to be preserved to allow RunOnce overrides,
// but the Preferred host filtering needs to be done in a Policy Unit
// only to allow the user to disable it.
return Collections.emptyList();
}
protected String getMemoryFromActiveSnapshot() {
// If the memory from the snapshot could have been restored already, the disks might be
// non coherent with the memory, thus we don't want to try to restore the memory again
if (memoryFromSnapshotUsed) {
return StringUtils.EMPTY;
}
if (getFlow() == RunVmFlow.RESUME_HIBERNATE) {
return getActiveSnapshot().getMemoryVolume();
}
if (!FeatureSupported.isMemorySnapshotSupportedByArchitecture(
getVm().getClusterArch(),
getVm().getCompatibilityVersion())) {
return StringUtils.EMPTY;
}
return getActiveSnapshot().getMemoryVolume();
}
/**
* Returns the full path file name of Iso file. If the Iso file has prefix of Iso://, then we set the prefix to the
* path of the domain on the Iso Domain server<BR/>
* otherwise, returns the original name.<BR/>
* Note: The prefix is not case sensitive.
*
* @param url
* - String of the file url. ("iso://initrd.ini" or "/init/initrd.ini".
* @return String of the full file path.
*/
protected String getIsoPrefixFilePath(String url) {
// The initial Url.
String fullPathFileName = url;
// If file name got prefix of iso:// then set the path to the Iso domain.
int prefixLength = ISO_PREFIX.length();
if (url.length() >= prefixLength && url.substring(0, prefixLength).equalsIgnoreCase(ISO_PREFIX)) {
fullPathFileName = cdPathWindowsToLinux(url.substring(prefixLength));
}
return fullPathFileName;
}
protected String cdPathWindowsToLinux(String url) {
return cdPathWindowsToLinux(url, getVm().getStoragePoolId(), getVdsId());
}
private void resumeVm() {
setVdsId(getVm().getRunOnVds());
if (getVds() != null) {
try {
VDSReturnValue result = getVdsBroker()
.runAsyncVdsCommand(
VDSCommandType.Resume,
new ResumeVDSCommandParameters(getVdsId(), getVm().getId()),
this);
setActionReturnValue(result.getReturnValue());
setSucceeded(result.getSucceeded());
ExecutionHandler.setAsyncJob(getExecutionContext(), true);
} finally {
freeLock();
}
} else {
setActionReturnValue(getVm().getStatus());
}
}
protected void runVm() {
setActionReturnValue(VMStatus.Down);
if (getVdsToRunOn()) {
VMStatus status = null;
try {
acquireHostDevicesLock();
if (connectLunDisks(getVdsId()) && updateCinderDisksConnections()) {
if (!checkRequiredHostDevicesAvailability()) {
// if between validate and execute the host-devices were stolen by another VM
// (while the host-device lock wasn't being held) we need to bail here
throw new EngineException(EngineError.HOST_DEVICES_TAKEN_BY_OTHER_VM);
} else {
status = createVm();
ExecutionHandler.setAsyncJob(getExecutionContext(), true);
markHostDevicesAsUsed();
}
}
} catch(EngineException e) {
// if the returned exception is such that shoudn't trigger the re-run process,
// re-throw it. otherwise, continue (the vm will be down and a re-run will be triggered)
switch (e.getErrorCode()) {
case Done: // should never get here with errorCode = 'Done' though
case exist:
cleanupPassthroughVnics();
reportCompleted();
throw e;
case VDS_NETWORK_ERROR: // probably wrong xml format sent.
case PROVIDER_FAILURE:
case HOST_DEVICES_TAKEN_BY_OTHER_VM:
runningFailed();
throw e;
default:
log.warn("Failed to run VM '{}': {}", getVmName(), e.getMessage());
}
} finally {
releaseHostDevicesLock();
freeLock();
}
setActionReturnValue(status);
if (status != null && (status.isRunning() || status == VMStatus.RestoringState)) {
setSucceeded(true);
} else {
// Try to rerun Vm on different vds no need to log the command because it is
// being logged inside the rerun
log.info("Trying to rerun VM '{}'", getVm().getName());
setCommandShouldBeLogged(false);
setSucceeded(true);
rerun();
}
}
else {
runningFailed();
}
}
private void cleanupPassthroughVnics() {
cleanupPassthroughVnics(getVdsId());
}
/**
* Checks if all required Host Devices are available.
* Should be called with held host-device lock.
* <br>
* See {@link #acquireHostDevicesLock()} and {@link #releaseHostDevicesLock()}.
**/
private boolean checkRequiredHostDevicesAvailability() {
if (!needsHostDevices) {
return true;
}
// Only single dedicated host allowed for host devices, verified on validates
return hostDeviceManager.checkVmHostDeviceAvailability(getVm(), getVm().getDedicatedVmForVdsList().get(0));
}
private void markHostDevicesAsUsed() {
if (needsHostDevices) {
hostDeviceManager.allocateVmHostDevices(getVm());
}
}
private void acquireHostDevicesLock() {
if (needsHostDevices) {
// Only single dedicated host allowed for host devices, verified on validates
hostDeviceManager.acquireHostDevicesLock(getVm().getDedicatedVmForVdsList().get(0));
}
}
private void releaseHostDevicesLock() {
if (needsHostDevices) {
// Only single dedicated host allowed for host devices, verified on validates
hostDeviceManager.releaseHostDevicesLock(getVm().getDedicatedVmForVdsList().get(0));
}
}
@Override
protected void executeVmCommand() {
getVmManager().setPowerOffTimeout(System.nanoTime());
setActionReturnValue(VMStatus.Down);
initVm();
perform();
}
@Override
public void rerun() {
cleanupPassthroughVnics();
setFlow(null);
super.rerun();
}
private RunVmFlow setFlow(RunVmFlow flow) {
getParameters().setCachedFlow(flow);
return flow;
}
/**
* Determine the flow in which the command should be operating or
* return the cached flow if it was already computed
*
* @return the flow in which the command is operating
*/
protected RunVmFlow getFlow() {
RunVmFlow cachedFlow = getParameters().getCachedFlow();
if (cachedFlow != null) {
return cachedFlow;
}
switch(getVm().getStatus()) {
case Paused:
return setFlow(RunVmFlow.RESUME_PAUSE);
case Suspended:
return setFlow(RunVmFlow.RESUME_HIBERNATE);
default:
}
if (isRunAsStateless()) {
fetchVmDisksFromDb();
if (isStatelessSnapshotExistsForVm()) {
log.error("VM '{}' ({}) already contains stateless snapshot, removing it",
getVm().getName(), getVm().getId());
return setFlow(RunVmFlow.REMOVE_STATELESS_IMAGES);
}
return setFlow(RunVmFlow.CREATE_STATELESS_IMAGES);
}
if (!isInternalExecution()
&& isStatelessSnapshotExistsForVm()
&& !isVmPartOfManualPool()) {
return setFlow(RunVmFlow.REMOVE_STATELESS_IMAGES);
}
return setFlow(RunVmFlow.RUN);
}
protected void perform() {
switch(getFlow()) {
case RESUME_PAUSE:
resumeVm();
break;
case REMOVE_STATELESS_IMAGES:
removeStatlessSnapshot();
break;
case CREATE_STATELESS_IMAGES:
createStatelessSnapshot();
break;
case RESUME_HIBERNATE:
case RUN:
default:
runVm();
}
}
/**
* @return true if a stateless snapshot exists for the VM, false otherwise
*/
protected boolean isStatelessSnapshotExistsForVm() {
if (cachedStatelessSnapshotExistsForVm == null) {
cachedStatelessSnapshotExistsForVm = snapshotDao.exists(getVm().getId(), SnapshotType.STATELESS);
}
return cachedStatelessSnapshotExistsForVm;
}
/**
* Returns the CD path in the following order (from high to low):
* (1) The path given in the parameters
* (2) The ISO path stored in the database if the boot sequence contains CD ROM
* (3) Guest agent tools iso
* (4) The ISO path stored in the database
*
* Note that in (2) we assume that the CD is bootable
*/
private String chooseCd() {
if (getParameters().getDiskPath() != null) {
return getParameters().getDiskPath();
}
if (getVm().getBootSequence() != null && getVm().getBootSequence().containsSubsequence(BootSequence.D)) {
return getVm().getIsoPath();
}
String guestToolPath = guestToolsVersionTreatment();
if (guestToolPath != null) {
return guestToolPath;
}
return getVm().getIsoPath();
}
protected IsoDomainListSynchronizer getIsoDomainListSynchronizer() {
return isoDomainListSynchronizer;
}
private void createStatelessSnapshot() {
warnIfNotAllDisksPermitSnapshots();
log.info("Creating stateless snapshot for VM '{}' ({})",
getVm().getName(), getVm().getId());
CreateAllSnapshotsFromVmParameters createAllSnapshotsFromVmParameters = buildCreateSnapshotParameters();
VdcReturnValueBase vdcReturnValue = runInternalAction(VdcActionType.CreateAllSnapshotsFromVm,
createAllSnapshotsFromVmParameters,
createContextForStatelessSnapshotCreation());
// setting lock to null in order not to release lock twice
setLock(null);
setSucceeded(vdcReturnValue.getSucceeded());
if (!vdcReturnValue.getSucceeded()) {
if (areDisksLocked(vdcReturnValue)) {
throw new EngineException(EngineError.IRS_IMAGE_STATUS_ILLEGAL);
}
getReturnValue().setFault(vdcReturnValue.getFault());
log.error("Failed to create stateless snapshot for VM '{}' ({})",
getVm().getName(), getVm().getId());
}
}
private CommandContext createContextForStatelessSnapshotCreation() {
Map<String, String> values = getVmValuesForMsgResolving();
// Creating snapshots as sub step of run stateless
Step createSnapshotsStep = addSubStep(StepEnum.EXECUTING,
StepEnum.CREATING_SNAPSHOTS, values);
// Add the step as the first step of the new context
ExecutionContext createSnapshotsCtx = new ExecutionContext();
createSnapshotsCtx.setMonitored(true);
createSnapshotsCtx.setStep(createSnapshotsStep);
getContext().withExecutionContext(createSnapshotsCtx);
persistCommandIfNeeded();
return getContext().clone().withoutCompensationContext();
}
private CreateAllSnapshotsFromVmParameters buildCreateSnapshotParameters() {
CreateAllSnapshotsFromVmParameters parameters =
new CreateAllSnapshotsFromVmParameters(getVm().getId(), STATELESS_SNAPSHOT_DESCRIPTION, false);
parameters.setShouldBeLogged(false);
parameters.setParentCommand(getActionType());
parameters.setParentParameters(getParameters());
parameters.setEntityInfo(getParameters().getEntityInfo());
parameters.setSnapshotType(SnapshotType.STATELESS);
return parameters;
}
private boolean areDisksLocked(VdcReturnValueBase vdcReturnValue) {
return vdcReturnValue.getValidationMessages().contains(
EngineMessage.ACTION_TYPE_FAILED_DISKS_LOCKED.name());
}
private void warnIfNotAllDisksPermitSnapshots() {
for (Disk disk : getVm().getDiskMap().values()) {
if (!disk.isAllowSnapshot()) {
auditLogDirector.log(this,
AuditLogType.USER_RUN_VM_AS_STATELESS_WITH_DISKS_NOT_ALLOWING_SNAPSHOT);
break;
}
}
}
protected Map<String, String> getVmValuesForMsgResolving() {
return Collections.singletonMap(VdcObjectType.VM.name().toLowerCase(), getVmName());
}
private void removeStatlessSnapshot() {
runInternalAction(VdcActionType.ProcessDownVm,
new ProcessDownVmParameters(getVm().getId(), true),
ExecutionHandler.createDefaultContextForTasks(getContext(), getLock()));
// setting lock to null in order not to release lock twice
setLock(null);
setSucceeded(true);
}
protected VMStatus createVm() {
updateCdPath();
if (!StringUtils.isEmpty(getParameters().getFloppyPath())) {
getVm().setFloppyPath(cdPathWindowsToLinux(getParameters().getFloppyPath()));
}
// Set path for initrd and kernel image.
if (!StringUtils.isEmpty(getVm().getInitrdUrl())) {
getVm().setInitrdUrl(getIsoPrefixFilePath(getVm().getInitrdUrl()));
}
if (!StringUtils.isEmpty(getVm().getKernelUrl())) {
getVm().setKernelUrl(getIsoPrefixFilePath(getVm().getKernelUrl()));
}
initParametersForExternalNetworks();
VMStatus vmStatus = (VMStatus) getVdsBroker()
.runAsyncVdsCommand(VDSCommandType.Create, buildCreateVmParameters(), this).getReturnValue();
// Don't use the memory from the active snapshot anymore if there's a chance that disks were changed
memoryFromSnapshotUsed = vmStatus.isRunning() || vmStatus == VMStatus.RestoringState;
// After VM was create (or not), we can remove the quota vds group memory.
return vmStatus;
}
protected void updateCurrentCd(String cdPath) {
cdPath = StringUtils.isEmpty(cdPath) ? null : cdPath;
vmHandler.updateCurrentCd(getVm(), cdPath);
}
/**
* Initialize the parameters for the VDSM command of VM creation
*
* @return the VDS create VM parameters
*/
protected CreateVDSCommandParameters buildCreateVmParameters() {
CreateVDSCommandParameters parameters = new CreateVDSCommandParameters(getVdsId(), getVm());
parameters.setRunInUnknownStatus(getParameters().isRunInUnknownStatus());
parameters.setVmPayload(vmPayload);
parameters.setHibernationVolHandle(getMemoryFromActiveSnapshot());
parameters.setPassthroughVnicToVfMap(flushPassthroughVnicToVfMap());
if (initializationType == InitializationType.Sysprep
&& osRepository.isWindows(getVm().getVmOsId())
&& (getVm().getFloppyPath() == null || "".equals(getVm().getFloppyPath()))) {
parameters.setInitializationType(InitializationType.Sysprep);
}
if (initializationType == InitializationType.CloudInit && !osRepository.isWindows(getVm().getVmOsId())) {
parameters.setInitializationType(InitializationType.CloudInit);
}
return parameters;
}
protected void initParametersForExternalNetworks() {
if (getVm().getInterfaces().isEmpty()) {
return;
}
Map<VmDeviceId, VmDevice> nicDevices =
Entities.businessEntitiesById(vmDeviceDao.getVmDeviceByVmIdAndType(getVmId(),
VmDeviceGeneralType.INTERFACE));
for (VmNic iface : getVm().getInterfaces()) {
VnicProfile vnicProfile = vnicProfileDao.get(iface.getVnicProfileId());
Network network = NetworkHelper.getNetworkByVnicProfile(vnicProfile);
VmDevice vmDevice = nicDevices.get(new VmDeviceId(iface.getId(), getVmId()));
if (network != null && network.isExternal() && vmDevice.isPlugged()) {
Provider<?> provider = providerDao.get(network.getProvidedBy().getProviderId());
NetworkProviderProxy providerProxy = ProviderProxyFactory.getInstance().create(provider);
Map<String, String> deviceProperties = providerProxy.allocate(network, vnicProfile, iface, getVds());
getVm().getRuntimeDeviceCustomProperties().put(vmDevice.getId(), deviceProperties);
}
}
}
protected Map<Guid, String> flushPassthroughVnicToVfMap() {
Map<Guid, String> passthroughVnicToVf = getVnicToVfMap(getVdsId());
vfScheduler.cleanVmData(getVmId());
return passthroughVnicToVf;
}
@Override
public AuditLogType getAuditLogTypeValue() {
switch (getActionState()) {
case EXECUTE:
if (getFlow() == RunVmFlow.REMOVE_STATELESS_IMAGES) {
return AuditLogType.USER_RUN_VM_FAILURE_STATELESS_SNAPSHOT_LEFT;
}
if (getFlow() == RunVmFlow.RESUME_PAUSE) {
return getSucceeded() ? AuditLogType.USER_RESUME_VM : AuditLogType.USER_FAILED_RESUME_VM;
} else if (isInternalExecution()) {
if (getSucceeded()) {
boolean isStateless = isStatelessSnapshotExistsForVm();
if (isStateless) {
return AuditLogType.VDS_INITIATED_RUN_VM_AS_STATELESS;
} else if (getFlow() == RunVmFlow.CREATE_STATELESS_IMAGES) {
return AuditLogType.VDS_INITIATED_RUN_AS_STATELESS_VM_NOT_YET_RUNNING;
} else {
return AuditLogType.VDS_INITIATED_RUN_VM;
}
}
return AuditLogType.VDS_INITIATED_RUN_VM_FAILED;
} else {
return getSucceeded() ?
getActionReturnValue() == VMStatus.Up ?
isVmRunningOnNonDefaultVds() ?
AuditLogType.USER_RUN_VM_ON_NON_DEFAULT_VDS
: isStatelessSnapshotExistsForVm() ?
AuditLogType.USER_RUN_VM_AS_STATELESS
: AuditLogType.USER_RUN_VM
: _isRerun ?
AuditLogType.VDS_INITIATED_RUN_VM
: getVm().isRunAndPause() ?
AuditLogType.USER_INITIATED_RUN_VM_AND_PAUSE
: getTaskIdList().isEmpty() ?
AuditLogType.USER_STARTED_VM
: AuditLogType.USER_INITIATED_RUN_VM
: _isRerun ? AuditLogType.USER_INITIATED_RUN_VM_FAILED : AuditLogType.USER_FAILED_RUN_VM;
}
case END_SUCCESS:
// if not running as stateless, or if succeeded running as
// stateless,
// command should be with 'CommandShouldBeLogged = false':
return isStatelessSnapshotExistsForVm() && !getSucceeded() ?
AuditLogType.USER_RUN_VM_AS_STATELESS_FINISHED_FAILURE : AuditLogType.UNASSIGNED;
case END_FAILURE:
// if not running as stateless, command should
// be with 'CommandShouldBeLogged = false':
return isStatelessSnapshotExistsForVm() ?
AuditLogType.USER_RUN_VM_AS_STATELESS_FINISHED_FAILURE : AuditLogType.UNASSIGNED;
default:
// all other cases should be with 'CommandShouldBeLogged =
// false':
return AuditLogType.UNASSIGNED;
}
}
protected boolean isVmRunningOnNonDefaultVds() {
return !getVm().getDedicatedVmForVdsList().isEmpty()
&& !getVm().getDedicatedVmForVdsList().contains(getVm().getRunOnVds());
}
/**
* @return true if we need to create the VM object, false otherwise
*/
private boolean isInitVmRequired() {
return EnumSet.of(RunVmFlow.RUN, RunVmFlow.RESUME_HIBERNATE).contains(getFlow());
}
protected void initVm() {
if (!isInitVmRequired()) {
return;
}
fetchVmDisksFromDb();
updateVmDevicesOnRun();
updateGraphicsAndDisplayInfos();
getVm().setRunAndPause(getParameters().getRunAndPause() == null ? getVm().isRunAndPause() : getParameters().getRunAndPause());
// Clear the first user:
getVm().setConsoleUserId(null);
updateVmInit();
// if we asked for floppy from Iso Domain we cannot
// have floppy payload since we are limited to only one floppy device
if (!StringUtils.isEmpty(getParameters().getFloppyPath()) && isPayloadExists(VmDeviceType.FLOPPY)) {
vmPayload = null;
}
updateVmGuestAgentVersion();
// update dynamic cluster-parameters
if (getVm().getCpuName() == null) { // no run-once data -> use static field or inherit from cluster
if (getVm().getCustomCpuName() != null) {
getVm().setCpuName(getVm().getCustomCpuName());
} else {
// get what cpu flags should be passed to vdsm according to the cluster
getVm().setCpuName(getCpuFlagsManagerHandler().getCpuId(
getVm().getClusterCpuName(),
getVm().getCompatibilityVersion()));
}
}
if (getVm().getEmulatedMachine() == null) {
getVm().setEmulatedMachine(getEffectiveEmulatedMachine());
}
}
protected String getEffectiveEmulatedMachine() {
if (getVm().getCustomEmulatedMachine() != null) {
return getVm().getCustomEmulatedMachine();
}
// The 'default' to be set
String recentClusterDefault = getCluster().getEmulatedMachine();
if (getVm().getCustomCompatibilityVersion() == null) {
return recentClusterDefault;
}
String bestMatch = findBestMatchForEmulatedMachine(
recentClusterDefault,
Config.getValue(
ConfigValues.ClusterEmulatedMachines,
getVm().getCustomCompatibilityVersion().getValue()));
log.info("Emulated machine '{}' selected since Custom Compatibility Version is set for '{}'", bestMatch, getVm());
return bestMatch;
}
protected String findBestMatchForEmulatedMachine(
String currentEmulatedMachine,
List<String> candidateEmulatedMachines) {
if (candidateEmulatedMachines.contains(currentEmulatedMachine)) {
return currentEmulatedMachine;
}
return candidateEmulatedMachines
.stream()
.max(Comparator.comparingInt(s -> StringUtils.indexOfDifference(currentEmulatedMachine, s)))
.orElse(currentEmulatedMachine);
}
/**
* This methods sets graphics infos of a VM to correspond to graphics devices set in DB
* No Display Type update is needed here.
*/
protected void updateGraphicsAndDisplayInfos() {
for (VmDevice vmDevice : vmDeviceDao.getVmDeviceByVmIdAndType(getVmId(), VmDeviceGeneralType.GRAPHICS)) {
getVm().getGraphicsInfos().put(GraphicsType.fromString(vmDevice.getDevice()), new GraphicsInfo());
}
}
protected boolean isPayloadExists(VmDeviceType deviceType) {
return vmPayload != null && vmPayload.getDeviceType().equals(deviceType);
}
protected void loadPayload() {
List<VmDevice> disks = vmDeviceDao.getVmDeviceByVmIdAndType(getVm().getId(), VmDeviceGeneralType.DISK);
for (VmDevice disk : disks) {
if (disk.isManaged() && VmPayload.isPayload(disk.getSpecParams())) {
vmPayload = new VmPayload(disk);
break;
}
}
}
protected void fetchVmDisksFromDb() {
if (getVm().getDiskMap().isEmpty()) {
vmHandler.updateDisksFromDb(getVm());
}
}
protected void updateVmDevicesOnRun() {
// Before running the VM we update its devices, as they may
// need to be changed due to configuration option change
getVmDeviceUtils().updateVmDevicesOnRun(getVm().getStaticData());
}
protected void updateCdPath() {
final String cdPath = chooseCd();
if (StringUtils.isNotEmpty(cdPath)) {
log.info("Running VM with attached cd '{}'", cdPath);
}
updateCurrentCd(cdPath);
getVm().setCdPath(cdPathWindowsToLinux(cdPath));
}
protected void updateVmGuestAgentVersion() {
vmHandler.updateVmGuestAgentVersion(getVm());
}
protected void updateVmInit() {
if (getParameters().getInitializationType() == null) {
vmHandler.updateVmInitFromDB(getVm().getStaticData(), false);
if (!getVm().isInitialized() && getVm().getVmInit() != null) {
if (osRepository.isWindows(getVm().getVmOsId())) {
if (!isPayloadExists(VmDeviceType.FLOPPY)) {
initializationType = InitializationType.Sysprep;
}
}
else {
if (!isPayloadExists(VmDeviceType.CDROM)) {
initializationType = InitializationType.CloudInit;
}
}
}
} else if (getParameters().getInitializationType() != InitializationType.None) {
initializationType = getParameters().getInitializationType();
// If the user asked for sysprep/cloud-init via run-once we eliminate
// the payload since we can only have one media (Floppy/CDROM) per payload.
if (getParameters().getInitializationType() == InitializationType.Sysprep &&
isPayloadExists(VmDeviceType.FLOPPY)) {
vmPayload = null;
} else if (getParameters().getInitializationType() == InitializationType.CloudInit &&
isPayloadExists(VmDeviceType.CDROM)) {
vmPayload = null;
}
}
}
protected boolean getVdsToRunOn() {
Optional<Guid> vdsToRunOn =
schedulingManager.schedule(getCluster(),
getVm(),
getRunVdssList(),
getVdsWhiteList(),
getPredefinedVdsIdListToRunOn(),
new ArrayList<>(),
new VdsFreeMemoryChecker(this),
getCorrelationId());
setVdsId(vdsToRunOn.orElse(null));
if (vdsToRunOn.isPresent()) {
getRunVdssList().add(vdsToRunOn.get());
}
setVds(null);
setVdsName(null);
if (getVdsId().equals(Guid.Empty)) {
log.error("Can't find VDS to run the VM '{}' on, so this VM will not be run.", getVmId());
return false;
}
if (getVds() == null) {
EngineException outEx = new EngineException(EngineError.RESOURCE_MANAGER_VDS_NOT_FOUND);
log.error("VmHandler::{}: {}", getClass().getName(), outEx.getMessage());
return false;
}
return true;
}
/**
* If vds version greater then vm's and vm not running with cd and there is appropriate RhevAgentTools image -
* add it to vm as cd.
*/
private String guestToolsVersionTreatment() {
boolean attachCd = false;
String selectedToolsVersion = "";
String selectedToolsClusterVersion = "";
Guid isoDomainId = getActiveIsoDomainId();
if (osRepository.isWindows(getVm().getVmOsId()) && null != isoDomainId) {
// get cluster version of the vm tools
Version vmToolsClusterVersion = null;
if (getVm().getHasAgent()) {
Version clusterVer = getVm().getPartialVersion();
if (new Version("4.4").equals(clusterVer)) {
vmToolsClusterVersion = new Version("2.1");
} else {
vmToolsClusterVersion = clusterVer;
}
}
// Fetch cached Iso files from active Iso domain.
List<RepoImage> repoFilesMap =
getIsoDomainListSynchronizer().getCachedIsoListByDomainId(isoDomainId, ImageFileType.ISO);
Version bestClusterVer = null;
int bestToolVer = 0;
for (RepoImage map : repoFilesMap) {
String fileName = StringUtils.defaultString(map.getRepoImageId(), "");
Matcher matchToolPattern =
Pattern.compile(IsoDomainListSynchronizer.REGEX_TOOL_PATTERN).matcher(fileName);
if (matchToolPattern.find()) {
// Get cluster version and tool version of Iso tool.
Version clusterVer = new Version(matchToolPattern.group(IsoDomainListSynchronizer.TOOL_CLUSTER_LEVEL));
int toolVersion = Integer.parseInt(matchToolPattern.group(IsoDomainListSynchronizer.TOOL_VERSION));
if (clusterVer.compareTo(getVm().getCompatibilityVersion()) <= 0) {
if ((bestClusterVer == null)
|| (clusterVer.compareTo(bestClusterVer) > 0)) {
bestToolVer = toolVersion;
bestClusterVer = clusterVer;
} else if (clusterVer.equals(bestClusterVer) && toolVersion > bestToolVer) {
bestToolVer = toolVersion;
bestClusterVer = clusterVer;
}
}
}
}
if (bestClusterVer != null
&& (vmToolsClusterVersion == null
|| vmToolsClusterVersion.compareTo(bestClusterVer) < 0 || (vmToolsClusterVersion.equals(bestClusterVer)
&& getVm().getHasAgent() &&
getVm().getGuestAgentVersion().getBuild() < bestToolVer))) {
// Vm has no tools or there are new tools
selectedToolsVersion = Integer.toString(bestToolVer);
selectedToolsClusterVersion = bestClusterVer.toString();
attachCd = true;
}
}
if (attachCd) {
String rhevToolsPath =
String.format("%1$s%2$s_%3$s.iso", IsoDomainListSynchronizer.getGuestToolsSetupIsoPrefix(),
selectedToolsClusterVersion, selectedToolsVersion);
String isoDir = (String) runVdsCommand(VDSCommandType.IsoDirectory,
new IrsBaseVDSCommandParameters(getVm().getStoragePoolId())).getReturnValue();
rhevToolsPath = isoDir + File.separator + rhevToolsPath;
return rhevToolsPath;
}
return null;
}
@Override
protected boolean validate() {
VM vm = getVm();
if (vm == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND);
}
if (!validateObject(vm.getStaticData())) {
return false;
}
if (!canRunActionOnNonManagedVm()) {
return false;
}
if (getVm().getCustomCompatibilityVersion() != null &&
vm.getCustomCompatibilityVersion().less(getStoragePool().getCompatibilityVersion())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_COMATIBILITY_VERSION_NOT_SUPPORTED,
String.format("$VmName %1$s", getVm().getName()),
String.format("$VmVersion %1$s", getVm().getCustomCompatibilityVersion().toString()),
String.format("$DcVersion %1$s", getStoragePool().getCompatibilityVersion()));
}
RunVmValidator runVmValidator = getRunVmValidator();
if (!runVmValidator.canRunVm(
getReturnValue().getValidationMessages(),
getStoragePool(),
getRunVdssList(),
getVdsWhiteList(),
getCluster(),
getParameters().isRunInUnknownStatus())) {
return false;
}
if (!validate(runVmValidator.validateNetworkInterfaces())) {
return false;
}
if (!checkRngDeviceClusterCompatibility()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_RNG_SOURCE_NOT_SUPPORTED);
}
boolean isWindowsOs = osRepository.isWindows(getVm().getVmOsId());
boolean isCloudInitEnabled = (!getVm().isInitialized() && getVm().getVmInit() != null && !isWindowsOs) ||
(getParameters().getInitializationType() == InitializationType.CloudInit);
if (isCloudInitEnabled && hasMaximumNumberOfDisks()) {
return failValidation(EngineMessage.VMPAYLOAD_CDROM_OR_CLOUD_INIT_MAXIMUM_DEVICES);
}
if (!vmHandler.isCpuSupported(
getVm().getVmOsId(),
getVm().getCompatibilityVersion(),
getCluster().getCpuName(),
getReturnValue().getValidationMessages())) {
return false;
}
try {
acquireHostDevicesLock();
if (!checkRequiredHostDevicesAvailability()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_HOST_DEVICE_NOT_AVAILABLE);
}
} finally {
releaseHostDevicesLock();
}
return true;
}
protected void loadVmInit() {
if (getVm().getVmInit() == null) {
vmHandler.updateVmInitFromDB(getVm().getStaticData(), false);
}
}
protected boolean hasMaximumNumberOfDisks() {
return ArchStrategyFactory.getStrategy(getVm().getClusterArch()).run(new HasMaximumNumberOfDisks(getVm().getId())).returnValue();
}
/**
* Checks whether rng device of vm is required by cluster, which is requirement for running vm.
*
* @return true if the source of vm rng device is required by cluster (and therefore supported by hosts in cluster)
*/
boolean checkRngDeviceClusterCompatibility() {
List<VmDevice> rngDevs =
vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(getVmId(), VmDeviceGeneralType.RNG, VmDeviceType.VIRTIO.getName());
if (rngDevs.isEmpty()) {
return true;
}
VmRngDevice rngDevice = new VmRngDevice(rngDevs.get(0));
final RngUtils.RngValidationResult rngValidationResult =
RngUtils.validate(getCluster(), rngDevice, getVm().getCompatibilityVersion());
switch (rngValidationResult) {
case VALID:
return true;
case UNSUPPORTED_URANDOM_OR_RANDOM:
log.warn("Running VM {}({}) with rng source {} that is not supported in cluster {}.",
getVm().getName(),
getVm().getId(),
rngDevice.getSource(),
getCluster().getName());
return true;
case INVALID:
return false;
default:
throw new RuntimeException("Unknown enum constant " + rngValidationResult);
}
}
@Override
protected List<Class<?>> getValidationGroups() {
addValidationGroup(StartEntity.class);
return super.getValidationGroups();
}
protected RunVmValidator getRunVmValidator() {
return Injector.injectMembers(new RunVmValidator(getVm(), getParameters(), isInternalExecution(), getActiveIsoDomainId()));
}
protected Guid getActiveIsoDomainId() {
if (cachedActiveIsoDomainId == null) {
cachedActiveIsoDomainId = getIsoDomainListSynchronizer()
.findActiveISODomain(getVm().getStoragePoolId());
}
return cachedActiveIsoDomainId;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__RUN);
addValidationMessage(EngineMessage.VAR__TYPE__VM);
}
private boolean shouldEndSnapshotCreation() {
return getFlow() == RunVmFlow.CREATE_STATELESS_IMAGES && isStatelessSnapshotExistsForVm();
}
@Override
protected void endSuccessfully() {
if (shouldEndSnapshotCreation()) {
getBackend().endAction(VdcActionType.CreateAllSnapshotsFromVm,
getParameters().getImagesParameters().get(0),
getContext().clone().withoutCompensationContext().withoutExecutionContext().withoutLock());
getParameters().setShouldBeLogged(false);
getParameters().setRunAsStateless(false);
getParameters().setCachedFlow(null);
setSucceeded(getBackend().runInternalAction(
getActionType(), getParameters(), createContextForRunStatelessVm()).getSucceeded());
if (!getSucceeded()) {
getParameters().setShouldBeLogged(true);
log.error("Could not run VM '{}' ({}) in stateless mode",
getVm().getName(), getVm().getId());
// could not run the vm don't try to run the end action again
getReturnValue().setEndActionTryAgain(false);
}
}
// Hibernation (VMStatus.Suspended) treatment:
else {
super.endSuccessfully();
}
}
private CommandContext createContextForRunStatelessVm() {
Step step = getExecutionContext().getStep();
if (step == null) {
return cloneContextAndDetachFromParent();
}
// Retrieve the job object and its steps as this the endSuccessfully stage of command execution -
// at this is a new instance of the command is used
// (comparing with the execution state) so all information on the job and steps should be retrieved.
Job job = jobRepository.getJobWithSteps(step.getJobId());
Step executingStep = job.getDirectStep(StepEnum.EXECUTING);
// We would like to to set the run stateless step as substep of executing step
setInternalExecution(true);
ExecutionContext runStatelessVmCtx = new ExecutionContext();
// The internal command should be monitored for tasks
runStatelessVmCtx.setMonitored(true);
Step runStatelessStep =
executionHandler.addSubStep(getExecutionContext(),
executingStep,
StepEnum.RUN_STATELESS_VM,
ExecutionMessageDirector.resolveStepMessage(StepEnum.RUN_STATELESS_VM,
getVmValuesForMsgResolving()));
// This is needed in order to end the job upon execution of the steps of the child command
runStatelessVmCtx.setShouldEndJob(true);
runStatelessVmCtx.setJob(job);
// Since run stateless step involves invocation of command, we should set the run stateless vm step as
// the "beginning step" of the child command.
runStatelessVmCtx.setStep(runStatelessStep);
return cloneContextAndDetachFromParent().withExecutionContext(runStatelessVmCtx);
}
@Override
protected void endWithFailure() {
if (shouldEndSnapshotCreation()) {
VdcReturnValueBase vdcReturnValue = getBackend().endAction(VdcActionType.CreateAllSnapshotsFromVm,
getParameters().getImagesParameters().get(0), cloneContext().withoutExecutionContext()
.withoutLock());
setSucceeded(vdcReturnValue.getSucceeded());
// we are not running the VM, of course,
// since we couldn't create a snapshot.
}
else {
super.endWithFailure();
}
}
@Override
public void runningSucceded() {
removeMemoryFromActiveSnapshot();
setFlow(RunVmFlow.RUNNING_SUCCEEDED);
super.runningSucceded();
}
@Override
protected void runningFailed() {
cleanupPassthroughVnics();
if (memoryFromSnapshotUsed) {
removeMemoryFromActiveSnapshot();
}
super.runningFailed();
}
private void removeMemoryFromActiveSnapshot() {
String memory = getActiveSnapshot().getMemoryVolume();
if (StringUtils.isEmpty(memory)) {
return;
}
snapshotDao.removeMemoryFromActiveSnapshot(getVmId());
// If the memory volumes are not used by any other snapshot, we can remove them
if (snapshotDao.getNumOfSnapshotsByMemory(memory) == 0) {
removeMemoryDisks(memory);
}
}
/**
* @return true if the VM should run as stateless
*/
protected boolean isRunAsStateless() {
return getParameters().getRunAsStateless() != null ?
getParameters().getRunAsStateless()
: getVm().getVmPoolId() == null && getVm().isStateless();
}
@Override
public void addQuotaPermissionSubject(List<PermissionSubject> quotaPermissionList) {
}
@Override
public List<QuotaConsumptionParameter> getQuotaVdsConsumptionParameters() {
List<QuotaConsumptionParameter> list = new ArrayList<>();
list.add(new QuotaClusterConsumptionParameter(getVm().getQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.CONSUME,
getVm().getClusterId(),
getVm().getCpuPerSocket() * getVm().getNumOfSockets(),
getVm().getMemSizeMb()));
return list;
}
protected boolean isVmPartOfManualPool() {
if (getVm().getVmPoolId() == null) {
return false;
}
final VmPool vmPool = vmPoolDao.get(getVm().getVmPoolId());
return vmPool.getVmPoolType().equals(VmPoolType.MANUAL);
}
@Override
protected void endExecutionMonitoring() {
if (getVm().isRunAndPause() && vmDynamicDao.get(getVmId()).getStatus() == VMStatus.Paused) {
final ExecutionContext executionContext = getExecutionContext();
executionContext.setShouldEndJob(true);
executionHandler.endJob(executionContext, true);
} else {
super.endExecutionMonitoring();
}
}
/**
* Initial white list for scheduler (empty == all hosts)
*/
protected List<Guid> getVdsWhiteList() {
return Collections.emptyList();
}
/**
* Since this callback is called by the VdsUpdateRunTimeInfo thread, we don't want it
* to fetch the VM using {@link #getVm()}, as the thread that invokes {@link #rerun()},
* which runs in parallel, is doing setVm(null) to refresh the VM, and because of this
* race we might end up with null VM. so we fetch the static part of the VM from the DB.
*/
@Override
public void onPowerringUp() {
decreasePendingVm(vmStaticDao.get(getVmId()));
}
@Override
public void actualDowntimeReported(int actualDowntime) {
// nothing to do
}
@Override
public CommandCallback getCallback() {
return getFlow().isStateless() ? new ConcurrentChildCommandsExecutionCallback() : null;
}
}