/* * Copyright (c) 2010 Ecole des Mines de Nantes. * * 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.controlLoop; import java.io.File; import java.io.IOException; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import entropy.configuration.Configuration; import entropy.configuration.Configurations; import entropy.configuration.DefaultConfiguration; import entropy.configuration.DefaultManagedElementSet; import entropy.configuration.ManagedElementSet; import entropy.configuration.Node; import entropy.configuration.VirtualMachine; import entropy.decision.AssignmentException; import entropy.decision.predictor.TendencyBasedDecisionModule; import entropy.execution.TimedReconfigurationExecuter; import entropy.monitoring.Monitor; import entropy.monitoring.MonitoringException; import entropy.plan.CustomizablePlannerModule; import entropy.plan.PlanException; import entropy.plan.TimedReconfigurationPlan; import entropy.plan.choco.ChocoCustomRP; import entropy.plan.durationEvaluator.DurationEvaluator; import entropy.plan.parser.PlainTextTimedReconfigurationPlanSerializer; import entropy.vjob.BasicVJob; import entropy.vjob.ExplodedSet; import entropy.vjob.VJob; import entropy.vjob.builder.VJobBuilder; import entropy.vjob.builder.VJobBuilderException; import entropy.vjob.queue.VJobsPool; /** * A basic control loop where the decision module and the planner module is customizable * with several constraints. * * @author Fabien Hermenier */ public class CustomizableControlLoop extends ControlLoop implements Runnable { /** * The queue of VJobs. */ private VJobsPool queue; /** * The master VJob. */ private String masterVJobFile; /** * The different parts of nodes. */ private String partsFile; /** * The builder to make the VJobs. */ private VJobBuilder builder; /** * The executer to perform the actions. */ private TimedReconfigurationExecuter exec; /** * The module to monitor the infrastructure. */ private Monitor monitoring; /** * Perform auto-reconfiguration or not. */ private boolean autoReconf = true; /** * The decision module to estimate the demand of the VMs and their state (don't touch to the state here). */ private TendencyBasedDecisionModule decision; /** * Lock for state analyser. */ private final Object refreshLock = new Object(); /** * The configuration expected by the decision module. */ private Configuration currentExpected; /** * The hosting pool. */ private List<VJob> currentVJob; /** * Delay in seconds between two iterations of the loop. */ private int refreshDelay = 10; /** * Stop the loop ? */ private boolean stop = false; /** * Indicates the loop is performing a reconfiguration. */ private boolean isReconfiguring; /** * The plan module to assign the VMs and plan the actions. */ private CustomizablePlannerModule planner; /** * Make a new loop. * * @param monitor The monitor to use * @param pool the pool of vjobs * @param builder the VJobBuilder to use * @param eval The durationEvaluator to use * @param e the execution module to use */ public CustomizableControlLoop(Monitor monitor, VJobsPool pool, VJobBuilder builder, DurationEvaluator eval, TimedReconfigurationExecuter e) { this.builder = builder; this.monitoring = monitor; exec = e; this.queue = pool; this.decision = new TendencyBasedDecisionModule(); this.planner = new ChocoCustomRP(eval); this.currentVJob = new LinkedList<VJob>(); makeCurrents(); new Thread(this).start(); } /** * Run the loop once. * * @return true to stop the loop. */ @Override public boolean runLoop() { Configuration expected = null; Date timeStamp = Calendar.getInstance().getTime(); TimedReconfigurationPlan plan = null; try { // cur = monitoring.getConfiguration(); //System.err.println(this.masterVJobFile); List<VJob> vjobs; synchronized (refreshLock) { expected = new DefaultConfiguration(currentExpected); vjobs = new LinkedList<VJob>(currentVJob); } builder.getElementBuilder().useConfiguration(expected); //System.err.println("Expected: \n" + expected); getLogger().debug("Offlines: " + expected.getOfflines().size() + ", onlines: " + expected.getOnlines().size() + ", overloaded nodes: " + Configurations.futureOverloadedNodes(expected).size()); getLogger().debug("Runnings: " + expected.getRunnings().size() + ", waitings: " + expected.getWaitings().size() + ", sleeping: " + expected.getSleepings().size()); StringBuilder b = new StringBuilder(); for (Iterator<VJob> ite = vjobs.iterator(); ite.hasNext();) { VJob v = ite.next(); b.append(v.id()); if (ite.hasNext()) { b.append(", "); } } getLogger().debug("VJobs: " + b.toString()); ManagedElementSet<VirtualMachine> allRunnings = new DefaultManagedElementSet<VirtualMachine>(); allRunnings.addAll(expected.getRunnings()); allRunnings.addAll(expected.getWaitings()); if (!autoReconf) { getLogger().debug("No reconfiguration allowed."); return false; } plan = this.planner.compute(expected, allRunnings, new DefaultManagedElementSet<VirtualMachine>(), //expected.getWaitings(), expected.getSleepings(), new DefaultManagedElementSet<VirtualMachine>(), expected.getOnlines(), expected.getOfflines(), vjobs); if (plan.size() > 0) { getLogger().debug(plan.size() + " actions to execute:\n" + plan); isReconfiguring = true; exec.start(plan); isReconfiguring = false; } else { getLogger().info("No reconfiguration is necessary"); } } catch (PlanException e) { getLogger().error(e.getMessage(), e); } finally { if (expected != null) { if (getLogger().isDebugEnabled()) { String file = logConfiguration(expected, timeStamp, "src"); getLogger().info("Source configuration available into '" + file + "'"); } if (plan != null && plan.size() > 0) { String file = logPlan(plan, timeStamp, "plan"); getLogger().info("Plan available into '" + file + "'"); } } } return false; } /** * Set the timeout of the decision module. * * @param seconds the maximum duration of the solving process in seconds. */ public void setAssignTimeout(int seconds) { this.decision.setTimeout(seconds); } /** * Get the timeout value of the decision module. * * @return a duration, in seconds */ public int getAssignTimeout() { return this.decision.getTimeout(); } /** * Set the timeout of the plan module. * * @param seconds the maximum duration of the solving process in seconds. */ public void setPlanTimeout(int seconds) { this.planner.setTimeLimit(seconds); } /** * Get the timeout value of the plan module. * * @return a duration, in seconds */ public int getPlanTimeout() { return this.planner.getTimeLimit(); } /** * Set the file that contains the different parts of the infrastructure. * * @param file the path of the file */ public void setMasterVJobFile(String file) { this.masterVJobFile = file; } /** * Get the file that contains the parts of the infrastructure. * * @return the pathname of the file */ public final String getMasterVJobFile() { return this.masterVJobFile; } public final String getPartsFile() { return this.partsFile; } public void setPartsFile(String file) { this.partsFile = file; } public void setPredictionStep(int st) { this.decision.setStep(st); } public int getPredictionStep() { return decision.getStep(); } @Override public void destroy() { this.stop = true; } private void makeCurrents() { try { synchronized (this.refreshLock) { //Get the configuration Configuration cur = monitoring.getConfiguration(); currentExpected = decision.compute(cur); lightConfiguration(currentExpected); //Get the vjobs VJob partsJob; if (getPartsFile() != null && new File(getPartsFile()).exists()) { partsJob = builder.build("master", new File(this.getPartsFile())); } else { partsJob = new BasicVJob("parts"); } builder.setProlog(partsJob); List<VJob> vjobs = queue.getRunningPriorities(); //Decorate the current configuration. Unknown VMs are put //into the waiting state ManagedElementSet<VirtualMachine> allVMs = new DefaultManagedElementSet<VirtualMachine>(); for (VJob v : vjobs) { for (VirtualMachine vm : v.getVirtualMachines()) { if (currentExpected.getAllVirtualMachines().get(vm.getName()) == null) { currentExpected.addWaiting(vm); } allVMs.addAll(v.getVirtualMachines()); } //We add a VMset for each vjob, equals to all the VMs. partsJob.addVirtualMachines(new ExplodedSet<VirtualMachine>("$" + v.id(), v.getVirtualMachines())); } partsJob.addVirtualMachines(new ExplodedSet<VirtualMachine>("$ALL", allVMs)); vjobs.add(0, partsJob); //Add the master vjob at the end of the queue, if it exists if (getMasterVJobFile() != null && new File(this.getMasterVJobFile()).exists()) { VJob masterVJob = builder.build("master", new File(this.getMasterVJobFile())); vjobs.add(masterVJob); } currentVJob.clear(); currentVJob.addAll(vjobs); } } catch (AssignmentException e) { getLogger().error(e.getMessage(), e); } catch (MonitoringException e) { getLogger().error(e.getMessage(), e); } catch (IOException e) { getLogger().error(e.getMessage(), e); } catch (VJobBuilderException e) { getLogger().error(e.getMessage(), e); } } @Override public void run() { StateAnalyzer analyzer = new StateAnalyzer("analyze.txt"); while (!stop) { try { Thread.sleep(refreshDelay * 1000L); makeCurrents(); synchronized (this.refreshLock) { analyzer.analyze(currentExpected, currentVJob, isReconfiguring); getLogger().debug("Refreshing expected configuration & vjobs"); } //Wait } catch (InterruptedException e) { getLogger().warn(e.getMessage(), e); } } } /** * Log a plan into a file. * If an error occurs, it is logged at the error level * * @param p the plan to store * @param timeStamp the timeStamp for the configuration * @param suffix the suffix of the log file * @return the pathname of the log file */ public String logPlan(TimedReconfigurationPlan p, Date timeStamp, String suffix) { if (getLogsDir() != null) { String filename = this.getLogsDir() + "/" + DATE_FORMAT.format(timeStamp) + "/" + HOUR_FORMAT.format(timeStamp) + "-" + suffix + ".txt"; try { PlainTextTimedReconfigurationPlanSerializer.getInstance().write(p, filename); } catch (Exception e) { getLogger().warn("Unable to store the plan: " + e.getMessage()); } return filename; } return null; } public static void lightConfiguration(Configuration cfg) { for (Node n : Configurations.currentlyOverloadedNodes(cfg)) { //get the amount of the overload int over = -n.getCPUCapacity(); for (VirtualMachine vm : cfg.getRunnings(n)) { over += vm.getCPUConsumption(); } //Reduce the VMs while (over > 0) { for (int i = 0; i < cfg.getRunnings(n).size(); i++) { VirtualMachine vm = cfg.getRunnings(n).get(i); int step = over / 10 + 1; if (vm.getCPUConsumption() > step) { over -= vm.getCPUConsumption(); vm.setCPUConsumption(vm.getCPUConsumption() - step); over += vm.getCPUConsumption(); } if (over <= 0) { break; } } } } } /** * Set if the loop compute and perform the reconfiguration or stay idle. * * @param allow true to allow reconfiguration */ public void allowReconfiguration(boolean allow) { this.autoReconf = allow; } /** * Indicates wether the control loop auto-reconfigure the infrastructure * or not. * * @return true if reconfiguration are computed then performed */ public boolean isAllowedToReconfigure() { return this.autoReconf; } }