package org.ovirt.engine.core.bll.validator;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_ACTIVE;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_NOT_SHAREABLE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.ovirt.engine.core.bll.ValidationResult;
import org.ovirt.engine.core.bll.VmCommand;
import org.ovirt.engine.core.bll.hostdev.HostDeviceManager;
import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter;
import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.VdcActionUtils;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.businessentities.MigrationSupport;
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.VmBase;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.businessentities.network.VmInterfaceType;
import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface;
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.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.compat.Guid;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.dao.DiskDao;
import org.ovirt.engine.core.dao.DiskVmElementDao;
import org.ovirt.engine.core.di.Injector;
import org.ovirt.engine.core.utils.ReplacementUtils;
/** A Validator for various VM validate needs */
public class VmValidator {
private VM vm;
public VmValidator(VM vm) {
this.vm = vm;
}
/** @return Validation result that indicates if the VM is during migration or not. */
public ValidationResult vmNotDuringMigration() {
if (vm.getStatus() == VMStatus.MigratingFrom || vm.getStatus() == VMStatus.MigratingTo) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MIGRATION_IN_PROGRESS);
}
return ValidationResult.VALID;
}
/** @return Validation result that indicates if the VM is down or not. */
public ValidationResult vmDown() {
if (!vm.isDown()) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN);
}
return ValidationResult.VALID;
}
/** @return Validation result that indicates if the VM is qualified to have its snapshots merged. */
public ValidationResult vmQualifiedForSnapshotMerge() {
if (!vm.isQualifiedForSnapshotMerge()) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN_OR_UP,
String.format("$VmName %s", vm.getName()));
}
return ValidationResult.VALID;
}
public ValidationResult vmNotLocked() {
if (vm.getStatus() == VMStatus.ImageLocked) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_IS_LOCKED);
}
return ValidationResult.VALID;
}
public ValidationResult vmNotSavingRestoring() {
if (vm.getStatus().isHibernating() || vm.getStatus() == VMStatus.RestoringState) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_IS_SAVING_RESTORING);
}
return ValidationResult.VALID;
}
public ValidationResult validateVmStatusUsingMatrix(VdcActionType actionType) {
if (!VdcActionUtils.canExecute(Arrays.asList(vm), VM.class,
actionType)) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_STATUS_ILLEGAL,
LocalizedVmStatus.from(vm.getStatus()));
}
return ValidationResult.VALID;
}
public ValidationResult vmNotIlegal() {
if (vm.getStatus() == VMStatus.ImageIllegal) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_IMAGE_IS_ILLEGAL);
}
return ValidationResult.VALID;
}
public ValidationResult vmNotRunningStateless() {
if (DbFacade.getInstance().getSnapshotDao().exists(vm.getId(), SnapshotType.STATELESS)) {
EngineMessage message = vm.isRunning() ? EngineMessage.ACTION_TYPE_FAILED_VM_RUNNING_STATELESS :
EngineMessage.ACTION_TYPE_FAILED_VM_HAS_STATELESS_SNAPSHOT_LEFTOVER;
return new ValidationResult(message);
}
return ValidationResult.VALID;
}
/**
* @return ValidationResult indicating whether snapshots of disks are attached to other vms.
*/
public ValidationResult vmNotHavingDeviceSnapshotsAttachedToOtherVms(boolean onlyPlugged) {
List<Disk> vmDisks = getDbFacade().getDiskDao().getAllForVm(vm.getId());
ValidationResult result =
new DiskImagesValidator(DisksFilter.filterImageDisks(vmDisks, ONLY_NOT_SHAREABLE, ONLY_ACTIVE))
.diskImagesSnapshotsNotAttachedToOtherVms(onlyPlugged);
if (result != ValidationResult.VALID) {
return result;
}
return ValidationResult.VALID;
}
/**
* Determines whether VirtIO-SCSI can be disabled for the VM
* (can be disabled when no disk uses VirtIO-SCSI interface).
*/
public ValidationResult canDisableVirtioScsi(Collection<? extends Disk> vmDisks) {
if (vmDisks == null) {
vmDisks = getDiskDao().getAllForVm(vm.getId(), true);
populateDisksWithVmData(vmDisks, vm.getId());
}
boolean isVirtioScsiDiskExist =
vmDisks.stream().anyMatch(d -> d.getDiskVmElementForVm(vm.getId()).getDiskInterface() == DiskInterface.VirtIO_SCSI);
if (isVirtioScsiDiskExist) {
return new ValidationResult(EngineMessage.CANNOT_DISABLE_VIRTIO_SCSI_PLUGGED_DISKS);
}
return ValidationResult.VALID;
}
private void populateDisksWithVmData(Collection<? extends Disk> disks, Guid vmId) {
for (Disk disk : disks) {
DiskVmElement dve = getDiskVmElementDao().get(new VmDeviceId(disk.getId(), vmId));
disk.setDiskVmElements(Collections.singletonList(dve));
}
}
public DiskDao getDiskDao() {
return getDbFacade().getDiskDao();
}
public DbFacade getDbFacade() {
return DbFacade.getInstance();
}
/**
* @return ValidationResult indicating whether a vm contains non-migratable, plugged, passthrough vnics
*/
public ValidationResult allPassthroughVnicsMigratable() {
List<VmNetworkInterface> vnics =
getDbFacade().getVmNetworkInterfaceDao().getAllForVm(vm.getId());
List<String> nonMigratablePassthroughVnicNames = vnics.stream()
.filter(isVnicMigratable(vm).negate())
.map(VmNic::getName)
.collect(Collectors.toList());
if (!nonMigratablePassthroughVnicNames.isEmpty()) {
Collection<String> replacements =
ReplacementUtils.replaceWith("interfaces", nonMigratablePassthroughVnicNames);
replacements.add(String.format("$vmName %s", vm.getName()));
return new ValidationResult(
EngineMessage.ACTION_TYPE_FAILED_MIGRATION_OF_NON_MIGRATABLE_PASSTHROUGH_VNICS_IS_NOT_SUPPORTED,
replacements);
}
return ValidationResult.VALID;
}
private Predicate<? super VmNetworkInterface> isVnicMigratable(VM vm) {
return vnic -> !vnic.isPassthrough() || !vnic.isPlugged()
|| (FeatureSupported.sriovHotPlugSupported(vm.getClusterCompatibilityVersion())
&& getVnicProfile(vnic).isMigratable());
}
private VnicProfile getVnicProfile(VmNic vnic) {
VnicProfile profile = getDbFacade().getVnicProfileDao().get(vnic.getVnicProfileId());
return profile;
}
/**
* Checks whether VM uses lun with scsi reservation true.
* @return If scsi lun with scsi reservation is plugged to VM
*/
public ValidationResult isVmPluggedDiskNotUsingScsiReservation() {
List<DiskVmElement> dves = getDbFacade().getDiskVmElementDao().getAllPluggedToVm(vm.getId());
if (dves.stream().anyMatch(dve -> dve.isUsingScsiReservation())) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_USES_SCSI_RESERVATION,
String.format("$VmName %s", vm.getName()));
}
return ValidationResult.VALID;
}
public ValidationResult vmNotHavingPciPassthroughDevices() {
if (getHostDeviceManager().checkVmNeedsPciDevices(vm.getId())) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_HAS_ATTACHED_PCI_HOST_DEVICES);
}
return ValidationResult.VALID;
}
private HostDeviceManager getHostDeviceManager() {
return Injector.get(HostDeviceManager.class);
}
public ValidationResult isPinnedVmRunningOnDedicatedHost(VM recentVm, VmStatic paramVm){
boolean isPinned = paramVm.getMigrationSupport() == MigrationSupport.PINNED_TO_HOST;
Guid vdsId = recentVm.getRunOnVds();
List<Guid> hostList = paramVm.getDedicatedVmForVdsList();
// If hostList is empty -> all hosts are allowed
if (isPinned && vdsId != null && !hostList.isEmpty() && !hostList.contains(vdsId)){
// VM is NOT running on a dedicated host
// fail with error message
String hostName = String.format("$hostName %1$s", recentVm.getRunOnVdsName());
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_PINNED_VM_NOT_RUNNING_ON_DEDICATED_HOST, hostName, hostName);
}
return ValidationResult.VALID;
}
public ValidationResult isVmExists() {
if (vm == null) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND);
}
return ValidationResult.VALID;
}
/**
* This method checks that with the given parameters, the max PCI and IDE limits defined are not passed.
*/
public static ValidationResult checkPciAndIdeLimit(
int osId,
Version clusterVersion,
int monitorsNumber,
List<? extends VmNic> interfaces,
List<DiskVmElement> diskVmElements,
boolean virtioScsiEnabled,
boolean hasWatchdog,
boolean isBalloonEnabled,
boolean isSoundDeviceEnabled) {
// this adds: monitors + 2 * (interfaces with type rtl_pv) + (all other
// interfaces) + (all disks that are not IDE)
int pciInUse = monitorsNumber;
for (VmNic a : interfaces) {
if (a.getType() != null && VmInterfaceType.forValue(a.getType()) == VmInterfaceType.rtl8139_pv) {
pciInUse += 2;
} else if (a.getType() != null && VmInterfaceType.forValue(a.getType()) == VmInterfaceType.spaprVlan) {
// Do not count sPAPR VLAN devices since they are not PCI
} else {
pciInUse += 1;
}
}
pciInUse += diskVmElements.stream().filter(dve -> dve.getDiskInterface() == DiskInterface.VirtIO).count();
// VirtIO SCSI controller requires one PCI slot
pciInUse += virtioScsiEnabled ? 1 : 0;
// VmWatchdog controller requires one PCI slot
pciInUse += hasWatchdog ? 1 : 0;
// Balloon controller requires one PCI slot
pciInUse += isBalloonEnabled ? 1 : 0;
// Sound device controller requires one PCI slot
pciInUse += isSoundDeviceEnabled ? 1 : 0;
OsRepository osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class);
int maxPciSlots = osRepository.getMaxPciDevices(osId, clusterVersion);
ArrayList<EngineMessage> messages = new ArrayList<>();
if (pciInUse > maxPciSlots) {
messages.add(EngineMessage.ACTION_TYPE_FAILED_EXCEEDED_MAX_PCI_SLOTS);
}
else if (VmCommand.MAX_IDE_SLOTS < diskVmElements.stream().filter(a -> a.getDiskInterface() == DiskInterface.IDE).count()) {
messages.add(EngineMessage.ACTION_TYPE_FAILED_EXCEEDED_MAX_IDE_SLOTS);
}
else if (VmCommand.MAX_VIRTIO_SCSI_DISKS <
diskVmElements.stream().filter(a -> a.getDiskInterface() == DiskInterface.VirtIO_SCSI).count()) {
messages.add(EngineMessage.ACTION_TYPE_FAILED_EXCEEDED_MAX_VIRTIO_SCSI_DISKS);
}
else if (VmCommand.MAX_SPAPR_SCSI_DISKS <
diskVmElements.stream().filter(a -> a.getDiskInterface() == DiskInterface.SPAPR_VSCSI).count()) {
messages.add(EngineMessage.ACTION_TYPE_FAILED_EXCEEDED_MAX_SPAPR_VSCSI_DISKS);
}
if (!messages.isEmpty()) {
return new ValidationResult(messages);
}
return ValidationResult.VALID;
}
public static ValidationResult validateCpuSockets(VmBase vmBase, Version compatibilityVersion) {
int num_of_sockets = vmBase.getNumOfSockets();
int cpu_per_socket = vmBase.getCpuPerSocket();
int threadsPerCpu = vmBase.getThreadsPerCpu();
String version = compatibilityVersion.toString();
if ((num_of_sockets * cpu_per_socket * threadsPerCpu) >
Config.<Integer> getValue(ConfigValues.MaxNumOfVmCpus, version)) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAX_NUM_CPU);
}
if (num_of_sockets > Config.<Integer> getValue(ConfigValues.MaxNumOfVmSockets, version)) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAX_NUM_SOCKETS);
}
if (cpu_per_socket > Config.<Integer> getValue(ConfigValues.MaxNumOfCpuPerSocket, version)) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAX_CPU_PER_SOCKET);
}
if (threadsPerCpu > Config.<Integer> getValue(ConfigValues.MaxNumOfThreadsPerCpu, version)) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAX_THREADS_PER_CPU);
}
if (cpu_per_socket < 1) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MIN_CPU_PER_SOCKET);
}
if (num_of_sockets < 1) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MIN_NUM_SOCKETS);
}
if (threadsPerCpu < 1) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MIN_THREADS_PER_CPU);
}
return ValidationResult.VALID;
}
public DiskVmElementDao getDiskVmElementDao() {
return DbFacade.getInstance().getDiskVmElementDao();
}
}