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
    final Map<Guid, Set<PendingResource>> resourcesByHost = new ConcurrentHashMap<>();
    final Map<Guid, Set<PendingResource>> resourcesByVm = new ConcurrentHashMap<>();
    final Map<PendingResource, PendingResource> pendingResources = new ConcurrentHashMap<>();

    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<PendingResource>());
            }

            if (!resourcesByHost.containsKey(resource.getHost())) {
                resourcesByHost.put(resource.getHost(), new HashSet<PendingResource>());
            }

            /* 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 = PendingMemory.collectForHost(this, hostId);

        vdsManager.updatePendingData(pendingMemory, pendingCpus);
    }
}

@@ -8552,24 +8552,34 @@
 ry = Pending
+Overcommit
 Memory.colle

Use the right memory value for pending memory checks