package org.ovirt.engine.core.bll.scheduling.pending;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.vdsbroker.ResourceManager;
import org.ovirt.engine.core.vdsbroker.VdsManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tracking service for all pending resources. All writes are synchronized and ensure that the internal
* structures are consistent. Reads are best effort operations and require external locking if absolute consistency
* is needed.
*/
public class PendingResourceManager {
private static final Logger log = LoggerFactory.getLogger(PendingResourceManager.class);
// All internal structures have to be thread-safe for concurrent access
private final Map<Guid, Set<PendingResource>> resourcesByHost = new ConcurrentHashMap<>();
private final Map<Guid, Set<PendingResource>> resourcesByVm = new ConcurrentHashMap<>();
private final Map<PendingResource, PendingResource> pendingResources = new ConcurrentHashMap<>();
private final ResourceManager resourceManager;
public PendingResourceManager() {
resourceManager = null;
}
public PendingResourceManager(ResourceManager resourceManager) {
this.resourceManager = resourceManager;
}
/**
* Remove all pending resources associated with the VM.
* @param vm VmStatic with valid getId()
*/
public void clearVm(VmStatic vm) {
Set<Guid> modifiedHosts;
synchronized (this) {
if (!resourcesByVm.containsKey(vm.getId())) {
return;
}
log.debug("Clearing pending resources for VM {}", vm.getId());
modifiedHosts = new HashSet<>();
/* Remove all resources associated with the VM from the global set
* and from the byHost index
*/
for (PendingResource resource : resourcesByVm.get(vm.getId())) {
if (resourcesByHost.containsKey(resource.getHost())) {
modifiedHosts.add(resource.getHost());
resourcesByHost.get(resource.getHost()).remove(resource);
}
pendingResources.remove(resource);
}
resourcesByVm.get(vm.getId()).clear();
}
for (Guid hostId: modifiedHosts) {
notifyHostManagers(hostId);
}
}
public void clearVm(VM vm) {
clearVm(vm.getStaticData());
}
/**
* Remove all pending resources associated with the host.
*
* This is supposed to be used during maintenance flows to clear possible stale data.
*
* @param host VDS with valid getId()
*/
public void clearHost(VDS host) {
synchronized (this) {
if (!resourcesByHost.containsKey(host.getId())) {
return;
}
log.debug("Clearing pending resources for host {}", host.getId());
/* Remove all resources associated with the host from the global set
* and from the byVm index
*/
for (PendingResource resource : resourcesByHost.get(host.getId())) {
if (resourcesByVm.containsKey(resource.getVm())) {
resourcesByVm.get(resource.getVm()).remove(resource);
}
pendingResources.remove(resource);
}
resourcesByHost.get(host.getId()).clear();
}
notifyHostManagers(host.getId());
}
/**
* Add a resource record to the pending lists.
*
* IMPORTANT: Call notifyHostManagers once all resources are
* registered.
*
* @param resource Pending resource instance with valid host and vm
* fields.
*/
public void addPending(PendingResource resource) {
synchronized (this) {
/* Clear VM and Host indexes when the resource is added again.
* This should not happen in theory, but lets anticipate future bugs :)
*/
if (pendingResources.containsKey(resource)) {
PendingResource old = pendingResources.get(resource);
log.warn("Clearing stale pending resource {} (host: {}, vm: {})",
old, old.getHost(), old.getVm());
resourcesByVm.get(old.getVm()).remove(old);
resourcesByHost.get(old.getHost()).remove(old);
}
log.debug("Adding pending resource {} (host: {}, vm: {})",
resource, resource.getHost(), resource.getVm());
/* Make sure the index lists exist */
if (!resourcesByVm.containsKey(resource.getVm())) {
resourcesByVm.put(resource.getVm(), new HashSet<>());
}
if (!resourcesByHost.containsKey(resource.getHost())) {
resourcesByHost.put(resource.getHost(), new HashSet<>());
}
/* Update indexes */
resourcesByVm.get(resource.getVm()).add(resource);
resourcesByHost.get(resource.getHost()).add(resource);
pendingResources.put(resource, resource);
}
}
/**
* Return all currently pending resources of type "type" associated with host "vds".
* @param host ID of a host
* @param type Class object identifying the type of pending resources we are interested in
* @return Iterable object with the requested resources
*/
public <T extends PendingResource> Iterable<T> pendingHostResources(Guid host, Class<T> type) {
if (!resourcesByHost.containsKey(host)) {
return Collections.emptyList();
}
List<T> list = new ArrayList<>();
for (PendingResource resource: resourcesByHost.get(host)) {
if (resource.getClass().equals(type)) {
list.add((T)resource);
}
}
return list;
}
/**
* Return all currently pending resources of type "type" associated with VM "vm".
* @param vm ID of a VM
* @param type Class object identifying the type of pending resources we are interested in
* @return Iterable object with the requested resources
*/
public <T extends PendingResource> Iterable<T> pendingVmResources(Guid vm, Class<T> type) {
if (!resourcesByVm.containsKey(vm)) {
return Collections.emptyList();
}
List<T> list = new ArrayList<>();
for (PendingResource resource: resourcesByVm.get(vm)) {
if (resource.getClass().equals(type)) {
list.add((T)resource);
}
}
return list;
}
/**
* Find pending resource that matches the provided template and return it.
* @param template resource template filled with identification-specific fields
* @return The actual pending resource
*/
public <T extends PendingResource> T getExactPendingResource(T template) {
return (T)pendingResources.get(template);
}
/**
* Return all currently pending resources of type "type".
* @param type Class object identifying the type of pending resources we are interested in
* @return Iterable object with the requested resources
*/
public <T extends PendingResource> Iterable<T> pendingResources(Class<T> type) {
List<T> list = new ArrayList<>();
for (PendingResource resource: pendingResources.values()) {
if (resource.getClass().equals(type)) {
list.add((T)resource);
}
}
return list;
}
/**
* Notify host manager that the pending memory and CPU data have changed.
* This is automatically called when a VM or Host are cleared, however the user is responsible
* for calling it after finished with adding all new pending resources for a VM.
* @param hostId - it of the affected host
*/
public void notifyHostManagers(Guid hostId) {
if (resourceManager == null) {
return;
}
VdsManager vdsManager = resourceManager.getVdsManager(hostId);
int pendingCpus = PendingCpuCores.collectForHost(this, hostId);
int pendingMemory = PendingOvercommitMemory.collectForHost(this, hostId);
vdsManager.updatePendingData(pendingMemory, pendingCpus);
}
}