
org.btrplace.scheduler.choco.DefaultReconfigurationProblem Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scheduler-choco Show documentation
Show all versions of scheduler-choco Show documentation
Implementation of the VM scheduler that use
the Constraint Programming solver CHOCO to compute solutions.
The newest version!
/*
* Copyright 2024 The BtrPlace Authors. All rights reserved.
* Use of this source code is governed by a LGPL-style
* license that can be found in the LICENSE.txt file.
*/
package org.btrplace.scheduler.choco;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.btrplace.model.*;
import org.btrplace.plan.DefaultReconfigurationPlan;
import org.btrplace.plan.ReconfigurationPlan;
import org.btrplace.scheduler.InconsistentSolutionException;
import org.btrplace.scheduler.SchedulerException;
import org.btrplace.scheduler.SchedulerModelingException;
import org.btrplace.scheduler.UnstatableProblemException;
import org.btrplace.scheduler.choco.duration.DurationEvaluators;
import org.btrplace.scheduler.choco.transition.*;
import org.btrplace.scheduler.choco.view.AliasedCumulatives;
import org.btrplace.scheduler.choco.view.ChocoView;
import org.btrplace.scheduler.choco.view.Cumulatives;
import org.btrplace.scheduler.choco.view.Packing;
import org.chocosolver.memory.IEnvironment;
import org.chocosolver.solver.ResolutionPolicy;
import org.chocosolver.solver.Solution;
import org.chocosolver.solver.Solver;
import org.chocosolver.solver.search.loop.monitors.IMonitorSolution;
import org.chocosolver.solver.search.strategy.Search;
import org.chocosolver.solver.search.strategy.selectors.values.IntDomainMin;
import org.chocosolver.solver.search.strategy.selectors.values.RealDomainMiddle;
import org.chocosolver.solver.search.strategy.selectors.values.SetDomainMin;
import org.chocosolver.solver.search.strategy.selectors.variables.FirstFail;
import org.chocosolver.solver.search.strategy.selectors.variables.InputOrder;
import org.chocosolver.solver.search.strategy.selectors.variables.Occurrence;
import org.chocosolver.solver.search.strategy.strategy.IntStrategy;
import org.chocosolver.solver.search.strategy.strategy.RealStrategy;
import org.chocosolver.solver.search.strategy.strategy.SetStrategy;
import org.chocosolver.solver.search.strategy.strategy.StrategiesSequencer;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.impl.BitsetIntVarImpl;
import org.chocosolver.util.ESat;
import org.chocosolver.util.tools.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Default implementation of {@link ReconfigurationProblem}.
*
* @author Fabien Hermenier
*/
public class DefaultReconfigurationProblem implements ReconfigurationProblem {
private static final Logger LOGGER = LoggerFactory.getLogger("ChocoRP");
private boolean useLabels = false;
private IntVar objective;
private final Model model;
private final Solver solver;
private final org.chocosolver.solver.Model csp;
private final Set ready;
private final Set running;
private final Set sleeping;
private final Set killed;
private Set manageable;
private final Set misplaced;
private List vms;
private TObjectIntHashMap revVMs;
private List nodes;
private TObjectIntHashMap revNodes;
private final IntVar start;
private final IntVar end;
private List vmActions;
private List nodeActions;
private final DurationEvaluators durEval;
private List vmsCountOnNodes;
private ResolutionPolicy solvingPolicy;
private final TransitionFactory amFactory;
private final Map coreViews;
private final List solutions;
private final StopButton stopButton;
private static final String MULTIPLE_DESTINATION_STATES_MSG = "multiple destination state for {}: {} and {}";
/**
* Make a new RP where the next state for every VM is indicated.
* If the state for a VM is omitted, it is considered as unchanged
*
* @param m the initial model
* @param ps parameters to customize the problem
* @param ready the VMs that must be in the ready state
* @param running the VMs that must be in the running state
* @param sleeping the VMs that must be in the sleeping state
* @param killed the VMs that must be killed
* @param preRooted the VMs that can be managed by the solver when they are already running and they must keep running
* @param misplaced the VMs that are misplaced by views and constraints implementing.
* @throws org.btrplace.scheduler.SchedulerException if an error occurred
* @see DefaultReconfigurationProblemBuilder to ease the instantiation process
*/
DefaultReconfigurationProblem(Model m,
Parameters ps,
Set ready,
Set running,
Set sleeping,
Set killed,
Set preRooted,
Set misplaced) throws SchedulerException {
this.ready = new HashSet<>(ready);
this.running = new HashSet<>(running);
this.sleeping = new HashSet<>(sleeping);
this.killed = new HashSet<>(killed);
this.manageable = new HashSet<>(preRooted);
this.useLabels = ps.getVerbosity() > 0;
this.amFactory = ps.getTransitionFactory();
model = m;
durEval = ps.getDurationEvaluators();
IEnvironment env = ps.getEnvironmentFactory().build(m);
csp = new org.chocosolver.solver.Model(env, "", ps.chocoSettings());
solver = csp.getSolver();
start = fixed(0, "RP.start");
end = csp.intVar(makeVarLabel("RP.end"), 0, ps.getMaxEnd(), true);
this.solvingPolicy = ResolutionPolicy.SATISFACTION;
objective = null;
this.solutions = new ArrayList<>();
fillElements();
makeCardinalityVariables();
makeNodeTransitions();
makeVMTransitions();
manageable = Collections.unmodifiableSet(manageable);
this.misplaced = Collections.unmodifiableSet(misplaced);
coreViews = new HashMap<>();
for (Class extends ChocoView> c : ps.getChocoViews()) {
try {
ChocoView v = c.newInstance();
v.inject(ps, this);
addView(v);
} catch (Exception e) {
throw new SchedulerModelingException(model, "Unable to instantiate solver-only view '" + c.getSimpleName() + "'", e);
}
}
stopButton = new StopButton();
solver.addStopCriterion(stopButton);
}
@Override
public ReconfigurationPlan solve(int timeLimit, boolean optimize) throws SchedulerException {
//Check for multiple destination state
if (!distinctVMStates()) {
return null;
}
if (!optimize) {
solvingPolicy = ResolutionPolicy.SATISFACTION;
}
linkCardinalityWithSlices();
addContinuousResourceCapacities();
getRequiredView(Packing.VIEW_ID).beforeSolve(this);
getRequiredView(Cumulatives.VIEW_ID).beforeSolve(this);
getRequiredView(AliasedCumulatives.VIEW_ID).beforeSolve(this);
//Set the timeout
if (timeLimit > 0) {
solver.limitTime(timeLimit * 1000L);
}
if (solver.getSearch() == null) {
defaultHeuristic();
}
solver.plugMonitor((IMonitorSolution) () -> {
Solution s = new Solution(csp);
s.record();
solutions.add(s);
});
if (solvingPolicy == ResolutionPolicy.SATISFACTION) {
solver.findSolution();
} else {
solver.findOptimalSolution(objective, solvingPolicy.equals(ResolutionPolicy.MAXIMIZE));
}
if (solver.isFeasible() == ESat.UNDEFINED) {
//We don't know if the CSP has a solution
throw new UnstatableProblemException(model, timeLimit);
}
return makeResultingPlan();
}
/**
* Check if every VM has a single destination state
*
* @return {@code true} if states are distinct
*/
private boolean distinctVMStates() {
boolean ok = vms.size() == running.size() + sleeping.size() + ready.size() + killed.size();
//It is sure there is no solution as a VM cannot have multiple destination state
Map states = new HashMap<>();
for (VM v : running) {
states.put(v, VMState.RUNNING);
}
for (VM v : ready) {
VMState prev = states.put(v, VMState.READY);
if (prev != null) {
getLogger().debug(MULTIPLE_DESTINATION_STATES_MSG, v, prev, VMState.READY);
}
}
for (VM v : sleeping) {
VMState prev = states.put(v, VMState.SLEEPING);
if (prev != null) {
getLogger().debug(MULTIPLE_DESTINATION_STATES_MSG, v, prev, VMState.SLEEPING);
}
}
for (VM v : killed) {
VMState prev = states.put(v, VMState.KILLED);
if (prev != null) {
getLogger().debug(MULTIPLE_DESTINATION_STATES_MSG, v, prev, VMState.KILLED);
}
}
return ok;
}
@Override
public List getComputedSolutions() throws SchedulerException {
return solutions.stream()
.map(s -> buildReconfigurationPlan(s, model))
.collect(Collectors.toList());
}
private ReconfigurationPlan makeResultingPlan() {
//Check for the solution
ESat status = solver.isFeasible();
if (status == ESat.FALSE) {
//It is certain the CSP has no solution
return null;
}
Solution s = null;
if (!solutions.isEmpty()) {
s = solutions.get(solutions.size() - 1);
}
return buildReconfigurationPlan(s, model.copy());
}
/**
* Build a plan for a solution.
* @param s the solution
* @param src the source model
* @return the resulting plan
* @throws SchedulerException if a error occurred
*/
@Override
@SuppressWarnings("squid:S3346")
public ReconfigurationPlan buildReconfigurationPlan(Solution s, Model src) throws SchedulerException {
ReconfigurationPlan plan = new DefaultReconfigurationPlan(src);
for (NodeTransition action : nodeActions) {
action.insertActions(s, plan);
}
for (VMTransition action : vmActions) {
action.insertActions(s, plan);
}
assert plan.isApplyable() : "The following plan cannot be applied:\n" + plan;
assert checkConsistency(s, plan);
return plan;
}
/**
* A naive heuristic to be sure every variables will be instantiated.
*/
private void defaultHeuristic() {
IntStrategy intStrat = Search.intVarSearch(new FirstFail(csp), new IntDomainMin(), csp.retrieveIntVars(true));
SetStrategy setStrat = new SetStrategy(csp.retrieveSetVars(), new InputOrder<>(csp), new SetDomainMin(), true);
RealStrategy realStrat = new RealStrategy(csp.retrieveRealVars(), new Occurrence<>(), new RealDomainMiddle(), 1e-2, true);
solver.setSearch(new StrategiesSequencer(intStrat, realStrat, setStrat));
}
private void addContinuousResourceCapacities() {
TIntArrayList cUse = new TIntArrayList();
TIntArrayList iUse = new TIntArrayList();
for (int j = 0; j < getVMs().size(); j++) {
VMTransition a = vmActions.get(j);
if (a.getDSlice() != null) {
iUse.add(1);
}
if (a.getCSlice() != null) {
cUse.add(1);
}
}
ChocoView v = getView(Cumulatives.VIEW_ID);
if (v == null) {
throw SchedulerModelingException.missingView(model, Cumulatives.VIEW_ID);
}
((Cumulatives) v).addDim(getNbRunningVMs(), cUse.toArray(), iUse.toArray());
}
private void linkCardinalityWithSlices() {
Stream s = vmActions.stream().map(VMTransition::getDSlice).filter(Objects::nonNull);
IntVar[] ds = s.map(Slice::getHoster).toArray(IntVar[]::new);
int[] usages = new int[ds.length];
Arrays.fill(usages, 1);
ChocoView v = getView(Packing.VIEW_ID);
if (v == null) {
throw SchedulerModelingException.missingView(model, Packing.VIEW_ID);
}
((Packing) v).addDim("vmsOnNodes", vmsCountOnNodes, usages, ds);
}
/**
* Create the cardinality variables.
*/
private void makeCardinalityVariables() {
vmsCountOnNodes = new ArrayList<>(nodes.size());
int nbVMs = vms.size();
for (Node n : nodes) {
final IntVar card;
if (useLabels) {
card = csp.intVar(makeVarLabel("nbVMsOn('", n, "')"), 0, nbVMs, true);
} else {
card = csp.intVar("",0, nbVMs, true);
}
vmsCountOnNodes.add(card);
}
vmsCountOnNodes = Collections.unmodifiableList(vmsCountOnNodes);
}
@Override
public final VMState getFutureState(VM v) {
VMTransition t = getVMAction(v);
return t == null ? null : t.getFutureState();
}
@Override
public VMState getSourceState(VM v) {
VMTransition t = getVMAction(v);
return t == null ? null : t.getSourceState();
}
@Override
public NodeState getSourceState(Node n) {
NodeTransition t = getNodeAction(n);
return t == null ? null : t.getSourceState();
}
private void fillElements() {
Set allVMs = new HashSet<>(model.getMapping().getNbVMs());
allVMs.addAll(model.getMapping().getReadyVMs());
allVMs.addAll(ready);
for (final Node no : model.getMapping().getOnlineNodes()) {
allVMs.addAll(model.getMapping().getRunningVMs(no));
allVMs.addAll(model.getMapping().getSleepingVMs(no));
}
vms = new ArrayList<>(allVMs.size());
//0.5f is a default load factor in trove.
revVMs = new TObjectIntHashMap<>(allVMs.size(), 0.5f, -1);
int i = 0;
for (VM vm : allVMs) {
vms.add(vm);
revVMs.put(vm, i++);
}
vms = Collections.unmodifiableList(vms);
nodes = new ArrayList<>();
revNodes = new TObjectIntHashMap<>(nodes.size(), 0.5f, -1);
i = 0;
for (Node n : model.getMapping().getOnlineNodes()) {
nodes.add(n);
revNodes.put(n, i++);
}
for (Node n : model.getMapping().getOfflineNodes()) {
nodes.add(n);
revNodes.put(n, i++);
}
nodes = Collections.unmodifiableList(nodes);
}
private void makeVMTransitions() {
Mapping map = model.getMapping();
vmActions = new ArrayList<>(vms.size());
for (VM vmId : vms) {
VMState curState = map.getState(vmId);
if (curState == null) {
curState = VMState.INIT;
}
VMState nextState;
if (running.contains(vmId)) {
nextState = VMState.RUNNING;
} else if (sleeping.contains(vmId)) {
nextState = VMState.SLEEPING;
} else if (ready.contains(vmId)) {
nextState = VMState.READY;
} else if (killed.contains(vmId)) {
nextState = VMState.KILLED;
} else {
nextState = curState; //by default, maintain state
switch(nextState) {
case READY:
ready.add(vmId);
break;
case RUNNING:
running.add(vmId);
break;
case SLEEPING:
sleeping.add(vmId);
break;
default:
throw new LifeCycleViolationException(model, vmId, curState, nextState);
}
}
VMTransitionBuilder am = amFactory.getBuilder(curState, nextState);
if (am == null) {
throw new LifeCycleViolationException(model, vmId, curState, nextState);
}
VMTransition t = am.build(this, vmId);
vmActions.add(t);
if (t.isManaged()) {
manageable.add(vmId);
}
}
}
private void makeNodeTransitions() {
Mapping m = model.getMapping();
nodeActions = new ArrayList<>(nodes.size());
for (Node nId : nodes) {
NodeState state = m.getState(nId);
NodeTransitionBuilder b = amFactory.getBuilder(state);
if (b == null) {
throw new LifeCycleViolationException(model, nId, state, EnumSet.of(NodeState.OFFLINE, NodeState.ONLINE));
}
nodeActions.add(b.build(this, nId));
}
}
@Override
public int getCurrentVMLocation(int vmIdx) {
VM id = getVM(vmIdx);
if (id == null) {
return -1;
}
Node nodeId = model.getMapping().getVMLocation(id);
return nodeId == null ? -1 : getNode(nodeId);
}
private boolean checkConsistency(Solution s, ReconfigurationPlan p) {
if (p.getDuration() != s.getIntVal(end)) {
String msg = String.format("The plan effective duration (%s) and the computed duration (%s) mismatch",
p.getDuration(), s.getIntVal(end));
throw new InconsistentSolutionException(p, msg);
}
return true;
}
@Override
public List getNbRunningVMs() {
return vmsCountOnNodes;
}
@Override
public final ChocoView getView(String id) {
return coreViews.get(id);
}
@Override
public Collection getViews() {
return coreViews.keySet();
}
@Override
public boolean addView(ChocoView v) {
if (coreViews.containsKey(v.getIdentifier())) {
return false;
}
coreViews.put(v.getIdentifier(), v);
return true;
}
@Override
public Solver getSolver() {
return solver;
}
@Override
public org.chocosolver.solver.Model getModel() {
return csp;
}
@Override
public IntVar makeHostVariable(Object... n) {
return csp.intVar(makeVarLabel(n), 0, nodes.size() - 1, false);
}
@Override
public IntVar makeHostVariable() {
return csp.intVar("", 0, nodes.size() - 1, false);
}
@Override
public IntVar makeHostVariable(List candidates, Object... n) {
if (candidates.size() == 1) {
return makeCurrentNode(candidates.get(0), n);
}
final int[] ids = new int[candidates.size()];
int idx = 0;
for (final Node no : candidates) {
ids[idx++] = getNode(no);
}
// We don't rely on csp.makeInt() as this may lead to unnecessary clone().
final int[] sorted = ArrayUtils.mergeAndSortIfNot(ids);
return new BitsetIntVarImpl(makeVarLabel(n), sorted, csp.ref());
}
@Override
public IntVar makeCurrentHost(VM vmId, Object... n) throws SchedulerException {
return makeCurrentNode(model.getMapping().getVMLocation(vmId), useLabels ? n : "");
}
@Override
public IntVar makeCurrentHost(VM vmId) throws SchedulerException {
return makeCurrentNode(model.getMapping().getVMLocation(vmId));
}
@Override
public IntVar makeCurrentNode(Node nId, Object... n) throws SchedulerException {
int idx = getNode(nId);
if (idx < 0) {
throw new SchedulerModelingException(model, "Unknown node '" + nId + "'");
}
if (useLabels) {
return fixed(idx, makeVarLabel(n));
}
return csp.intVar(idx);
}
@Override
public IntVar makeCurrentNode(Node nId) throws SchedulerException {
int idx = getNode(nId);
if (idx < 0) {
throw new SchedulerModelingException(model, "Unknown node '" + nId + "'");
}
return csp.intVar(idx);
}
@Override
public IntVar makeUnboundedDuration(Object... n) {
return csp.intVar(makeVarLabel(n), 0, end.getUB(), true);
}
@Override
public IntVar makeUnboundedDuration() {
return csp.intVar("", 0, end.getUB(), true);
}
@Override
public IntVar makeDuration(int ub, int lb, Object... n) throws SchedulerException {
return csp.intVar(makeVarLabel(n), lb, ub, true);
}
@Override
public IntVar makeDuration(int ub, int lb) throws SchedulerException {
return csp.intVar("", lb, ub, true);
}
@Override
public final String makeVarLabel(Object... lbl) {
if (!useLabels) {
return "";
}
StringBuilder b = new StringBuilder();
for (Object s : lbl) {
if (s instanceof Object[]) {
for (Object o : (Object[]) s) {
b.append(o);
}
} else {
b.append(s);
}
}
return b.toString();
}
@Override
public final String makeVarLabel(Object lbl) {
if (!useLabels) {
return "";
}
return lbl.toString();
}
public boolean labelVariables() {
return useLabels;
}
@Override
public IntVar fixed(int v, Object... lbl) {
if (useLabels) {
return csp.intVar(makeVarLabel(lbl), v);
}
return csp.intVar(v);
}
@Override
public IntVar fixed(int v, Object lbl) {
if (useLabels) {
return csp.intVar(lbl.toString(), v);
}
return csp.intVar(v);
}
@Override
public Set getManageableVMs() {
return manageable;
}
@Override
public Set getMisplacedVMs() {
return this.misplaced;
}
@Override
public Logger getLogger() {
return LOGGER;
}
@Override
public List getNodeActions() {
return nodeActions;
}
@Override
public DurationEvaluators getDurationEvaluators() {
return durEval;
}
@Override
public List getNodes() {
return nodes;
}
@Override
public List getVMs() {
return vms;
}
@Override
public Model getSourceModel() {
return model;
}
@Override
public Set getFutureRunningVMs() {
return running;
}
@Override
public Set getFutureReadyVMs() {
return ready;
}
@Override
public Set getFutureSleepingVMs() {
return sleeping;
}
@Override
public Set getFutureKilledVMs() {
return killed;
}
@Override
public IntVar getStart() {
return start;
}
@Override
public IntVar getEnd() {
return end;
}
@Override
public int getVM(VM vm) {
return revVMs.get(vm);
}
@Override
public VM getVM(int idx) {
return vms.get(idx);
}
@Override
public int getNode(Node n) {
return revNodes.get(n);
}
@Override
public Node getNode(int idx) {
return nodes.get(idx);
}
@Override
public List getVMActions() {
return vmActions;
}
@Override
public List getVMActions(Collection ids) {
List trans = new ArrayList<>(ids.size());
for (VM v : ids) {
trans.add(getVMAction(v));
}
return trans;
}
@Override
public VMTransition getVMAction(VM id) {
int idx = getVM(id);
return idx < 0 ? null : vmActions.get(idx);
}
@Override
public NodeTransition getNodeAction(Node id) {
int idx = getNode(id);
return idx < 0 ? null : nodeActions.get(idx);
}
@Override
public VM cloneVM(VM vm) {
VM newVM = model.newVM();
if (newVM == null) {
return null;
}
for (Map.Entry e : coreViews.entrySet()) {
e.getValue().cloneVM(vm, newVM);
}
return newVM;
}
@Override
public IntVar getObjective() {
return objective;
}
@Override
public void setObjective(boolean b, IntVar v) {
this.objective = v;
this.solvingPolicy = b ? ResolutionPolicy.MINIMIZE : ResolutionPolicy.MAXIMIZE;
}
@Override
public ResolutionPolicy getResolutionPolicy() {
return this.solvingPolicy;
}
@Override
public void stop() {
stopButton.stopNow();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy