package org.ovirt.engine.core.vdsbroker.builder.vminfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.businessentities.ChipsetType;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.DisplayType;
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.NumaTuneMode;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VdsNumaNode;
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.VmNumaNode;
import org.ovirt.engine.core.common.businessentities.VmPayload;
import org.ovirt.engine.core.common.businessentities.network.Network;
import org.ovirt.engine.core.common.businessentities.network.VmInterfaceType;
import org.ovirt.engine.core.common.businessentities.network.VmNic;
import org.ovirt.engine.core.common.businessentities.qos.StorageQos;
import org.ovirt.engine.core.common.businessentities.storage.CinderDisk;
import org.ovirt.engine.core.common.businessentities.storage.Disk;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskInterface;
import org.ovirt.engine.core.common.businessentities.storage.DiskStorageType;
import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement;
import org.ovirt.engine.core.common.businessentities.storage.LunDisk;
import org.ovirt.engine.core.common.businessentities.storage.PropagateErrors;
import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.osinfo.OsRepository;
import org.ovirt.engine.core.common.utils.SimpleDependencyInjector;
import org.ovirt.engine.core.common.utils.VmCpuCountHelper;
import org.ovirt.engine.core.common.utils.VmDeviceCommonUtils;
import org.ovirt.engine.core.common.utils.VmDeviceType;
import org.ovirt.engine.core.common.utils.customprop.VmPropertiesUtils;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.VdsNumaNodeDao;
import org.ovirt.engine.core.dao.VmDeviceDao;
import org.ovirt.engine.core.dao.VmNumaNodeDao;
import org.ovirt.engine.core.dao.network.NetworkDao;
import org.ovirt.engine.core.utils.archstrategy.ArchStrategyFactory;
import org.ovirt.engine.core.utils.collections.ComparatorUtils;
import org.ovirt.engine.core.vdsbroker.architecture.CreateAdditionalControllers;
import org.ovirt.engine.core.vdsbroker.architecture.GetBootableDiskIndex;
import org.ovirt.engine.core.vdsbroker.architecture.GetControllerIndices;
import org.ovirt.engine.core.vdsbroker.vdsbroker.NumaSettingFactory;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VmSerialNumberBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class VmInfoBuilderImpl implements VmInfoBuilder {
private static final Logger log = LoggerFactory.getLogger(VmInfoBuilderImpl.class);
private static final String DEVICES = "devices";
private static final String USB_BUS = "usb";
private final VmInfoBuildUtils vmInfoBuildUtils;
private final VdsNumaNodeDao vdsNumaNodeDao;
private final VmNumaNodeDao vmNumaNodeDao;
private final VmDeviceDao vmDeviceDao;
private final ClusterDao clusterDao;
private final List<Map<String, Object>> devices = new ArrayList<>();
private final Map<String, Object> createInfo;
private final VM vm;
private OsRepository osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class);
private List<VmDevice> bootableDevices = null;
private Guid vdsId;
private Cluster cluster;
private int numOfReservedScsiIndexes = 0;
VmInfoBuilderImpl(
VM vm,
Guid vdsId,
Map<String, Object> createInfo,
ClusterDao clusterDao,
NetworkDao networkDao,
VdsNumaNodeDao vdsNumaNodeDao,
VmDeviceDao vmDeviceDao,
VmNumaNodeDao vmNumaNodeDao,
VmInfoBuildUtils vmInfoBuildUtils) {
this.clusterDao = Objects.requireNonNull(clusterDao);
this.vdsNumaNodeDao = Objects.requireNonNull(vdsNumaNodeDao);
this.vmDeviceDao = Objects.requireNonNull(vmDeviceDao);
this.vmNumaNodeDao = Objects.requireNonNull(vmNumaNodeDao);
this.vmInfoBuildUtils = Objects.requireNonNull(vmInfoBuildUtils);
this.vdsId = vdsId;
this.vm = vm;
this.createInfo = createInfo;
bootableDevices = new ArrayList<>();
}
@Override
public void buildVmVideoCards() {
boolean videoCardOverridden = vm.isRunOnce() && vm.getDefaultDisplayType() != null;
if (videoCardOverridden) {
if (vm.getDefaultDisplayType() == DisplayType.none) {
return;
}
buildVmVideoDeviceOverridden();
} else {
buildVmVideoDevicesFromDb();
}
}
@Override
public void buildVmGraphicsDevices() {
boolean graphicsOverridden = vm.isRunOnce() && vm.getGraphicsInfos() != null && !vm.getGraphicsInfos().isEmpty();
boolean isHeadlessMode = vm.getDefaultDisplayType() == DisplayType.none;
if (isHeadlessMode) {
return;
}
Map<GraphicsType, GraphicsInfo> infos = vm.getGraphicsInfos();
Map<String, Object> specParamsFromVm = null;
if (infos != null) {
specParamsFromVm = new HashMap<>();
vmInfoBuildUtils.addVmGraphicsOptions(infos, specParamsFromVm, vm);
}
if (graphicsOverridden) {
buildVmGraphicsDevicesOverridden(infos, specParamsFromVm);
} else {
buildVmGraphicsDevicesFromDb(specParamsFromVm);
}
}
@Override
public void buildVmCD(VmPayload vmPayload) {
boolean hasPayload = vmPayload != null && vmPayload.getDeviceType() == VmDeviceType.CDROM;
// check if we have payload CD
if (hasPayload) {
Map<String, Object> struct = vmInfoBuildUtils.buildCdDetails(vmPayload, vm);
addDevice(struct, vmPayload, "");
}
// check first if CD was given as a RunOnce parameter
if (vm.isRunOnce() && !StringUtils.isEmpty(vm.getCdPath())) {
VmDevice vmDevice =
new VmDevice(new VmDeviceId(Guid.newGuid(), vm.getId()),
VmDeviceGeneralType.DISK,
VmDeviceType.CDROM.getName(),
"",
null,
true,
true,
true,
"",
null,
null,
null);
Map<String, Object> struct = vmInfoBuildUtils.buildCdDetails(vmDevice, vm);
addDevice(struct, vmDevice, vm.getCdPath());
} else {
// get vm device for this CD from DB
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.DISK,
VmDeviceType.CDROM.getName());
for (VmDevice vmDevice : vmDevices) {
// skip unmanaged devices (handled separately)
if (!vmDevice.isManaged()) {
continue;
}
// The Payload is loaded in via RunVmCommand to VM.
// Payload and its device are handled at the beginning of
// the method, so no need to add the device again,
if (VmPayload.isPayload(vmDevice.getSpecParams())) {
continue;
}
String cdPath = vm.getCdPath();
Map<String, Object> struct = vmInfoBuildUtils.buildCdDetails(vmDevice, vm);
vmInfoBuildUtils.addAddress(vmDevice, struct);
addDevice(struct, vmDevice, cdPath == null ? "" : cdPath);
}
}
numOfReservedScsiIndexes++;
}
@Override
public void buildVmFloppy(VmPayload vmPayload) {
// check if we have payload Floppy
boolean hasPayload = vmPayload != null && vmPayload.getDeviceType() == VmDeviceType.FLOPPY;
if (hasPayload) {
Map<String, Object>struct = vmInfoBuildUtils.buildFloppyDetails(vmPayload);
addDevice(struct, vmPayload, "");
}
// check first if Floppy was given as a parameter
else if (vm.isRunOnce() && !StringUtils.isEmpty(vm.getFloppyPath())) {
VmDevice vmDevice = vmInfoBuildUtils.createFloppyDevice(vm);
Map<String, Object> struct = vmInfoBuildUtils.buildFloppyDetails(vmDevice);
addDevice(struct, vmDevice, vm.getFloppyPath());
} else {
// get vm device for this Floppy from DB
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.DISK,
VmDeviceType.FLOPPY.getName());
for (VmDevice vmDevice : vmDevices) {
// skip unmanaged devices (handled separately)
if (!vmDevice.isManaged()) {
continue;
}
// more than one device mean that we have payload and should use it
// instead of the blank cd
if (!VmPayload.isPayload(vmDevice.getSpecParams()) && vmDevices.size() > 1) {
continue;
}
// The Payload is loaded in via RunVmCommand to VM.Payload
// and its handled at the beginning of the method, so no
// need to add the device again
if (VmPayload.isPayload(vmDevice.getSpecParams())) {
continue;
}
String file = vm.getFloppyPath();
Map<String, Object> struct = vmInfoBuildUtils.buildFloppyDetails(vmDevice);
addDevice(struct, vmDevice, file);
}
}
}
@Override
public void buildVmDrives() {
boolean bootDiskFound = false;
List<Disk> disks = vmInfoBuildUtils.getSortedDisks(vm);
Map<Integer, Map<VmDevice, Integer>> vmDeviceVirtioScsiUnitMap =
vmInfoBuildUtils.getVmDeviceUnitMapForVirtioScsiDisks(vm);
Map<Integer, Map<VmDevice, Integer>> vmDeviceSpaprVscsiUnitMap =
vmInfoBuildUtils.getVmDeviceUnitMapForSpaprScsiDisks(vm);
Map<DiskInterface, Integer> controllerIndexMap =
ArchStrategyFactory.getStrategy(vm.getClusterArch()).run(new GetControllerIndices()).returnValue();
int virtioScsiIndex = controllerIndexMap.get(DiskInterface.VirtIO_SCSI);
int sPaprVscsiIndex = controllerIndexMap.get(DiskInterface.SPAPR_VSCSI);
Map<Guid, StorageQos> qosCache = new HashMap<>();
int pinnedDriveIndex = 0;
for (Disk disk : disks) {
Map<String, Object> struct = new HashMap<>();
// get vm device for this disk from DB
VmDevice vmDevice = vmInfoBuildUtils.getVmDeviceByDiskId(disk.getId(), vm.getId());
DiskVmElement dve = disk.getDiskVmElementForVm(vm.getId());
// skip unmanaged devices (handled separately)
if (!vmDevice.isManaged()) {
continue;
}
if (vmDevice.isPlugged()) {
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
switch (dve.getDiskInterface()) {
case IDE:
struct.put(VdsProperties.INTERFACE, VdsProperties.Ide);
break;
case VirtIO:
struct.put(VdsProperties.INTERFACE, VdsProperties.Virtio);
pinnedDriveIndex = pinToIoThreads(vmDevice, pinnedDriveIndex);
break;
case VirtIO_SCSI:
setupVirtioScsiDisk(vmDeviceVirtioScsiUnitMap, virtioScsiIndex, disk, struct, vmDevice);
break;
case SPAPR_VSCSI:
struct.put(VdsProperties.INTERFACE, VdsProperties.Scsi);
if (StringUtils.isEmpty(vmDevice.getAddress())) {
// Explicitly define device's address if missing
Integer unit = vmDeviceSpaprVscsiUnitMap.get(sPaprVscsiIndex).get(vmDevice);
vmDevice.setAddress(
vmInfoBuildUtils.createAddressForScsiDisk(sPaprVscsiIndex, unit).toString());
}
break;
default:
logUnsupportedInterfaceType();
break;
}
// Insure that boot disk is created first
if (!bootDiskFound && dve.isBoot()) {
bootDiskFound = true;
struct.put(VdsProperties.Index, getBootableDiskIndex(disk));
}
if (FeatureSupported.passDiscardSupported(vm.getCompatibilityVersion())) {
struct.put(VdsProperties.DISCARD, dve.isPassDiscard());
}
vmInfoBuildUtils.addAddress(vmDevice, struct);
switch (disk.getDiskStorageType()) {
case IMAGE:
DiskImage diskImage = (DiskImage) disk;
struct.put(VdsProperties.PoolId, diskImage.getStoragePoolId().toString());
struct.put(VdsProperties.DomainId, diskImage.getStorageIds().get(0).toString());
struct.put(VdsProperties.ImageId, diskImage.getId().toString());
struct.put(VdsProperties.VolumeId, diskImage.getImageId().toString());
struct.put(VdsProperties.Format, diskImage.getVolumeFormat()
.toString()
.toLowerCase());
struct.put(VdsProperties.PropagateErrors, disk.getPropagateErrors()
.toString()
.toLowerCase());
if (!qosCache.containsKey(diskImage.getDiskProfileId())) {
qosCache.put(diskImage.getDiskProfileId(), vmInfoBuildUtils.loadStorageQos(diskImage));
}
vmInfoBuildUtils.handleIoTune(vmDevice, qosCache.get(diskImage.getDiskProfileId()));
break;
case LUN:
LunDisk lunDisk = (LunDisk) disk;
struct.put(VdsProperties.Guid, lunDisk.getLun().getLUNId());
struct.put(VdsProperties.Format, VolumeFormat.RAW.toString().toLowerCase());
struct.put(VdsProperties.PropagateErrors, PropagateErrors.Off.toString().toLowerCase());
break;
case CINDER:
vmInfoBuildUtils.buildCinderDisk((CinderDisk) disk, struct);
break;
}
struct.put(VdsProperties.Shareable,
(vmDevice.getSnapshotId() != null)
? VdsProperties.Transient : String.valueOf(disk.isShareable()));
struct.put(VdsProperties.Optional, Boolean.FALSE.toString());
struct.put(VdsProperties.ReadOnly, String.valueOf(vmDevice.getReadOnly()));
struct.put(VdsProperties.SpecParams, vmDevice.getSpecParams());
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
devices.add(struct);
bootableDevices.add(vmDevice);
}
}
ArchStrategyFactory.getStrategy(vm.getClusterArch()).run(new CreateAdditionalControllers(devices));
}
public void setupVirtioScsiDisk(Map<Integer, Map<VmDevice, Integer>> vmDeviceVirtioScsiUnitMap, int virtioScsiIndex, Disk disk, Map<String, Object> struct, VmDevice vmDevice) {
struct.put(VdsProperties.INTERFACE, VdsProperties.Scsi);
// If SCSI pass-through is enabled (DirectLUN disk and SGIO is defined),
// set device type as 'lun' (instead of 'disk') and set the specified SGIO.
if (disk.getDiskStorageType() == DiskStorageType.LUN && disk.isScsiPassthrough()) {
struct.put(VdsProperties.Device, VmDeviceType.LUN.getName());
struct.put(VdsProperties.Sgio, disk.getSgio().toString().toLowerCase());
}
VmDevice deviceFromMap = vmDevice;
int controllerId = virtioScsiIndex;
Integer unit = null;
for (Entry<Integer, Map<VmDevice, Integer>> controllerToDevices : vmDeviceVirtioScsiUnitMap.entrySet()) {
Optional<VmDevice> maybeDeviceFromMap = controllerToDevices.getValue().keySet().stream()
.filter(d -> d.getId().equals(vmDevice.getId()))
.findFirst();
if (maybeDeviceFromMap.isPresent()) {
controllerId = controllerToDevices.getKey();
deviceFromMap = maybeDeviceFromMap.get();
unit = controllerToDevices.getValue().get(deviceFromMap);
break;
}
}
if (StringUtils.isEmpty(deviceFromMap.getAddress())) {
// by default use default controller
if (unit == null) {
// should never get here, but for safety having this fallback and generating a new unit id
unit = vmInfoBuildUtils.getAvailableUnitForScsiDisk(
vmDeviceVirtioScsiUnitMap.get(controllerId),
false
);
log.debug("The unit was null for disk '{}' on controller '{}', generating a new one '{}'", disk.getId(), controllerId, unit);
}
vmDevice.setAddress(
vmInfoBuildUtils.createAddressForScsiDisk(controllerId, unit).toString());
}
}
private int pinToIoThreads(VmDevice vmDevice, int pinnedDriveIndex) {
if (vm.getNumOfIoThreads() != 0) {
// simple round robin e.g. for 2 threads and 4 disks it will be pinned like this:
// disk 0 -> iothread 1
// disk 1 -> iothread 2
// disk 2 -> iothread 1
// disk 3 -> iothread 2
int pinTo = pinnedDriveIndex % vm.getNumOfIoThreads() + 1;
pinnedDriveIndex++;
vmDevice.getSpecParams().put(VdsProperties.pinToIoThread, pinTo);
}
return pinnedDriveIndex;
}
@Override
public void buildVmNetworkInterfaces(Map<Guid, String> passthroughVnicToVfMap) {
Map<VmDeviceId, VmDevice> devicesByDeviceId =
Entities.businessEntitiesById(
vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.INTERFACE,
VmDeviceType.BRIDGE.getName()));
devicesByDeviceId.putAll(Entities.businessEntitiesById(
vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.INTERFACE,
VmDeviceType.HOST_DEVICE.getName())));
for (VmNic vmInterface : vm.getInterfaces()) {
// get vm device for this nic from DB
VmDevice vmDevice =
devicesByDeviceId.get(new VmDeviceId(vmInterface.getId(), vmInterface.getVmId()));
if (vmDevice != null && vmDevice.isManaged() && vmDevice.isPlugged()) {
Map<String, Object> struct = new HashMap<>();
VmInterfaceType ifaceType = VmInterfaceType.rtl8139;
if (vmInterface.getType() != null) {
ifaceType = VmInterfaceType.forValue(vmInterface.getType());
}
if (vmInterface.isPassthrough()) {
String vfDeviceName = passthroughVnicToVfMap.get(vmInterface.getId());
vmInfoBuildUtils.addNetworkVirtualFunctionProperties(struct,
vmInterface,
vmDevice,
vfDeviceName,
vm);
} else {
addNetworkInterfaceProperties(struct,
vmInterface,
vmDevice,
vmInfoBuildUtils.evaluateInterfaceType(ifaceType, vm.getHasAgent()));
}
devices.add(struct);
bootableDevices.add(vmDevice);
}
}
}
@Override
public void buildVmSoundDevices() {
buildVmDevicesFromDb(VmDeviceGeneralType.SOUND, true, null);
}
@Override
public void buildVmConsoleDevice() {
buildVmDevicesFromDb(VmDeviceGeneralType.CONSOLE, false, null);
}
@Override
public void buildUnmanagedDevices() {
@SuppressWarnings("unchecked")
Map<String, String> customMap =
(Map<String, String>) createInfo.getOrDefault(VdsProperties.Custom, new HashMap<>());
List<VmDevice> vmDevices = vmDeviceDao.getUnmanagedDevicesByVmId(vm.getId());
if (!vmDevices.isEmpty()) {
StringBuilder id = new StringBuilder();
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
id.append(VdsProperties.Device);
id.append("_");
id.append(vmDevice.getDeviceId());
if (VmDeviceCommonUtils.isInWhiteList(vmDevice.getType(), vmDevice.getDevice())) {
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
vmInfoBuildUtils.addAddress(vmDevice, struct);
struct.put(VdsProperties.SpecParams, vmDevice.getSpecParams());
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
devices.add(struct);
} else {
customMap.put(id.toString(), vmDevice.toString());
}
}
}
createInfo.put(VdsProperties.Custom, customMap);
createInfo.put(DEVICES, devices);
}
@Override
public void buildVmBootSequence() {
// recalculate boot order from source devices and set it to target devices
VmDeviceCommonUtils.updateVmDevicesBootOrder(
vm.getBootSequence(),
bootableDevices,
vm.getInterfaces(),
VmDeviceCommonUtils.extractDiskVmElements(vm));
for (VmDevice vmDevice : bootableDevices) {
for (Map<String, Object> struct : devices) {
String deviceId = (String) struct.get(VdsProperties.DeviceId);
if (deviceId != null && deviceId.equals(vmDevice.getDeviceId().toString())) {
if (vmDevice.getBootOrder() > 0) {
struct.put(VdsProperties.BootOrder, String.valueOf(vmDevice.getBootOrder()));
}
break;
}
}
}
}
@Override
public void buildSysprepVmPayload(String sysPrepContent) {
VmDevice vmDevice = vmInfoBuildUtils.createSysprepPayloadDevice(sysPrepContent, vm);
Map<String, Object>struct = vmInfoBuildUtils.buildFloppyDetails(vmDevice);
addDevice(struct, vmDevice, vm.getFloppyPath());
}
@Override
public void buildCloudInitVmPayload(Map<String, byte[]> cloudInitContent) {
VmDevice vmDevice = vmInfoBuildUtils.createCloudInitPayloadDevice(cloudInitContent, vm);
Map<String, Object> struct = vmInfoBuildUtils.buildCdDetails(vmDevice, vm);
addDevice(struct, vmDevice, "");
}
@Override
public void buildVmUsbDevices() {
buildVmUsbControllers();
buildVmUsbSlots();
buildSmartcardDevice();
}
@Override
public void buildVmMemoryBalloon() {
// get vm device for this Balloon from DB
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.BALLOON,
VmDeviceType.MEMBALLOON.getName());
for (VmDevice vmDevice : vmDevices) {
// skip unamanged devices (handled separtely)
if (!vmDevice.isManaged()) {
continue;
}
addMemBalloonDevice(vmDevice);
break; // only one memory balloon should exist
}
}
@Override
public void buildVmWatchdog() {
List<VmDevice> watchdogs = vmDeviceDao.getVmDeviceByVmIdAndType(vm.getId(), VmDeviceGeneralType.WATCHDOG);
for (VmDevice watchdog : watchdogs) {
Map<String, Object> watchdogFromRpc = new HashMap<>();
watchdogFromRpc.put(VdsProperties.Type, VmDeviceGeneralType.WATCHDOG.getValue());
watchdogFromRpc.put(VdsProperties.Device, watchdog.getDevice());
Map<String, Object> specParams = watchdog.getSpecParams();
if (specParams == null) {
specParams = new HashMap<>();
}
watchdogFromRpc.put(VdsProperties.SpecParams, specParams);
addDevice(watchdogFromRpc, watchdog, null);
}
}
@Override
public void buildVmVirtioScsi() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.CONTROLLER,
VmDeviceType.VIRTIOSCSI.getName());
Map<DiskInterface, Integer> controllerIndexMap =
ArchStrategyFactory.getStrategy(vm.getClusterArch()).run(new GetControllerIndices()).returnValue();
int virtioScsiIndex = controllerIndexMap.get(DiskInterface.VirtIO_SCSI);
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, VmDeviceGeneralType.CONTROLLER.getValue());
struct.put(VdsProperties.Device, VdsProperties.Scsi);
struct.put(VdsProperties.Model, VdsProperties.VirtioScsi);
struct.put(VdsProperties.Index, Integer.toString(virtioScsiIndex));
vmInfoBuildUtils.addAddress(vmDevice, struct);
virtioScsiIndex++;
addDevice(struct, vmDevice, null);
}
}
@Override
public void buildVmVirtioSerial() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.CONTROLLER,
VmDeviceType.VIRTIOSERIAL.getName());
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, VmDeviceGeneralType.CONTROLLER.getValue());
struct.put(VdsProperties.Device, VdsProperties.VirtioSerial);
vmInfoBuildUtils.addAddress(vmDevice, struct);
addDevice(struct, vmDevice, null);
}
}
@Override
public void buildVmRngDevice() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.RNG,
VmDeviceType.VIRTIO.getName());
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, VmDeviceGeneralType.RNG.getValue());
struct.put(VdsProperties.Device, VmDeviceType.VIRTIO.getName());
struct.put(VdsProperties.Model, VdsProperties.Virtio);
struct.put(VdsProperties.SpecParams, vmDevice.getSpecParams());
addDevice(struct, vmDevice, null);
}
}
@Override
public void buildVmNumaProperties() {
addNumaSetting();
}
@Override
public void buildVmProperties(String hibernationVolHandle) {
createInfo.put(VdsProperties.vm_guid, vm.getId().toString());
createInfo.put(VdsProperties.vm_name, vm.getName());
createInfo.put(VdsProperties.mem_size_mb, vm.getVmMemSizeMb());
if (FeatureSupported.hotPlugMemory(vm.getCompatibilityVersion(), vm.getClusterArch())) {
// because QEMU fails if memory and maxMemory are the same
if (vm.getVmMemSizeMb() != vm.getMaxMemorySizeMb()) {
createInfo.put(VdsProperties.maxMemSize, vm.getMaxMemorySizeMb());
}
createInfo.put(VdsProperties.maxMemSlots, Config.getValue(ConfigValues.MaxMemorySlots));
}
createInfo.put(VdsProperties.mem_guaranteed_size_mb, vm.getMinAllocatedMem());
createInfo.put(VdsProperties.smartcardEnabled, Boolean.toString(vm.isSmartcardEnabled()));
createInfo.put(VdsProperties.num_of_cpus, String.valueOf(vm.getNumOfCpus()));
if (vm.getNumOfIoThreads() != 0) {
createInfo.put(VdsProperties.numOfIoThreads, vm.getNumOfIoThreads());
}
if (Config.getValue(ConfigValues.SendSMPOnRunVm)) {
createInfo.put(VdsProperties.cores_per_socket, Integer.toString(vm.getCpuPerSocket()));
createInfo.put(VdsProperties.threads_per_core, Integer.toString(vm.getThreadsPerCpu()));
if (FeatureSupported.supportedInConfig(
ConfigValues.HotPlugCpuSupported,
vm.getCompatibilityVersion(),
vm.getClusterArch())) {
createInfo.put(
VdsProperties.max_number_of_cpus,
calcMaxVCpu().toString());
}
}
addCpuPinning();
if (vm.getEmulatedMachine() != null) {
createInfo.put(VdsProperties.emulatedMachine, vm.getEmulatedMachine());
}
createInfo.put(VdsProperties.kvmEnable, "true");
createInfo.put(VdsProperties.acpiEnable, vm.getAcpiEnable()
.toString()
.toLowerCase());
createInfo.put(VdsProperties.BOOT_MENU_ENABLE, Boolean.toString(vm.isBootMenuEnabled()));
createInfo.put(VdsProperties.Custom,
VmPropertiesUtils.getInstance().getVMProperties(vm.getCompatibilityVersion(),
vm.getStaticData()));
createInfo.put(VdsProperties.vm_type, "kvm"); // "qemu", "kvm"
if (vm.isRunAndPause()) {
createInfo.put(VdsProperties.launch_paused_param, "true");
}
if (vm.isUseHostCpuFlags()) {
createInfo.put(VdsProperties.cpuType,
"hostPassthrough");
} else if (vm.getCpuName() != null) { // uses dynamic vm data which was already updated by runVmCommand
createInfo.put(VdsProperties.cpuType, vm.getCpuName());
}
createInfo.put(VdsProperties.niceLevel,
String.valueOf(vm.getNiceLevel()));
if (vm.getCpuShares() > 0) {
createInfo.put(VdsProperties.cpuShares,
String.valueOf(vm.getCpuShares()));
}
if (!StringUtils.isEmpty(hibernationVolHandle)) {
createInfo.put(VdsProperties.hiberVolHandle, hibernationVolHandle);
}
if (osRepository.isLinux(vm.getVmOsId())) {
createInfo.put(VdsProperties.PitReinjection, "false");
}
if (vm.getGraphicsInfos().size() == 1 && vm.getGraphicsInfos().containsKey(GraphicsType.VNC)) {
createInfo.put(VdsProperties.TabletEnable, "true");
}
createInfo.put(VdsProperties.transparent_huge_pages,
vm.isTransparentHugePages() ? "true" : "false");
if (osRepository.isHypervEnabled(vm.getVmOsId(), vm.getCompatibilityVersion())) {
createInfo.put(VdsProperties.hypervEnable, "true");
}
if (vm.getLeaseStorageDomainId() != null) {
buildVmLease();
}
if (FeatureSupported.isAgentChannelNamingSupported(vm.getCompatibilityVersion())) {
createInfo.put(VdsProperties.agentChannelName, "ovirt-guest-agent.0");
}
}
public void buildVmLease() {
Map<String, Object> device = new HashMap<>();
device.put(VdsProperties.Type, VdsProperties.VmLease);
device.put(VdsProperties.Device, VdsProperties.VmLease);
device.put(VdsProperties.DeviceId, Guid.newGuid().toString());
device.put(VdsProperties.VmLeaseSdId, vm.getLeaseStorageDomainId().toString());
device.put(VdsProperties.VmLeaseId, vm.getId().toString());
devices.add(device);
}
@Override
public void buildVmNetworkCluster() {
// set Display network
Network net = vmInfoBuildUtils.getDisplayNetwork(vm);
if (net != null) {
createInfo.put(VdsProperties.DISPLAY_NETWORK, net.getName());
}
}
@Override
public void buildVmBootOptions() {
// Boot Options
if (!StringUtils.isEmpty(vm.getInitrdUrl())) {
createInfo.put(VdsProperties.InitrdUrl, vm.getInitrdUrl());
}
if (!StringUtils.isEmpty(vm.getKernelUrl())) {
createInfo.put(VdsProperties.KernelUrl, vm.getKernelUrl());
if (!StringUtils.isEmpty(vm.getKernelParams())) {
createInfo.put(VdsProperties.KernelParams,
vm.getKernelParams());
}
}
}
@Override
public void buildVmTimeZone() {
// get vm timezone
createInfo.put(VdsProperties.utc_diff, "" + vmInfoBuildUtils.getVmTimeZone(vm));
}
@Override
public void buildVmSerialNumber() {
new VmSerialNumberBuilder(vm, getCluster(), createInfo).buildVmSerialNumber();
}
@Override
public void buildVmHostDevices() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdAndType(vm.getId(), VmDeviceGeneralType.HOSTDEV);
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, VmDeviceType.HOST_DEVICE.getName());
struct.put(VdsProperties.Device, vmDevice.getDevice());
struct.put(VdsProperties.SpecParams, vmDevice.getSpecParams());
struct.put(VdsProperties.DeviceId, vmDevice.getId().getDeviceId().toString());
vmInfoBuildUtils.addAddress(vmDevice, struct);
devices.add(struct);
}
}
/**
* Creates graphics devices from graphics info - this will override the graphics devices from the db. Used when vm
* is run via run once.
*
* @param graphicsInfos
* - vm graphics
*
* @see #buildVmGraphicsDevicesFromDb(Map)
*/
private void buildVmGraphicsDevicesOverridden(
Map<GraphicsType, GraphicsInfo> graphicsInfos,
Map<String, Object> extraSpecParams) {
final Comparator<GraphicsType> spiceLastComparator =
ComparatorUtils.sortLast(GraphicsType.SPICE);
final List<Entry<GraphicsType, GraphicsInfo>> sortedGraphicsInfos = graphicsInfos.entrySet().stream()
.sorted(Comparator.comparing(Entry::getKey, spiceLastComparator))
.collect(Collectors.toList());
for (Entry<GraphicsType, GraphicsInfo> graphicsInfo : sortedGraphicsInfos) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, VmDeviceGeneralType.GRAPHICS.getValue());
struct.put(VdsProperties.Device, graphicsInfo.getKey().name().toLowerCase());
struct.put(VdsProperties.DeviceId, String.valueOf(Guid.newGuid()));
if (extraSpecParams != null) {
struct.put(VdsProperties.SpecParams, extraSpecParams);
}
devices.add(struct);
}
if (!graphicsInfos.isEmpty() && FeatureSupported.isLegacyDisplaySupported(vm.getCompatibilityVersion())) {
String legacyGraphicsType = (graphicsInfos.size() == 2)
? VdsProperties.QXL
: graphicsTypeToLegacyDisplayType(graphicsInfos.keySet().iterator().next());
createInfo.put(VdsProperties.display, legacyGraphicsType);
}
}
private void buildVmVideoDevicesFromDb() {
List<VmDevice> vmVideoDevices = vmDeviceDao.getVmDeviceByVmIdAndType(vm.getId(), VmDeviceGeneralType.VIDEO);
for (VmDevice vmVideoDevice : vmVideoDevices) {
// skip unmanaged devices (handled separately)
if (!vmVideoDevice.isManaged()) {
continue;
}
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, vmVideoDevice.getType().getValue());
struct.put(VdsProperties.Device, vmVideoDevice.getDevice());
vmInfoBuildUtils.addAddress(vmVideoDevice, struct);
struct.put(VdsProperties.SpecParams, vmVideoDevice.getSpecParams());
struct.put(VdsProperties.DeviceId, String.valueOf(vmVideoDevice.getId().getDeviceId()));
devices.add(struct);
}
}
private void buildVmVideoDeviceOverridden() {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, VmDeviceGeneralType.VIDEO.getValue());
struct.put(VdsProperties.Device, vm.getDefaultDisplayType().getDefaultVmDeviceType().getName());
struct.put(VdsProperties.DeviceId, String.valueOf(Guid.newGuid()));
devices.add(struct);
}
/**
* Builds vm graphics from database.
*
* <p>SPICE device is put at the end of the graphics device list. Libvirt sets QEMU
* environment variable QEMU_AUDIO_DRV according to the last graphics device and we
* want to allow sound for SPICE if graphics protocol "SPICE+VNC" is selected.</p>
*/
private void buildVmGraphicsDevicesFromDb(Map<String, Object> extraSpecParams) {
Comparator<VmDevice> spiceLastDeviceComparator = Comparator.comparing(
VmDevice::getDevice,
ComparatorUtils.sortLast(VmDeviceType.SPICE.getName()));
buildVmDevicesFromDb(VmDeviceGeneralType.GRAPHICS, false, extraSpecParams, spiceLastDeviceComparator);
if (FeatureSupported.isLegacyDisplaySupported(vm.getCompatibilityVersion())) {
String legacyDisplay = deriveDisplayTypeLegacy();
if (legacyDisplay != null) {
createInfo.put(VdsProperties.display, legacyDisplay);
}
}
}
private void buildVmDevicesFromDb(VmDeviceGeneralType generalType,
boolean addAddress,
Map<String, Object> extraSpecParams) {
buildVmDevicesFromDb(generalType, addAddress, extraSpecParams, null);
}
/**
* @param deviceComparator allows to sort devices in resulting {@link #devices} list and thus
* also in libvirt domain xml. {@code null} indicates no special
* ordering.
*/
private void buildVmDevicesFromDb(VmDeviceGeneralType generalType,
boolean addAddress,
Map<String, Object> extraSpecParams,
Comparator<VmDevice> deviceComparator) {
final List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdAndType(vm.getId(), generalType);
List<VmDevice> sortedDevices = deviceComparator == null
? vmDevices
: vmDevices.stream().sorted(deviceComparator).collect(Collectors.toList());
for (VmDevice vmDevice : sortedDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
Map<String, Object> specParams = vmDevice.getSpecParams();
if (extraSpecParams != null) {
specParams.putAll(extraSpecParams);
}
struct.put(VdsProperties.SpecParams, specParams);
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
if (addAddress) {
vmInfoBuildUtils.addAddress(vmDevice, struct);
}
devices.add(struct);
}
}
private int getBootableDiskIndex(Disk disk) {
int index = ArchStrategyFactory.getStrategy(vm.getClusterArch())
.run(new GetBootableDiskIndex(numOfReservedScsiIndexes))
.returnValue();
log.info("Bootable disk '{}' set to index '{}'", disk.getId(), index);
return index;
}
private void addNetworkInterfaceProperties(Map<String, Object> struct,
VmNic vmInterface,
VmDevice vmDevice,
String nicModel) {
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
struct.put(VdsProperties.LINK_ACTIVE, String.valueOf(vmInterface.isLinked()));
vmInfoBuildUtils.addAddress(vmDevice, struct);
struct.put(VdsProperties.MAC_ADDR, vmInterface.getMacAddress());
struct.put(VdsProperties.SpecParams, vmDevice.getSpecParams());
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
struct.put(VdsProperties.NIC_TYPE, nicModel);
vmInfoBuildUtils.addProfileDataToNic(struct, vm, vmDevice, vmInterface);
vmInfoBuildUtils.addNetworkFiltersToNic(struct, vmInterface);
}
private void addDevice(Map<String, Object> struct, VmDevice vmDevice, String path) {
boolean isPayload = VmPayload.isPayload(vmDevice.getSpecParams()) &&
vmDevice.getDevice().equals(VmDeviceType.CDROM.getName());
Map<String, Object> specParams =
(vmDevice.getSpecParams() == null) ? Collections.emptyMap() : vmDevice.getSpecParams();
if (path != null) {
struct.put(VdsProperties.Path, isPayload ? "" : path);
}
if (isPayload) {
String cdInterface = osRepository.getCdInterface(
vm.getOs(),
vm.getCompatibilityVersion(),
ChipsetType.fromMachineType(vm.getEmulatedMachine()));
int index = VmDeviceCommonUtils.getCdPayloadDeviceIndex(cdInterface);
struct.put(VdsProperties.Index, Integer.toString(index));
if ("scsi".equals(cdInterface)) {
struct.put(VdsProperties.Address, vmInfoBuildUtils.createAddressForScsiDisk(0, index));
}
}
struct.put(VdsProperties.SpecParams, specParams);
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
devices.add(struct);
bootableDevices.add(vmDevice);
}
private void buildVmUsbControllers() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.CONTROLLER,
VmDeviceType.USB.getName());
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
vmInfoBuildUtils.setVdsPropertiesFromSpecParams(vmDevice.getSpecParams(), struct);
struct.put(VdsProperties.SpecParams, new HashMap<String, Object>());
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
vmInfoBuildUtils.addAddress(vmDevice, struct);
String model = (String) struct.get(VdsProperties.Model);
// This is a workaround until libvirt will fix the requirement to order these controllers
if (model != null && vmInfoBuildUtils.isFirstMasterController(model)) {
devices.add(0, struct);
} else {
devices.add(struct);
}
}
}
private void buildVmUsbSlots() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.REDIR,
VmDeviceType.SPICEVMC.getName());
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
struct.put(VdsProperties.Bus, USB_BUS);
struct.put(VdsProperties.SpecParams, vmDevice.getSpecParams());
struct.put(VdsProperties.DeviceId, String.valueOf(vmDevice.getId().getDeviceId()));
vmInfoBuildUtils.addAddress(vmDevice, struct);
devices.add(struct);
}
}
private void buildSmartcardDevice() {
List<VmDevice> vmDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
vm.getId(),
VmDeviceGeneralType.SMARTCARD,
VmDeviceType.SMARTCARD.getName());
for (VmDevice vmDevice : vmDevices) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
addDevice(struct, vmDevice, null);
}
}
private void addMemBalloonDevice(VmDevice vmDevice) {
Map<String, Object> struct = new HashMap<>();
struct.put(VdsProperties.Type, vmDevice.getType().getValue());
struct.put(VdsProperties.Device, vmDevice.getDevice());
Map<String, Object> specParams = vmDevice.getSpecParams();
// validate & set spec params for balloon device
if (specParams == null) {
specParams = new HashMap<>();
vmDevice.setSpecParams(specParams);
}
specParams.put(VdsProperties.Model, VdsProperties.Virtio);
vmInfoBuildUtils.addAddress(vmDevice, struct);
addDevice(struct, vmDevice, null);
}
/**
* Numa will use the same compatibilityVersion as cpu pinning since numa may also add cpu pinning configuration and
* the two features have almost the same libvirt version support
*/
private void addNumaSetting() {
List<VdsNumaNode> totalVdsNumaNodes = vdsNumaNodeDao.getAllVdsNumaNodeByVdsId(vdsId);
if (totalVdsNumaNodes.isEmpty()) {
log.warn("No NUMA nodes found for host {} for vm {} {}", vdsId, vm.getName(), vm.getId());
return;
}
List<VmNumaNode> vmNumaNodes = vmNumaNodeDao.getAllVmNumaNodeByVmId(vm.getId());
// if user didn't set specific NUMA conf
// create a default one with one guest numa node
if (vmNumaNodes.isEmpty()) {
if (FeatureSupported.hotPlugMemory(vm.getCompatibilityVersion(), vm.getClusterArch())) {
VmNumaNode vmNode = new VmNumaNode();
vmNode.setIndex(0);
vmNode.setMemTotal(vm.getMemSizeMb());
for (int i = 0; i < vm.getNumOfCpus(); i++) {
vmNode.getCpuIds().add(i);
}
vmNumaNodes.add(vmNode);
} else {
// no need to send numa if memory hotplug not supported
return;
}
}
NumaTuneMode numaTune = vm.getNumaTuneMode();
if (numaTune != null) {
Map<String, Object> numaTuneSetting =
NumaSettingFactory.buildVmNumatuneSetting(numaTune, vmNumaNodes);
if (!numaTuneSetting.isEmpty()) {
createInfo.put(VdsProperties.NUMA_TUNE, numaTuneSetting);
}
}
List<Map<String, Object>> createVmNumaNodes = NumaSettingFactory.buildVmNumaNodeSetting(vmNumaNodes);
if (!createVmNumaNodes.isEmpty()) {
createInfo.put(VdsProperties.VM_NUMA_NODES, createVmNumaNodes);
}
if (StringUtils.isEmpty(vm.getCpuPinning())) {
Map<String, Object> cpuPinDict =
NumaSettingFactory.buildCpuPinningWithNumaSetting(vmNumaNodes, totalVdsNumaNodes);
if (!cpuPinDict.isEmpty()) {
createInfo.put(VdsProperties.cpuPinning, cpuPinDict);
}
}
}
private Integer calcMaxVCpu() {
return VmCpuCountHelper.calcMaxVCpu(vm, vm.getClusterCompatibilityVersion());
}
private void addCpuPinning() {
final String cpuPinning = vm.getCpuPinning();
if (StringUtils.isNotEmpty(cpuPinning)) {
final Map<String, Object> pinDict = new HashMap<>();
for (String pin : cpuPinning.split("_")) {
final String[] split = pin.split("#");
pinDict.put(split[0], split[1]);
}
createInfo.put(VdsProperties.cpuPinning, pinDict);
}
}
private String getTimeZoneForVm(VM vm) {
if (!StringUtils.isEmpty(vm.getTimeZone())) {
return vm.getTimeZone();
}
// else fallback to engine config default for given OS type
if (osRepository.isWindows(vm.getOs())) {
return Config.getValue(ConfigValues.DefaultWindowsTimeZone);
} else {
return "Etc/GMT";
}
}
private void logUnsupportedInterfaceType() {
log.error("Unsupported interface type, ISCSI interface type is not supported.");
}
private Cluster getCluster() {
if (cluster == null) {
cluster = clusterDao.get(vm.getClusterId());
}
return cluster;
}
/**
* Derives display type from vm configuration, used with legacy vdsm.
*
* @return either "vnc" or "qxl" string or null if the vm is headless
*/
private String deriveDisplayTypeLegacy() {
List<VmDevice> vmDevices =
vmDeviceDao.getVmDeviceByVmIdAndType(vm.getId(), VmDeviceGeneralType.GRAPHICS);
if (vmDevices.isEmpty()) {
return null;
} else if (vmDevices.size() == 2) { // we have spice & vnc together, we prioritize SPICE
return VdsProperties.QXL;
}
GraphicsType deviceType = GraphicsType.fromString(vmDevices.get(0).getDevice());
return graphicsTypeToLegacyDisplayType(deviceType);
}
private String graphicsTypeToLegacyDisplayType(GraphicsType graphicsType) {
switch (graphicsType) {
case SPICE:
return VdsProperties.QXL;
case VNC:
return VdsProperties.VNC;
default:
return null;
}
}
}