package org.ovirt.engine.core.vdsbroker;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.BackendService;
import org.ovirt.engine.core.common.businessentities.IVdsEventListener;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VDSStatus;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmDynamic;
import org.ovirt.engine.core.common.businessentities.VmExitReason;
import org.ovirt.engine.core.common.businessentities.VmExitStatus;
import org.ovirt.engine.core.common.businessentities.VmPauseStatus;
import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface;
import org.ovirt.engine.core.common.businessentities.network.VmNetworkStatistics;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.interfaces.FutureVDSCall;
import org.ovirt.engine.core.common.qualifiers.VmDeleted;
import org.ovirt.engine.core.common.vdscommands.FutureVDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSAsyncReturnValue;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSParametersBase;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.common.vdscommands.VdsIdVDSCommandParametersBase;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl;
import org.ovirt.engine.core.dao.VdsDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
import org.ovirt.engine.core.dao.network.VmNetworkStatisticsDao;
import org.ovirt.engine.core.di.Injector;
import org.ovirt.engine.core.utils.ReflectionUtils;
import org.ovirt.engine.core.utils.collections.MultiValueMapUtils;
import org.ovirt.engine.core.vdsbroker.vdsbroker.FutureVDSCommand;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsCommandExecutor;
import org.ovirt.vdsm.jsonrpc.client.events.EventSubscriber;
import org.ovirt.vdsm.jsonrpc.client.reactors.ReactorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class ResourceManager implements BackendService {
private static ResourceManager instance;
private final Map<Guid, HashSet<Guid>> vdsAndVmsList = new ConcurrentHashMap<>();
private final Map<Guid, VdsManager> vdsManagersDict = new ConcurrentHashMap<>();
private final Set<Guid> asyncRunningVms =
Collections.newSetFromMap(new ConcurrentHashMap<>());
private final ConcurrentHashMap<Guid, VmManager> vmManagers = new ConcurrentHashMap<>();
private static final String VDSCommandPrefix = "VDSCommand";
private static final Logger log = LoggerFactory.getLogger(ResourceManager.class);
private int parallelism;
@Inject
private Instance<IVdsEventListener> eventListener;
@Inject
private BeanManager beanManager;
@Inject
private AuditLogDirector auditLogDirector;
@Inject
private VdsDao hostDao;
@Inject
private VmDynamicDao vmDynamicDao;
@Inject
private VmNetworkStatisticsDao vmNetworkStatisticsDao;
@Inject
Instance<VdsCommandExecutor> commandExecutor;
@Inject
private VdsManagerFactory vdsManagerFactory;
private ResourceManager() {
this.parallelism = Config.getValue(ConfigValues.EventProcessingPoolSize);
}
/**
* TODO remove this after moving all places to use CDI.
* kept for backward compatibility.
*/
@Deprecated
public static ResourceManager getInstance() {
return instance;
}
private static void setInstance(ResourceManager manager) {
instance = manager;
}
@PostConstruct
private void init() {
// init the singleton. TODO remove once all code is using CDI
setInstance(this);
log.info("Start initializing {}", getClass().getSimpleName());
populateVdsAndVmsList();
// Populate the VDS dictionary
final List<VDS> allVdsList = hostDao.getAll();
for (VDS curVds : allVdsList) {
addVds(curVds, true);
}
log.info("Finished initializing {}", getClass().getSimpleName());
}
private void populateVdsAndVmsList() {
final List<VmDynamic> vms = vmDynamicDao.getAll();
for (VmDynamic vm : vms) {
if (!vm.getStatus().isNotRunning() && vm.getRunOnVds() != null) {
MultiValueMapUtils.addToMap(vm.getRunOnVds(),
vm.getId(),
vdsAndVmsList,
new MultiValueMapUtils.HashSetCreator<>());
}
}
}
public boolean addAsyncRunningVm(Guid vmId) {
return asyncRunningVms.add(vmId);
}
public void removeAsyncRunningVm(Guid vmId) {
asyncRunningVms.remove(vmId);
getEventListener().removeAsyncRunningCommand(vmId);
}
public void succededToRunVm(Guid vmId, Guid vdsId) {
if (asyncRunningVms.contains(vmId)) {
getEventListener().runningSucceded(vmId);
}
removeAsyncRunningVm(vmId);
}
/**
* Initiate rerun event when vm failed to run
*/
public void rerunFailedCommand(Guid vmId, Guid vdsId) {
if (asyncRunningVms.remove(vmId)) {
// remove async record from broker only
getEventListener().rerun(vmId);
}
}
public boolean isVmInAsyncRunningList(Guid vmId) {
return asyncRunningVms.contains(vmId);
}
public void removeVmFromDownVms(Guid vdsId, Guid vmId) {
HashSet<Guid> vms = vdsAndVmsList.get(vdsId);
if (vms != null) {
vms.remove(vmId);
}
}
public void handleVmsFinishedInitOnVds(Guid vdsId) {
HashSet<Guid> vms = vdsAndVmsList.get(vdsId);
if (vms != null) {
getEventListener().processOnVmStop(vms, vdsId);
vdsAndVmsList.remove(vdsId);
}
}
public IVdsEventListener getEventListener() {
return eventListener.get();
}
public void addVds(VDS vds, boolean isInternal) {
VdsManager vdsManager = vdsManagerFactory.create(vds, this);
if (isInternal) {
VDSStatus status = vds.getStatus();
switch (vds.getStatus()) {
case Error:
status = VDSStatus.Up;
break;
case Reboot:
case NonResponsive:
case Connecting:
case Installing:
status = VDSStatus.Unassigned;
break;
}
if (status != vds.getStatus()) {
vdsManager.setStatus(status, vds);
vdsManager.updateStatisticsData(vds.getStatisticsData());
}
// set pending to 0
vds.setPendingVcpusCount(0);
vdsManager.updateDynamicData(vds.getDynamicData());
}
vdsManager.scheduleJobs();
vdsManagersDict.put(vds.getId(), vdsManager);
log.info("VDS '{}' was added to the Resource Manager", vds.getId());
}
public void removeVds(Guid vdsId) {
removeVds(vdsId, false);
}
public void removeVds(Guid vdsId, boolean newHost) {
VdsManager vdsManager = getVdsManager(vdsId, newHost);
if (vdsManager != null) {
vdsManager.dispose();
vdsManagersDict.remove(vdsId);
}
}
public VdsManager getVdsManager(Guid vdsId) {
return getVdsManager(vdsId, false);
}
public VdsManager getVdsManager(Guid vdsId, boolean newHost) {
VdsManager vdsManger = vdsManagersDict.get(vdsId);
if (vdsManger == null) {
if (!newHost) {
log.error("Cannot get vdsManager for vdsid='{}'.", vdsId);
}
}
return vdsManger;
}
/**
* Set vm status to Unknown and save to DB.
*/
public void setVmUnknown(VM vm) {
removeAsyncRunningVm(vm.getId());
internalSetVmStatus(vm.getDynamicData(), VMStatus.Unknown);
// log VM transition to unknown status
AuditLogable logable = new AuditLogableImpl();
logable.setVmId(vm.getId());
logable.setVmName(vm.getName());
auditLogDirector.log(logable, AuditLogType.VM_SET_TO_UNKNOWN_STATUS);
storeVm(vm);
}
private void storeVm(VM vm) {
vmDynamicDao.update(vm.getDynamicData());
getVmManager(vm.getId()).update(vm.getStatisticsData());
List<VmNetworkInterface> interfaces = vm.getInterfaces();
if (interfaces != null) {
for (VmNetworkInterface ifc : interfaces) {
VmNetworkStatistics stats = ifc.getStatistics();
vmNetworkStatisticsDao.update(stats);
}
}
}
public boolean isVmDuringInitiating(Guid vm_guid) {
return asyncRunningVms.contains(vm_guid);
}
/**
* Set vm status without saving to DB
*
* <p> Note: Calling this method with status=down, must be only when the
* VM went down normally, otherwise call {@link #InternalSetVmStatus(VM, VMStatus, VmExitStatus, String)}
*/
public void internalSetVmStatus(VmDynamic vm, final VMStatus status) {
internalSetVmStatus(vm, status, VmExitStatus.Normal, StringUtils.EMPTY, VmExitReason.Unknown);
}
public void internalSetVmStatus(VmDynamic vm, final VMStatus status, VmExitStatus exitStatus) {
internalSetVmStatus(vm, status, exitStatus, StringUtils.EMPTY, VmExitReason.Unknown);
}
public void internalSetVmStatus(VmDynamic vm,
final VMStatus status,
final VmExitStatus exitStaus,
final String exitMessage,
final VmExitReason exitReason) {
vm.setStatus(status);
vm.setExitStatus(exitStaus);
vm.setExitMessage(exitMessage);
vm.setExitReason(exitReason);
boolean isVmNotRunning = status.isNotRunning();
if (isVmNotRunning || status == VMStatus.Unknown) {
resetVmAttributes(vm);
if (isVmNotRunning) {
vm.setRunOnVds(null);
vm.setPauseStatus(VmPauseStatus.NONE);
vm.setLastStopTime(new Date());
}
}
}
/**
* Resets VM attributes
* @param vm
* the VM to reset
*/
private void resetVmAttributes(VmDynamic vm) {
vm.setMigratingToVds(null);
vm.getGraphicsInfos().clear();
vm.setGuestCurrentUserName(null);
vm.setConsoleCurrentUserName(null);
vm.setConsoleUserId(null);
vm.setGuestOs(null);
vm.setIp(null);
vm.setFqdn(null);
vm.setCpuName(null);
vm.setEmulatedMachine(null);
}
private static String getCommandTypeName(VDSCommandType command) {
String packageName = command.getPackageName();
String commandName = String.format("%s.%s%s", packageName, command, VDSCommandPrefix);
return commandName;
}
/**
* Create the command which needs to run.
* @return The command, or null if it can't be created.
*/
private <P extends VDSParametersBase> VDSCommandBase<P> createCommand(
VDSCommandType commandType, P parameters) {
try {
@SuppressWarnings("unchecked")
Class<VDSCommandBase<P>> type =
(Class<VDSCommandBase<P>>) Class.forName(getCommandTypeName(commandType));
Constructor<VDSCommandBase<P>> constructor =
ReflectionUtils.findConstructor(type, parameters.getClass());
if (constructor != null) {
return instantiateInjectedCommand(parameters, constructor);
}
} catch (Exception e) {
if (e.getCause() != null) {
log.error("createCommand failed: {}", e.getCause().getMessage());
log.error("Exception", e);
throw new RuntimeException(e.getCause().getMessage(), e.getCause());
}
log.error("createCommand failed: {}", e.getMessage());
log.debug("Exception", e);
}
return null;
}
private <P extends VDSParametersBase, T extends VDSCommandBase<P>> T instantiateInjectedCommand(P parameters,
Constructor<T> constructor) throws Exception {
T cmd = constructor.newInstance(new Object[] { parameters });
Injector.injectMembers(cmd);
return cmd;
}
private <P extends VdsIdVDSCommandParametersBase> FutureVDSCommand<P> createFutureCommand(FutureVDSCommandType commandType,
P parameters) {
try {
Class<FutureVDSCommand<P>> type =
(Class<FutureVDSCommand<P>>) Class.forName(commandType.getFullyQualifiedClassName());
Constructor<FutureVDSCommand<P>> constructor = ReflectionUtils.findConstructor(type, parameters.getClass());
if (constructor != null) {
return instantiateInjectedCommand(parameters, constructor);
}
} catch (Exception e) {
if (e.getCause() != null) {
log.error("CreateFutureCommand failed: {}", e.getCause().getMessage());
log.debug("Exception", e);
throw new RuntimeException(e.getCause().getMessage(), e.getCause());
}
log.error("CreateFutureCommand failed: {}", e.getMessage());
log.debug("Exception", e);
}
return null;
}
public <P extends VDSParametersBase> VDSReturnValue runVdsCommand(VDSCommandType commandType, P parameters) {
// try run vds command
VDSCommandBase<P> command = createCommand(commandType, parameters);
if (command != null) {
return commandExecutor.get().execute(command, commandType);
}
return null;
}
public <P extends VDSParametersBase> VDSAsyncReturnValue runAsyncVdsCommand(VDSCommandType commandType, P parameters) {
VDSCommandBase<P> command = createCommand(commandType, parameters);
if (command != null) {
command.setAsync(true);
commandExecutor.get().execute(command, commandType);
VDSReturnValue value = command.getVDSReturnValue();
if (!VDSAsyncReturnValue.class.isInstance(value)) {
throw new IllegalStateException("Wrong return value type");
}
return (VDSAsyncReturnValue) value;
}
return null;
}
public <P extends VdsIdVDSCommandParametersBase> FutureVDSCall<VDSReturnValue> runFutureVdsCommand(final FutureVDSCommandType commandType,
final P parameters) {
FutureVDSCommand<P> command = createFutureCommand(commandType, parameters);
if (command != null) {
command.execute();
return command;
}
return null;
}
public VmManager getVmManager(Guid vmId) {
return getVmManager(vmId, true);
}
public VmManager getVmManager(Guid vmId, boolean createIfAbsent) {
if (createIfAbsent && !vmManagers.containsKey(vmId)) {
vmManagers.computeIfAbsent(vmId, guid -> Injector.injectMembers(new VmManager(guid)));
}
return vmManagers.get(vmId);
}
public void clearLastStatusEventStampsFromVds(Guid vdsId) {
for (VmManager vmManager : vmManagers.values()) {
vmManager.clearLastStatusEventStampIfFromVds(vdsId);
}
}
public void onVmDelete(@Observes @VmDeleted Guid vmId) {
vmManagers.remove(vmId);
}
public void subscribe(EventSubscriber subscriber) {
log.debug("subscribe called with subscription id: {}", subscriber.getSubscriptionId());
ReactorFactory.getWorker(this.parallelism).getPublisher().subscribe(subscriber);
}
}