package org.ovirt.engine.core.bll; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.network.macpool.MacPool; import org.ovirt.engine.core.bll.network.macpool.MacPoolPerCluster; import org.ovirt.engine.core.bll.snapshots.SnapshotsManager; import org.ovirt.engine.core.bll.snapshots.SnapshotsValidator; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.utils.VmDeviceUtils; import org.ovirt.engine.core.bll.validator.LocalizedVmStatus; import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.RemoveDiskParameters; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.VmLeaseParameters; import org.ovirt.engine.core.common.action.VmOperationParameterBase; import org.ovirt.engine.core.common.asynctasks.AsyncTaskType; import org.ovirt.engine.core.common.businessentities.Quota; import org.ovirt.engine.core.common.businessentities.Snapshot; import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.TagsVmMap; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmPayload; import org.ovirt.engine.core.common.businessentities.VmStatic; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.DiskInterface; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.utils.SimpleDependencyInjector; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VdsAndPoolIDVDSParametersBase; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.QuotaDao; import org.ovirt.engine.core.dao.SnapshotDao; import org.ovirt.engine.core.dao.StorageDomainDao; import org.ovirt.engine.core.dao.TagDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.dao.network.VmNicDao; import org.ovirt.engine.core.utils.GuidUtils; import org.ovirt.engine.core.vdsbroker.ResourceManager; import org.ovirt.engine.core.vdsbroker.VmManager; public abstract class VmCommand<T extends VmOperationParameterBase> extends CommandBase<T> { private static final int Kb = 1024; @Inject protected MacPoolPerCluster macPoolPerCluster; @Inject private VmDeviceUtils vmDeviceUtils; @Inject protected CpuFlagsManagerHandler cpuFlagsManagerHandler; @Inject private SnapshotsManager snapshotsManager; @Inject protected SnapshotsValidator snapshotsValidator; @Inject protected VmHandler vmHandler; @Inject protected VmTemplateHandler vmTemplateHandler; @Inject private ResourceManager resourceManager; @Inject private VmStaticDao vmStaticDao; @Inject private VmNicDao vmNicDao; @Inject private TagDao tagDao; @Inject private SnapshotDao snapshotDao; @Inject private QuotaDao quotaDao; @Inject private StorageDomainDao storageDomainDao; protected final OsRepository osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class); private Boolean skipCommandExecution; private MacPool macPool; public VmCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); setVmId(parameters.getVmId()); } protected VmManager getVmManager() { return resourceManager.getVmManager(getVmId()); } protected MacPool getMacPool() { if (this.macPool == null) { this.macPool = macPoolPerCluster.getMacPoolForCluster(getClusterId(), getContext()); } return this.macPool; } protected CpuFlagsManagerHandler getCpuFlagsManagerHandler() { return cpuFlagsManagerHandler; } @Override public Guid getStoragePoolId() { if (super.getStoragePoolId() == null) { VM vm = getVm(); if (vm != null) { setStoragePoolId(vm.getStoragePoolId()); } } return super.getStoragePoolId(); } /** * Constructor for command creation when compensation is applied on startup */ protected VmCommand(Guid commandId) { super(commandId); } @Override protected void executeCommand() { if (shouldSkipCommandExecutionCached()) { setSucceeded(true); return; } executeVmCommand(); } protected void executeVmCommand() { // The default action is no action. // Other command may override this behavior. } @Override protected String getDescription() { return getVmName(); } // 3 IDE slots: 4 total minus 1 for CD public static final int MAX_IDE_SLOTS = 3; // The maximum number of VirtIO SCSI disks that libvirt // allows without creating another controller public static final int MAX_VIRTIO_SCSI_DISKS = 16383; // The maximum number of sPAPR VSCSI disks that // can be detected by the Linux kernel of PPC64 guests public static final int MAX_SPAPR_SCSI_DISKS = 7; private List<VmNic> interfaces; protected void removeVmStatic() { removeVmStatic(true); } protected void removeVmStatic(boolean removePermissions) { vmStaticDao.remove(getVmId(), removePermissions); } protected List<VmNic> getInterfaces() { if (interfaces == null) { interfaces = vmNicDao.getAllForVm(getVmId()); } return interfaces; } protected void removeVmNetwork() { MacPool macPool = getMacPool(); if (getInterfaces() != null) { for (VmNic iface : getInterfaces()) { macPool.freeMac(iface.getMacAddress()); } } } protected void removeVmSnapshots() { getSnapshotsManager().removeSnapshots(getVmId()); } protected void removeVmUsers() { List<TagsVmMap> all = tagDao.getTagVmMapByVmIdAndDefaultTag(getVmId()); for (TagsVmMap tagVm : all) { tagDao.detachVmFromTag(tagVm.getTagId(), getVmId()); } } protected void endVmCommand() { if (getVm() != null) { vmStaticDao.incrementDbGeneration(getVm().getId()); } endActionOnDisks(); unlockVm(); setSucceeded(true); } protected List<VdcReturnValueBase> endActionOnDisks() { List<VdcReturnValueBase> returnValues = new ArrayList<>(); for (VdcActionParametersBase p : getParametersForChildCommand()) { if (overrideChildCommandSuccess()) { p.setTaskGroupSuccess(getParameters().getTaskGroupSuccess()); } VdcReturnValueBase returnValue = getBackend().endAction( p.getCommandType() == VdcActionType.Unknown ? getChildActionType() : p.getCommandType(), p, getContext().clone().withoutCompensationContext().withoutExecutionContext().withoutLock()); returnValues.add(returnValue); } return returnValues; } protected List<VdcActionParametersBase> getParametersForChildCommand() { return getParameters().getImagesParameters(); } protected void unlockVm() { // Set VM property to null in order to refresh it from db setVm(null); if (getVm() != null) { if (getVm().getStatus() == VMStatus.ImageLocked) { vmHandler.unlockVm(getVm(), getCompensationContext()); } } else { setLoggingForCommand(); log.warn("VM is null - no unlocking"); } } protected void setLoggingForCommand() { setCommandShouldBeLogged(false); } /** * @return By default, <code>true</code> to override the child's success flag with the command's success flag. */ protected boolean overrideChildCommandSuccess() { return true; } @Override protected void endSuccessfully() { endVmCommand(); } @Override protected void endWithFailure() { endVmCommand(); } protected VdcActionType getChildActionType() { return VdcActionType.Unknown; } protected boolean removeMemoryDisks(String memory) { List<Guid> guids = GuidUtils.getGuidListFromString(memory); RemoveDiskParameters removeMemoryDumpDiskParameters = new RemoveDiskParameters(guids.get(2)); removeMemoryDumpDiskParameters.setShouldBeLogged(false); VdcReturnValueBase retVal = runInternalAction(VdcActionType.RemoveDisk, removeMemoryDumpDiskParameters); if (!retVal.getSucceeded()) { return false; } RemoveDiskParameters removeMemoryMetadataDiskParameters = new RemoveDiskParameters(guids.get(4)); removeMemoryMetadataDiskParameters.setShouldBeLogged(false); retVal = runInternalAction(VdcActionType.RemoveDisk, removeMemoryMetadataDiskParameters); if (!retVal.getSucceeded()) { return false; } return true; } @Override protected AsyncTaskType getTaskType() { return AsyncTaskType.deleteImage; } @Override public List<PermissionSubject> getPermissionCheckSubjects() { List<PermissionSubject> permissionList = new ArrayList<>(); permissionList.add(new PermissionSubject(getParameters().getVmId(), VdcObjectType.VM, getActionType().getActionGroup())); return permissionList; } /** * Checks if VM name has valid length (check that it's too long). This is used for validation by descending * commands. * * @param vm * the VM to check * @return true if the name is valid; false if it's too long */ protected boolean isVmNameValidLength(VM vm) { int maxLength = Config.<Integer> getValue(ConfigValues.MaxVmNameLength); boolean nameLengthValid = vm.getName().length() <= maxLength; return nameLengthValid; } /** * Lock the VM.<br> * If the command is run internally then compensation won't be used, since it might cause a deadlock if the calling * command has already updated the VM's row in the DB but hasn't committed before calling the child command.<br> * Otherwise, compensation will be used, and the VM will be locked in a new transaction, so that the lock gets * reflected in the DB immediately. */ protected void lockVmWithCompensationIfNeeded() { log.info("Locking VM(id = '{}') {} compensation.", getVmId(), isInternalExecution() ? "without" : "with"); if (isInternalExecution()) { vmHandler.checkStatusAndLockVm(getVmId()); } else { vmHandler.checkStatusAndLockVm(getVmId(), getCompensationContext()); } } /** * The following method should check if os of guest is supported for nic hot plug/unplug operation */ protected boolean isNicSupportedForPlugUnPlug() { if (osRepository.hasNicHotplugSupport(getVm().getOs(), getVm().getCompatibilityVersion())) { return true; } return failValidation(EngineMessage.ACTION_TYPE_FAILED_GUEST_OS_VERSION_IS_NOT_SUPPORTED); } /** * The following method should check if os of guest is supported for disk hot plug/unplug operation */ protected boolean isDiskSupportedForPlugUnPlug(DiskVmElement diskVmElement, String diskAlias) { if (diskVmElement.getDiskInterface() == DiskInterface.IDE) { addValidationMessageVariable("diskAlias", diskAlias); addValidationMessageVariable("vmName", getVm().getName()); return failValidation(EngineMessage.HOT_PLUG_IDE_DISK_IS_NOT_SUPPORTED); } Set<String> diskHotpluggableInterfaces = osRepository.getDiskHotpluggableInterfaces(getVm().getOs(), getVm().getCompatibilityVersion()); if (CollectionUtils.isEmpty(diskHotpluggableInterfaces) || !diskHotpluggableInterfaces.contains(diskVmElement.getDiskInterface().name())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_GUEST_OS_VERSION_IS_NOT_SUPPORTED); } return true; } protected boolean checkPayload(VmPayload payload, String isoPath) { boolean returnValue = true; if (payload.getDeviceType() != VmDeviceType.CDROM && payload.getDeviceType() != VmDeviceType.FLOPPY) { addValidationMessage(EngineMessage.VMPAYLOAD_INVALID_PAYLOAD_TYPE); returnValue = false; } else { for (String content : payload.getFiles().values()) { // Check each file individually, no constraint on total size if (!VmPayload.isPayloadSizeLegal(content)) { Integer lengthInKb = 2 * Config.<Integer> getValue(ConfigValues.PayloadSize) / Kb; addValidationMessage(EngineMessage.VMPAYLOAD_SIZE_EXCEEDED); addValidationMessageVariable("size", lengthInKb.toString()); returnValue = false; break; } } } return returnValue; } protected boolean canRunActionOnNonManagedVm() { ValidationResult nonManagedVmValidationResult = VmHandler.canRunActionOnNonManagedVm(getVm(), this.getActionType()); if (!nonManagedVmValidationResult.isValid()) { return failValidation(nonManagedVmValidationResult.getMessages()); } return true; } /** * use this method to get the result of shouldSkipCommandExecution * as it is also allow to cache the result for multiple calls */ protected final boolean shouldSkipCommandExecutionCached() { if (skipCommandExecution == null) { skipCommandExecution = shouldSkipCommandExecution(); } return skipCommandExecution; } /** * check for special conditions that will cause the command to skip its validate and execution * this method should be overridden with specific logic for each command * using the result should be done with shouldSkipCommandExecutionCached method that cache the result in the command * @return true if the command should not execute any logic and should not return errors to the user */ protected boolean shouldSkipCommandExecution() { return false; } protected Snapshot getActiveSnapshot() { return snapshotDao.get(getVm().getId(), SnapshotType.ACTIVE); } /** Helper method for failing validate on invalid VM status */ protected boolean failVmStatusIllegal() { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_STATUS_ILLEGAL, LocalizedVmStatus.from(getVm().getStatus())); } protected void unlockSnapshot(Guid snapshotId) { // if we got here, the target snapshot exists for sure snapshotDao.updateStatus(snapshotId, Snapshot.SnapshotStatus.OK); } protected VmDeviceUtils getVmDeviceUtils() { return vmDeviceUtils; } protected SnapshotsManager getSnapshotsManager() { return snapshotsManager; } protected boolean validateQuota(Guid quotaId) { if (quotaId == null || Guid.Empty.equals(quotaId)) { // QuotaManager will use default quota if the id is null or empty return true; } Quota quota = quotaDao.getById(quotaId); if (quota == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_QUOTA_NOT_EXIST); } if (!Objects.equals(quota.getStoragePoolId(), getStoragePoolId())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_QUOTA_IS_NOT_VALID); } return true; } protected String cdPathWindowsToLinux(String windowsPath, Guid storagePoolId, Guid vdsId) { if (StringUtils.isEmpty(windowsPath)) { return ""; // empty string is used for 'eject' } return cdPathWindowsToLinux(windowsPath, getIsoPrefix(storagePoolId, vdsId)); } private String cdPathWindowsToLinux(String windowsPath, String isoPrefix) { String fileName = new File(windowsPath).getName(); return String.format("%1$s/%2$s", isoPrefix, fileName); } protected boolean removeVmLease(Guid leaseStorageDomainId, Guid vmId) { if (leaseStorageDomainId == null) { return true; } return runInternalActionWithTasksContext( VdcActionType.RemoveVmLease, new VmLeaseParameters(getStoragePoolId(), leaseStorageDomainId, vmId) ).getSucceeded(); } protected boolean shouldAddLease(VmStatic vm) { return vm.getLeaseStorageDomainId() != null; } protected boolean validateLeaseStorageDomain(Guid leaseStorageDomainId) { StorageDomain domain = storageDomainDao.getForStoragePool(leaseStorageDomainId, getStoragePoolId()); StorageDomainValidator validator = new StorageDomainValidator(domain); return validate(validator.isDomainExistAndActive()) && validate(validator.isDataDomain()); } protected boolean addVmLease(Guid leaseStorageDomainId, Guid vmId) { if (leaseStorageDomainId == null) { return true; } return runInternalActionWithTasksContext( VdcActionType.AddVmLease, new VmLeaseParameters(getStoragePoolId(), leaseStorageDomainId, vmId) ).getSucceeded(); } protected String getIsoPrefix(Guid storagePoolId, Guid vdsId) { return (String) runVdsCommand(VDSCommandType.IsoPrefix, new VdsAndPoolIDVDSParametersBase(vdsId, storagePoolId)).getReturnValue(); } }