/* * Copyright (c) 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.plan.choco; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import entropy.configuration.Configuration; import entropy.configuration.Configurations; import entropy.configuration.ManagedElementSet; import entropy.configuration.Node; import entropy.configuration.SimpleManagedElementSet; import entropy.configuration.VirtualMachine; import entropy.plan.CustomizablePlannerModule; import entropy.plan.Plan; import entropy.plan.PlanException; import entropy.plan.SolutionStatistics; import entropy.plan.SolvingStatistics; import entropy.plan.TimedReconfigurationPlan; import entropy.plan.action.Action; import entropy.plan.choco.actionModel.ActionModel; import entropy.plan.choco.actionModel.ActionModels; import entropy.plan.choco.actionModel.VirtualMachineActionModel; import entropy.plan.choco.actionModel.slice.Slice; import entropy.plan.durationEvaluator.DurationEvaluationException; import entropy.plan.durationEvaluator.DurationEvaluator; import entropy.vjob.PlacementConstraint; import entropy.vjob.VJob; /** * A CustomizablePlannerModule based on Choco. * * @author Fabien Hermenier */ public class ChocoCustomRP extends CustomizablePlannerModule { private List<SConstraint> costConstraints; /** * The model. */ private ReconfigurationProblem model; private boolean repair = true; private List<VJob> queue; /** * Make a new plan module. * * @param eval to evaluate the duration of the actions. */ public ChocoCustomRP(DurationEvaluator eval) { super(eval); costConstraints = new LinkedList<SConstraint>(); } /** * Get the model. * * @return the model to express constraints. */ public ReconfigurationProblem getModel() { return this.model; } @Override public List<SolutionStatistics> getSolutionsStatistics() { if (model == null) { return new ArrayList<SolutionStatistics>(); } return this.model.getSolutionsStatistics(); } /** * @return some statistics about the solving process */ @Override public SolvingStatistics getSolvingStatistics() { if (model == null) { return SolvingStatistics.getStatisticsForNotSolvingProcess(); } return model.getSolvingStatistics(); } @SuppressWarnings("deprecation") @Override public TimedReconfigurationPlan compute(Configuration src, ManagedElementSet<VirtualMachine> run, ManagedElementSet<VirtualMachine> wait, ManagedElementSet<VirtualMachine> sleep, ManagedElementSet<VirtualMachine> stop, ManagedElementSet<Node> on, ManagedElementSet<Node> off, List<VJob> q) throws PlanException { long st = System.currentTimeMillis(); queue = q; model = null; ManagedElementSet<VirtualMachine> vms = null; if (repair) { //Look for the VMs to consider vms = new SimpleManagedElementSet<VirtualMachine>(); for (VJob v : queue) { for (PlacementConstraint c : v.getConstraints()) { if (!c.isSatisfied(src)) { vms.addAll(c.getMisPlaced(src)); } } } //Hardcore way for the packing. TODO: externalize //System.err.println("pack issue:" + src.getRunnings(src.getUnacceptableNodes())); vms.addAll(src.getRunnings(Configurations.futureOverloadedNodes(src))); } else { vms = src.getAllVirtualMachines(); } System.currentTimeMillis(); //System.err.println(run); //System.err.println(vms); //System.err.println(on); //System.err.println(off); model = new DefaultReconfigurationProblem(src, run, wait, sleep, stop, vms, on, off, this.getDurationEvaluator()); System.currentTimeMillis(); //System.err.println((t2 - t1) + " ms to make the core RP"); Map<Class, Integer> occurences = new HashMap<Class, Integer>(); int nbConstraints = 0; System.currentTimeMillis(); for (VJob vjob : queue) { for (PlacementConstraint c : vjob.getConstraints()) { try { c.inject(model); if (!occurences.containsKey(c.getClass())) { occurences.put(c.getClass(), 0); } nbConstraints++; occurences.put(c.getClass(), 1 + occurences.get(c.getClass())); } catch (Exception e) { Plan.logger.error(e.getMessage(), e); } } } System.currentTimeMillis(); /* * A pretty print of the problem */ //The elements Plan.logger.debug(run.size() + wait.size() + sleep.size() + stop.size() + " VMs: " + run.size() + " will run; " + wait.size() + " will wait; " + sleep.size() + " will sleep; " + stop.size() + " will be stopped"); Plan.logger.debug(on.size() + off.size() + " nodes: " + on.size() + " to run; " + off.size() + " to halt"); Plan.logger.debug("Manage " + vms.size() + " VMs (" + (repair ? "repair" : "rebuild") + ")"); Plan.logger.debug("Timeout is " + getTimeLimit() + " seconds"); //The constraints StringBuilder b = new StringBuilder(); b.append(nbConstraints + " constraints: "); for (Map.Entry<Class, Integer> e : occurences.entrySet()) { b.append(e.getValue() + " " + e.getKey().getSimpleName() + "; "); } Plan.logger.debug(b.toString()); /** * globalCost is equals to the sum of each action costs. */ IntDomainVar globalCost = model.createBoundIntVar("globalCost", 0, Choco.MAX_UPPER_BOUND); List<ActionModel> allActions = new ArrayList<ActionModel>(); allActions.addAll(model.getVirtualMachineActions()); allActions.addAll(model.getNodeMachineActions()); IntDomainVar[] allCosts = ActionModels.extractCosts(allActions); List<IntDomainVar> varCosts = new ArrayList<IntDomainVar>(); for (int i = 0; i < allCosts.length; i++) { IntDomainVar c = allCosts[i]; if (c.isInstantiated() && c.getVal() == 0) { } else { varCosts.add(c); } } IntDomainVar[] costs = varCosts.toArray(new IntDomainVar[varCosts.size()]); //model.post(model.eq(globalCost, /*model.sum(costs)*/explodedSum(model, costs, 200, true))); SConstraint cs = model.eq(globalCost, explodedSum(model, costs, 100, false)); costConstraints.add(cs); //model.post(cs); cs = model.leq(model.getEnd(), globalCost); //costConstraints.add(cs); model.post(cs); try { setTotalDurationBounds(globalCost, vms); } catch (DurationEvaluationException e) { throw new PlanException(e.getMessage(), e); } updateUB(); //TODO: Set the LB for the horizon && the end of each action //cs = model.leq(model.getEnd(), explodedSum(model, ActionModels.extractDurations(allActions), 200, true)); //costConstraints.add(cs); //model.post(cs); if (getTimeLimit() > 0) { model.setTimeLimit(getTimeLimit() * 1000); } //solver.clearGoals(); new BasicPlacementHeuristic2(globalCost).add(this); new DummyPlacementHeuristic().add(this.getModel()); model.setDoMaximize(false); model.setObjective(globalCost); model.setRestart(false); model.setFirstSolution(false); model.generateSearchStrategy(); ISolutionPool sp = SolutionPoolFactory.makeInfiniteSolutionPool(model.getSearchStrategy()); model.getSearchStrategy().setSolutionPool(sp); long ed = System.currentTimeMillis(); logger.debug((ed - st) + "ms to build the solver " + model.getNbIntConstraints() + " cstr " + model.getNbIntVars() + "+" + model.getNbBooleanVars() + " variables " + model.getNbConstants() + " cte"); model.launch(); Boolean ret = model.isFeasible(); if (ret == null) { throw new PlanException("Unable to check wether a solution exists or not"); } else { Plan.logger.debug("#nodes= " + model.getNodeCount() + ", #backtracks= " + model.getBackTrackCount() + ", #duration= " + model.getTimeCount() + ", #nbsol= " + model.getNbSolutions()); if (Boolean.FALSE.equals(ret)) { throw new PlanException("No solution"); } else { TimedReconfigurationPlan plan = model.extractSolution(); Configuration res = plan.getDestination(); if (Configurations.futureOverloadedNodes(res).size() != 0) { throw new PlanException("Resulting configuration is not viable: Overloaded nodes=" + Configurations.futureOverloadedNodes(res)); } int cost = 0; for (Action a : plan) { cost += a.getFinishMoment(); } if (cost != globalCost.getVal()) { throw new PlanException("Practical cost of the plan (" + cost + ") and objective (" + globalCost.getVal() + ") missmatch:\n" + plan); } for (VJob vjob : queue) { for (PlacementConstraint c : vjob.getConstraints()) { if (!c.isSatisfied(res)) { throw new PlanException("Resulting configuration does not satisfy '" + c.toString() + "'"); } } } return plan; } } } /** * Estimate the lower and the upper bound of model.getEnd() * * @param totalDuration the totalDuration of all the action * @throws entropy.plan.durationEvaluator.DurationEvaluationException * if an error occured during evaluation of the durations. */ private void setTotalDurationBounds(IntDomainVar totalDuration, ManagedElementSet<VirtualMachine> vms) throws DurationEvaluationException { for (VirtualMachine vm : vms) { //FIXME: Bad, should consider the real actions getDurationEvaluator().evaluateMigration(vm); } int sup = ReconfigurationProblem.MAX_TIME;//Math.min(maxEnd, ReconfigurationProblem.MAX_TIME); int min = 0; try { model.getEnd().setInf(min); model.getEnd().setSup(sup); totalDuration.setInf(min); totalDuration.setSup(sup); } catch (Exception e) { Plan.logger.warn(e.getMessage(), e); } Plan.logger.debug(totalDuration.pretty()); Plan.logger.debug(model.getEnd().pretty()); } /** * Update the upper bounds of all the variable to simplify the problem. */ private void updateUB() { int ub = model.getEnd().getSup(); List<ActionModel> allActionModels = new LinkedList<ActionModel>(model.getNodeMachineActions()); allActionModels.addAll(model.getVirtualMachineActions()); try { for (VirtualMachineActionModel a : model.getVirtualMachineActions()) { if (a.end().getSup() > ub) { a.end().setSup(ub); } if (a.start().getSup() > ub) { a.start().setSup(ub); } if (a.getGlobalCost().getSup() > ub) { a.getGlobalCost().setSup(ub); } Slice task = a.getDemandingSlice(); if (task != null) { if (task.end().getSup() > ub) { task.end().setSup(ub); } if (task.start().getSup() > ub) { task.start().setSup(ub); } if (task.duration().getSup() > ub) { task.duration().setSup(ub); } } task = a.getConsumingSlice(); if (task != null) { if (task.end().getSup() > ub) { task.end().setSup(ub); } if (task.start().getSup() > ub) { task.start().setSup(ub); } if (task.duration().getSup() > ub) { task.duration().setSup(ub); } } } } catch (Exception e) { Plan.logger.warn(e.getMessage(), e); } } /** * Get all the vjobs managed by the module * * @return a list of vjobs, may be empty */ public List<VJob> getQueue() { return queue; } /** * Use the repair mode. * * @param b {@code true} to use the repair mode */ public void setRepairMode(boolean b) { this.repair = b; } /** * Make a sum of a large number of variables using * decomposition * * @param m the model * @param vars the variables to sum * @param step the size of the subsums. * @return the variable storing the result of the sum. */ private IntExp explodedSum(ReconfigurationProblem m, IntDomainVar[] vars, int step, boolean post) { int s = vars.length > step ? step : vars.length; IntDomainVar[] subSum = new IntDomainVar[s]; int nbSubs = (int) Math.ceil(vars.length / step); if (vars.length % step != 0) { nbSubs++; } IntDomainVar[] ress = new IntDomainVar[nbSubs]; int curRes = 0; int shiftedX = 0; for (int i = 0; i < vars.length; i++) { subSum[shiftedX++] = vars[i]; if (shiftedX == subSum.length) { IntDomainVar subRes = m.createBoundIntVar("subSum[" + (i - shiftedX + 1) + ".." + i + "]", 0, ReconfigurationProblem.MAX_TIME); SConstraint c = m.eq(subRes, m.sum(subSum)); if (post) { m.post(c); } else { costConstraints.add(c); } ress[curRes++] = subRes; if (i != vars.length - 1) { int remainder = vars.length - (i + 1); s = remainder > step ? step : remainder; subSum = new IntDomainVar[s]; } shiftedX = 0; } } return m.sum(ress); } public List<SConstraint> getCostConstraints() { return this.costConstraints; } }