/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.ow2.choreos.ee.nodes; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import org.ow2.choreos.ee.config.CloudConfiguration; import org.ow2.choreos.nodes.NodeNotCreatedException; import org.ow2.choreos.nodes.NodeNotDestroyed; import org.ow2.choreos.nodes.datamodel.CloudNode; import org.ow2.choreos.nodes.datamodel.NodeSpec; import org.ow2.choreos.utils.Concurrency; /** * Maintains a pool of VMs. * * The VMs in the pool are not knew by the NPM. The idea is to provide a fast VM * creation: when NPM requests a VM to the pool, the pool returns an already * created VM that was in the pool, but NPM will see it just like a new VM * created quickly. * * @author leonardo * */ public class IdlePool { private static final int FILLING_POOL_TIMEOUT_MINUTES = 10; private static IdlePool INSTANCE; private static Logger logger = Logger.getLogger(IdlePool.class); private int poolSize; private int threshold; private Set<CloudNode> idleNodes = new HashSet<CloudNode>(); private ExecutorService fillerExecutor = Executors.newSingleThreadExecutor(); private CloudConfiguration cloudConfiguration; IdlePool(CloudConfiguration cloudConfiguration, int poolSize, int threshold) { this.poolSize = poolSize; this.threshold = threshold; this.cloudConfiguration = cloudConfiguration; } public int getSize() { return poolSize; } public int getThreshold() { return threshold; } /** * * @return an unmodifiable list with the ids of the nodes in the idle pool */ Set<CloudNode> getIdleNodes() { return Collections.unmodifiableSet(idleNodes); } /** * Retrieves a node from the idle pool. * * The node is removed from the pool. The method is synchronized, so * multiple invocations will not get the same node. If the pool is empty, * the client waits for the creation of a VM * * @throws NodeNotCreatedException * @return */ public CloudNode retriveNode() throws NodeNotCreatedException { if (idleNodes.isEmpty()) { VMCreator vmCreator = new VMCreator(); vmCreator.run(); if (!vmCreator.ok) { throw new NodeNotCreatedException(""); } } synchronized (this) { CloudNode node = idleNodes.iterator().next(); idleNodes.remove(node); adaptPoolSize(); return node; } } private void adaptPoolSize() { if (idleNodes.size() <= threshold) { poolSize++; logger.info("Idle pool size has increased to " + poolSize); } } /** * Creates extra VMs asynchronously * * @param howManyVMs * @param nodeCreator */ public void createExtraVMs(int howManyVMs) { for (int i = 0; i < howManyVMs; i++) { VMCreator vmCreator = new VMCreator(); Thread thrd = new Thread(vmCreator); thrd.start(); } } public boolean isFull() { return idleNodes.size() >= poolSize; } /** * Give a order to fill the pool. The execution is asynchronous, i.e., the * client will not wait for the pool be filled. */ public void fillPool() { PoolFiller filler = new PoolFiller(); this.fillerExecutor.execute(filler); } public void emptyPool() throws NodeNotDestroyed { NodesDestroyer destroyer = new NodesDestroyer(cloudConfiguration, idleNodes); destroyer.destroyNodes(); } private class VMCreator implements Runnable { boolean ok; @Override public void run() { try { NodeCreatorFactory factory = new NodeCreatorFactory(); NodeCreator nodeCreator = factory.getNewNodeCreator(cloudConfiguration); CloudNode node = nodeCreator.createBootstrappedNode(new NodeSpec()); ok = true; synchronized (IdlePool.this) { idleNodes.add(node); } } catch (NodeNotCreatedException e) { logger.error("Could not create a VM by the pool"); ok = false; } } } private class PoolFiller implements Runnable { @Override public void run() { int extra = poolSize - idleNodes.size(); if (extra > 0) { ExecutorService executor = Executors.newFixedThreadPool(extra); for (int i = 0; i < extra; i++) { VMCreator vmCreator = new VMCreator(); executor.execute(vmCreator); } Concurrency.waitExecutor(executor, FILLING_POOL_TIMEOUT_MINUTES, "Could not properly fill the pool."); } } } }