package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.job.ExecutionContext;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.AddUnmanagedVmsParameters;
import org.ovirt.engine.core.common.action.AddVmParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.businessentities.ArchitectureType;
import org.ovirt.engine.core.common.businessentities.DisplayType;
import org.ovirt.engine.core.common.businessentities.GraphicsDevice;
import org.ovirt.engine.core.common.businessentities.GraphicsType;
import org.ovirt.engine.core.common.businessentities.OriginType;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.job.Step;
import org.ovirt.engine.core.common.job.StepEnum;
import org.ovirt.engine.core.common.osinfo.OsRepository;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.utils.SimpleDependencyInjector;
import org.ovirt.engine.core.common.utils.VmDeviceType;
import org.ovirt.engine.core.common.vdscommands.FullListVDSCommandParameters;
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.VmStaticDao;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.ovirt.engine.core.vdsbroker.ResourceManager;
import org.ovirt.engine.core.vdsbroker.monitoring.VmDevicesMonitoring;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsBrokerObjectsBuilder;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties;
@InternalCommandAttribute
@NonTransactiveCommandAttribute
public class AddUnmanagedVmsCommand<T extends AddUnmanagedVmsParameters> extends CommandBase<T> {
private static final String EXTERNAL_VM_NAME_FORMAT = "external-%1$s";
@Inject
private Instance<HostedEngineImporter> hostedEngineImporterProvider;
@Inject
private VmDevicesMonitoring vmDevicesMonitoring;
@Inject
private ResourceManager resourceManager;
@Inject
private VmStaticDao vmStaticDao;
private Set<String> graphicsDeviceTypes = new HashSet<>(Arrays.asList(
GraphicsType.SPICE.toString().toLowerCase(),
GraphicsType.VNC.toString().toLowerCase()
));
public AddUnmanagedVmsCommand(T parameters, CommandContext context) {
super(parameters, context);
}
@Override
protected void init() {
super.init();
setVdsId(getParameters().getVdsId());
// this command is called internally within lock so the host surely exists
setClusterId(getVds().getClusterId());
}
@Override
protected void executeCommand() {
if (!getParameters().getVmIds().isEmpty()) {
int defaultOsId = getDefaultOsId(getCluster().getArchitecture());
DisplayType defaultDisplayType = getDefaultDisplayType(defaultOsId, getCluster().getCompatibilityVersion());
// Query VDSM for VMs info, and creating a proper VMStatic to be used when importing them
long fetchTime = System.nanoTime();
Map<String, Object>[] vmsInfo = getVmsInfo();
for (Map<String, Object> vmInfo : vmsInfo) {
convertVm(defaultOsId, defaultDisplayType, fetchTime, vmInfo);
}
}
setSucceeded(true);
}
// Visible for testing
protected void convertVm(int defaultOsId, DisplayType defaultDisplayType, long fetchTime, Map<String, Object>
vmInfo) {
Guid vmId = Guid.createGuidFromString((String) vmInfo.get(VdsProperties.vm_guid));
String vmNameOnHost = (String) vmInfo.get(VdsProperties.vm_name);
if (isHostedEngineVm(vmId, vmNameOnHost)) {
// its a hosted engine VM -> import it
importHostedEngineVm(vmInfo);
return;
}
VmStatic vmStatic = new VmStatic();
vmStatic.setId(vmId);
vmStatic.setCreationDate(new Date());
vmStatic.setClusterId(getClusterId());
vmStatic.setName(String.format(EXTERNAL_VM_NAME_FORMAT, vmNameOnHost));
vmStatic.setOrigin(OriginType.EXTERNAL);
vmStatic.setNumOfSockets(VdsBrokerObjectsBuilder.parseIntVdsProperty(vmInfo.get(VdsProperties.num_of_cpus)));
vmStatic.setMemSizeMb(VdsBrokerObjectsBuilder.parseIntVdsProperty(vmInfo.get(VdsProperties.mem_size_mb)));
// VMs started before engine 3.6 may not have 'maxMemory' set
final int maxMemorySize = vmInfo.get(VdsProperties.maxMemSize) != null
? VdsBrokerObjectsBuilder.parseIntVdsProperty(vmInfo.get(VdsProperties.maxMemSize))
: vmStatic.getMemSizeMb();
vmStatic.setMaxMemorySizeMb(maxMemorySize);
vmStatic.setSingleQxlPci(false);
setOsId(vmStatic, (String) vmInfo.get(VdsProperties.guest_os), defaultOsId);
setDisplayType(vmStatic, (String) vmInfo.get(VdsProperties.displayType), defaultDisplayType);
addExternallyManagedVm(vmStatic);
addDevices(vmInfo, fetchTime);
log.info("Importing VM '{}' as '{}', as it is running on the on Host, but does not exist in the engine.", vmNameOnHost, vmStatic.getName());
}
private List<GraphicsDevice> extractGraphicsDevices(Guid vmId, Object[] devices) {
List<GraphicsDevice> graphicsDevices = new ArrayList<>();
for (Object o : devices) {
Map device = (Map<String, Object>) o;
String deviceName = (String) device.get(VdsProperties.Device);
if (graphicsDeviceTypes.contains(deviceName)) {
GraphicsDevice graphicsDevice = new GraphicsDevice(VmDeviceType.valueOf(deviceName.toUpperCase()));
graphicsDevice.setVmId(vmId);
graphicsDevice.setDeviceId(Guid.newGuid());
graphicsDevice.setManaged(true);
graphicsDevices.add(graphicsDevice);
}
}
return graphicsDevices;
}
private boolean isHostedEngineVm(Guid vmId, String vmNameOnHost) {
VmStatic dbVm = vmStaticDao.get(vmId);
return dbVm == null ?
Objects.equals(vmNameOnHost, Config.<String>getValue(ConfigValues.HostedEngineVmName))
: dbVm.getOrigin() == OriginType.HOSTED_ENGINE;
}
/**
* Gets VM full information for the given list of VMs.
*/
@SuppressWarnings("unchecked")
protected Map<String, Object>[] getVmsInfo() {
List<String> vmsToUpdate = getParameters().getVmIds().stream().map(Guid::toString).collect(Collectors.toList());
Map<String, Object>[] result = new Map[0];
VDSReturnValue vdsReturnValue = runVdsCommand(
VDSCommandType.FullList,
new FullListVDSCommandParameters(getVdsId(), vmsToUpdate));
if (vdsReturnValue.getSucceeded()) {
result = (Map<String, Object>[]) vdsReturnValue.getReturnValue();
}
return result;
}
// Visible for testing
protected void importHostedEngineVm(Map<String, Object> vmStruct) {
VM vm = VdsBrokerObjectsBuilder.buildVmsDataFromExternalProvider(vmStruct);
if (vm != null) {
vm.setImages(VdsBrokerObjectsBuilder.buildDiskImagesFromDevices(vmStruct, vm.getId()));
vm.setInterfaces(VdsBrokerObjectsBuilder.buildVmNetworkInterfacesFromDevices(vmStruct));
for (DiskImage diskImage : vm.getImages()) {
vm.getDiskMap().put(diskImage.getId(), diskImage);
}
vm.setClusterId(getClusterId());
vm.setRunOnVds(getVdsId());
List<GraphicsDevice> graphicsDevices = extractGraphicsDevices(vm.getId(),
(Object[]) vmStruct.get(VdsProperties.Devices));
if (graphicsDevices.size() == 1
&& VmDeviceType.valueOf(graphicsDevices.get(0).getDevice().toUpperCase()) == VmDeviceType.VNC) {
vm.setDefaultDisplayType(DisplayType.vga);
} else {
vm.setDefaultDisplayType(DisplayType.qxl);
}
// HE VM does not support that feature, disable it
vm.setSingleQxlPci(false);
for (GraphicsDevice graphicsDevice : graphicsDevices) {
vm.getManagedVmDeviceMap().put(graphicsDevice.getDeviceId(), graphicsDevice);
}
VmDevice consoleDevice = VdsBrokerObjectsBuilder.buildConsoleDevice(vmStruct, vm.getId());
if(consoleDevice != null){
vm.getManagedVmDeviceMap().put(consoleDevice.getDeviceId(), consoleDevice);
}
importHostedEngineVm(vm);
}
}
public void importHostedEngineVm(final VM vm) {
ThreadPoolUtil.execute(() -> hostedEngineImporterProvider.get().doImport(vm));
}
// Visible for testing
protected void addExternallyManagedVm(VmStatic vmStatic) {
VdcReturnValueBase returnValue =
runInternalAction(VdcActionType.AddVmFromScratch,
new AddVmParameters(vmStatic),
createAddExternalVmContext(vmStatic));
if (!returnValue.getSucceeded()) {
log.debug("Failed adding Externally managed VM '{}'", vmStatic.getName());
return;
}
resourceManager.getVmManager(vmStatic.getId()).update(vmStatic);
}
// Visible for testing
protected void addDevices(Map<String, Object> vmInfo, long fetchTime) {
VmDevicesMonitoring.Change change = vmDevicesMonitoring.createChange(fetchTime);
change.updateVmFromFullList(vmInfo);
change.flush();
}
protected CommandContext createAddExternalVmContext(VmStatic vmStatic) {
ExecutionContext ctx = new ExecutionContext();
try {
Step step = executionHandler.addSubStep(getExecutionContext(),
getExecutionContext().getJob().getStep(StepEnum.EXECUTING),
StepEnum.ADD_VM,
ExecutionMessageDirector.resolveStepMessage(
StepEnum.ADD_VM,
Collections.singletonMap(VdcObjectType.VM.name().toLowerCase(), vmStatic.getName())));
ctx.setJob(getExecutionContext().getJob());
ctx.setStep(step);
ctx.setMonitored(true);
} catch (RuntimeException e) {
log.error("Failed to create ExecutionContext for AddVmFromScratch", e);
}
return cloneContextAndDetachFromParent().withExecutionContext(ctx);
}
protected void setOsId(VmStatic vmStatic, String guestOsNameFromVdsm, int defaultArchOsId) {
if (StringUtils.isEmpty(guestOsNameFromVdsm)) {
log.debug("VM '{}': setting default OS ID: '{}'", vmStatic.getName(), defaultArchOsId);
vmStatic.setOsId(defaultArchOsId);
}
}
protected void setDisplayType(VmStatic vmStatic, String displayTypeFromVdsm, DisplayType defaultDisplayType) {
if (StringUtils.isEmpty(displayTypeFromVdsm)) {
log.debug("VM '{}': setting default display type: '{}'", vmStatic.getName(), defaultDisplayType.getValue());
vmStatic.setDefaultDisplayType(defaultDisplayType);
}
}
private int getDefaultOsId(ArchitectureType architecture) {
OsRepository osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class);
Integer defaultArchOsId = osRepository.getDefaultOSes().get(architecture);
return (defaultArchOsId == null) ? 0 : defaultArchOsId;
}
private DisplayType getDefaultDisplayType(int osId, Version clusterVersion) {
OsRepository osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class);
List<Pair<GraphicsType, DisplayType>> pairs = osRepository.getGraphicsAndDisplays(osId, clusterVersion);
if (!pairs.isEmpty()) {
Pair<GraphicsType, DisplayType> graphicsDisplayPair = pairs.get(0);
return graphicsDisplayPair.getSecond();
}
return DisplayType.qxl;
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
return Collections.emptyList();
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
jobProperties.put(VdcObjectType.VDS.name().toLowerCase(), getVdsName());
jobProperties.put(VdcObjectType.Cluster.name().toLowerCase(), getClusterName());
}
return jobProperties;
}
}