/*
* Copyright (c) 2010 Fabien Hermenier
*
* This file is part of Entropy.
*
* Entropy is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Entropy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Entropy. If not, see <http://www.gnu.org/licenses/>.
*/
package entropy.vjob;
import java.util.ArrayList;
import java.util.List;
import choco.cp.solver.constraints.global.AmongGAC;
import choco.cp.solver.constraints.global.Occurrence;
import choco.kernel.common.util.tools.ArrayUtils;
import choco.kernel.solver.ContradictionException;
import choco.kernel.solver.variables.integer.IntDomainVar;
import entropy.configuration.Configuration;
import entropy.configuration.ManagedElementSet;
import entropy.configuration.Node;
import entropy.configuration.SimpleManagedElementSet;
import entropy.configuration.VirtualMachine;
import entropy.plan.choco.ReconfigurationProblem;
import entropy.plan.choco.actionModel.VirtualMachineActionModel;
import entropy.plan.choco.actionModel.slice.DemandingSlice;
import entropy.plan.choco.actionModel.slice.Slices;
import gnu.trove.TIntHashSet;
/**
* A constraint to restrict the number of virtual machines a set
* of nodes can host simultaneously.
* <p/>
* TODO: Improve the parent API for a better integration of constraints only focused on nodes
* TODO: create a builder.
* TODO: test
*
* @author Fabien Hermenier
*/
public class Capacity implements PlacementConstraint {
private int max;
private VJobSet<Node> nodes;
private final static ExplodedSet<VirtualMachine> emptyVMs = new ExplodedSet<VirtualMachine>();
/**
* Make a new constraint.
*
* @param ns the nodes to consider
* @param m the maximum hosting capacity of all the nodes.
*/
public Capacity(VJobSet<Node> ns, int m) {
max = m;
nodes = ns;
}
@Override
public String toString() {
return "capacity(" + nodes.pretty() + ", " + max + ")";
}
@Override
public void inject(ReconfigurationProblem core) {
ManagedElementSet<Node> onlines = new SimpleManagedElementSet<Node>();
for (Node n : nodes) {
if (core.getFutureOnlines().contains(n)) {
onlines.add(n);
}
}
TIntHashSet involved = new TIntHashSet();
int[] nIdxs = new int[onlines.size()];
int i = 0;
int minIdx = Integer.MAX_VALUE;
int maxIdx = -1;
for (Node n : onlines) {
int idx = core.getNode(n);
nIdxs[i++] = idx;
if (idx > maxIdx) {
maxIdx = idx;
}
if (idx < minIdx) {
minIdx = idx;
}
involved.add(idx);
}
//TODO: no need to have constraints if max > the amount of future running VMs
//TODO: a false() constraint if the given nodes are all the future online nodes
// while max < the amount of future running VMs
if (max == 0 && !onlines.isEmpty()) {
//max == 0, so we directly remove the nodes
// from the VMs d-slices hoster variable.
for (VirtualMachineActionModel a : core.getVirtualMachineActions()) {
DemandingSlice dSlice = a.getDemandingSlice();
if (dSlice != null) {
for (int x = 0; i < nIdxs.length; x++) {
try {
dSlice.hoster().remVal(nIdxs[x]);
} catch (ContradictionException e) {
VJob.logger.error(e.getMessage(), e);
}
}
}
}
} else if (onlines.size() > 1) { //More than one node, so we restrict the sum of sets cardinality
IntDomainVar card = core.createBoundIntVar("c", 0, max);
List<IntDomainVar> toWatch = new ArrayList<IntDomainVar>();
for (DemandingSlice s : core.getDemandingSlices()) {
IntDomainVar v = s.hoster();
if (v.isInstantiated()) {
int x = v.getVal();
if (x >= minIdx && x <= maxIdx && involved.contains(x)) {
toWatch.add(v);
}
} else {
toWatch.add(v);
}
}
core.post(new AmongGAC(ArrayUtils.append(toWatch.toArray(new IntDomainVar[toWatch.size()]), new IntDomainVar[]{card}), nIdxs, core.getEnvironment()));
//IntDomainVar[] hs = Slices.extractHosters(core.getDemandingSlices());
//core.post(new AmongGAC(ArrayUtils.append(hs, new IntDomainVar[]{card}), nIdxs, core.getEnvironment()));
} else if (!onlines.isEmpty()) { //One node, only restrict the cardinality of its set model.
IntDomainVar card = core.createBoundIntVar("c", max, max);
IntDomainVar[] hs = Slices.extractHosters(core.getDemandingSlices());
core.post(new Occurrence(ArrayUtils.append(hs, new IntDomainVar[]{card}), nIdxs[0], true, false, core.getEnvironment()));
}
}
/**
* Check that the nodes does not host a number of VMs greater
* than the maximum specified
*
* @param configuration the configuration to check
* @return {@code true} if the constraint is satisfied.
*/
@Override
public boolean isSatisfied(Configuration configuration) {
int nb = 0;
for (Node n : nodes.getElements()) {
nb += configuration.getRunnings(n).size();
}
if (nb > max) {
VJob.logger.debug(nodes.pretty() + " host " + nb + " virtual machinew but maximum allowed is " + max);
return false;
}
return true;
}
@Override
public ExplodedSet<VirtualMachine> getAllVirtualMachines() {
return emptyVMs;
}
/**
* If the amount of VMs exceed its capacity, it returns all the hosted VMs
*
* @param configuration the configuration to check
* @return a set of virtual machines that may be empty
*/
@Override
public ExplodedSet<VirtualMachine> getMisPlaced(Configuration configuration) {
ExplodedSet<VirtualMachine> bad = new ExplodedSet<VirtualMachine>();
int nb = 0;
for (Node n : nodes.getElements()) {
ManagedElementSet<VirtualMachine> vms = configuration.getRunnings(n);
nb += vms.size();
bad.addAll(vms); // just in case to avoid a double loop
}
if (nb < getMaximumCapacity()) {
bad.clear(); //Its clean, so no VMs are misplaced
}
return bad;
}
/**
* Get the nodes involved in the constraint.
*
* @return a set of nodes. Should not be empty
*/
@Override
public ExplodedSet<Node> getNodes() {
return this.nodes.flatten();
}
/**
* Get the maximum number of virtual machines
* the set of nodes can host simultaneously
*
* @return a positive integer
*/
public int getMaximumCapacity() {
return this.max;
}
}