/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.corona; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.SAXException; /** * Reloads corona scheduling parameters periodically. Generates the pools * config if the {@link PoolsConfigDocumentGenerator} class is set. * Needs to be thread safe * * The following is a corona.xml example: * * <?xml version="1.0"?> * <configuration> * <defaultSchedulingMode>FAIR</defaultSchedulingMode> * <nodeLocalityWaitMAP>0</nodeLocalityWaitMAP> * <rackLocalityWaitMAP>5000</rackLocalityWaitMAP> * <preemptedTaskMaxRunningTime>60000</preemptedTaskMaxRunningTime> * <shareStarvingRatio>0.9</shareStarvingRatio> * <starvingTimeForShare>60000</starvingTimeForShare> * <starvingTimeForMinimum>30000</starvingTimeForMinimum> * <group name="group_a"> * <minMAP>200</minMAP> * <minMAP>100</minMAP> * <minREDUCE>100</minREDUCE> * <maxMAP>200</maxMAP> * <maxREDUCE>200</maxREDUCE> * <pool name="pool_sla"> * <minMAP>100</minMAP> * <minREDUCE>100</minREDUCE> * <maxMAP>200</maxMAP> * <maxREDUCE>200</maxREDUCE> * <weight>2.0</weight> * <schedulingMode>FIFO</schedulingMode> * </pool> * <pool name="pool_nonsla"> * </pool> * </group> * <group name ="group_b"> * <maxMAP>200</maxMAP> * <maxREDUCE>200</maxREDUCE> * <weight>3.0</weight> * </group> * </configuration> * * Note that the type strings "MAP" and "REDUCE" must be * defined in {@link CoronaConf} */ public class ConfigManager { /** Configuration xml tag name */ public static final String CONFIGURATION_TAG_NAME = "configuration"; /** Redirect xml tag name */ public static final String REDIRECT_TAG_NAME = "redirect"; /** Group xml tag name */ public static final String GROUP_TAG_NAME = "group"; /** Pool xml tag name */ public static final String POOL_TAG_NAME = "pool"; /** Scheduling mode xml tag name */ public static final String SCHEDULING_MODE_TAG_NAME = "schedulingMode"; /** Preemptability xml tag name */ public static final String PREEMPTABILITY_MODE_TAG_NAME = "preemptable"; /** Weight xml tag name */ public static final String WEIGHT_TAG_NAME = "weight"; /** Min xml tag name prefix */ public static final String MIN_TAG_NAME_PREFIX = "min"; /** Max xml tag name prefix */ public static final String MAX_TAG_NAME_PREFIX = "max"; /** Name xml attribute */ public static final String NAME_ATTRIBUTE = "name"; /** Source xml attribute (for redirect) */ public static final String SOURCE_ATTRIBUTE = "source"; /** Destination xml attribute (for redirect) */ public static final String DESTINATION_ATTRIBUTE = "destination"; /** Logger */ private static final Log LOG = LogFactory.getLog(ConfigManager.class); /** The default behavior to schedule from nodes to sessions. */ private static final boolean DEFAULT_SCHEDULE_FROM_NODE_TO_SESSION = false; /** The default max running time to consider for preemption */ private static final long DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME = 5 * 60 * 1000L; /** The default number of attempts to preempt */ private static final int DEFAULT_PREEMPTION_ROUNDS = 10; /** The default ratio to consider starvation */ private static final double DEFAULT_SHARE_STARVING_RATIO = 0.7; /** The minimum amount of time to wait between preemptions */ public static final long DEFAULT_MIN_PREEMPT_PERIOD = 60 * 1000L; /** * The default number of grants to give out on each iteration of * the Scheduler. */ public static final int DEFAULT_GRANTS_PER_ITERATION = 5000; /** The default allowed starvation time for a share */ public static final long DEFAULT_STARVING_TIME_FOR_SHARE = 5 * 60 * 1000L; /** The default allowed starvation time for a minimum allocation */ public static final long DEFAULT_STARVING_TIME_FOR_MINIMUM = 3 * 60 * 1000L; /** The comparator to use by default */ private static final ScheduleComparator DEFAULT_COMPARATOR = ScheduleComparator.FIFO; /** If defined, generate the pools config document periodically */ private PoolsConfigDocumentGenerator poolsConfigDocumentGenerator; /** The types this configuration is initialized for */ private final Collection<ResourceType> TYPES; /** The classloader to use when looking for resource in the classpath */ private final ClassLoader classLoader; /** Set of configured pool group names */ private Set<String> poolGroupNameSet; /** Set of configured pool info names */ private Set<PoolInfo> poolInfoSet; /** The Map of max allocations for a given type and group */ private TypePoolGroupNameMap<Integer> typePoolGroupNameToMax = new TypePoolGroupNameMap<Integer>(); /** The Map of min allocations for a given type and group */ private TypePoolGroupNameMap<Integer> typePoolGroupNameToMin = new TypePoolGroupNameMap<Integer>(); /** The Map of max allocations for a given type, group, and pool */ private TypePoolInfoMap<Integer> typePoolInfoToMax = new TypePoolInfoMap<Integer>(); /** The Map of min allocations for a given type, group, and pool */ private TypePoolInfoMap<Integer> typePoolInfoToMin = new TypePoolInfoMap<Integer>(); /** The set of pools that can't be preempted */ private Set<PoolInfo> nonPreemptablePools = new HashSet<PoolInfo>(); /** The Map of node locality wait times for different resource types */ private Map<ResourceType, Long> typeToNodeWait; /** The Map of rack locality wait times for different resource types */ private Map<ResourceType, Long> typeToRackWait; /** The Map of comparators configurations for the pools */ private Map<PoolInfo, ScheduleComparator> poolInfoToComparator; /** The Map of the weights configuration for the pools */ private Map<PoolInfo, Double> poolInfoToWeight; /** The Map of redirections (source -> target) for PoolInfo objects */ private Map<PoolInfo, PoolInfo> poolInfoToRedirect; /** The default comparator for the schedulables within the pool */ private ScheduleComparator defaultComparator; /** The ratio of the share to consider starvation */ private double shareStarvingRatio; /** The allowed starvation time for the share */ private long starvingTimeForShare; /** The minimum period between preemptions. */ private long minPreemptPeriod; /** Tasks to schedule in one iteration of the scheduler */ private volatile int grantsPerIteration = DEFAULT_GRANTS_PER_ITERATION; /** The allowed starvation time for the min allocation */ private long starvingTimeForMinimum; /** The max time of the task to consider for preemption */ private long preemptedTaskMaxRunningTime; /** The number of times to iterate over pools trying to preempt */ private int preemptionRounds; /** Match nodes to session */ private boolean scheduleFromNodeToSession; /** The flag for the reload thread */ private volatile boolean running = true; /** The thread that monitors and reloads the config file */ private ReloadThread reloadThread; /** The last timestamp when the config was successfully loaded */ private long lastSuccessfulReload = -1L; /** Corona conf used to get static config */ private final CoronaConf conf; /** Pools reload period ms */ private final long poolsReloadPeriodMs; /** Config reload period ms */ private final long configReloadPeriodMs; /** The name of the general config file to use */ private volatile String configFileName; /** The name of the pools config file to use */ private volatile String poolsConfigFileName; /** * The main constructor for the config manager given the types and the name * of the config file to use * @param types the types to initialize the configuration for */ public ConfigManager( Collection<ResourceType> types, CoronaConf conf) { this.TYPES = types; this.conf = conf; Class<?> poolsConfigDocumentGeneratorClass = conf.getPoolsConfigDocumentGeneratorClass(); if (poolsConfigDocumentGeneratorClass != null) { try { this.poolsConfigDocumentGenerator = (PoolsConfigDocumentGenerator) poolsConfigDocumentGeneratorClass.newInstance(); poolsConfigDocumentGenerator.initialize(conf); } catch (InstantiationException e) { LOG.warn("Failed to instantiate " + poolsConfigDocumentGeneratorClass, e); } catch (IllegalAccessException e) { LOG.warn("Failed to instantiate " + poolsConfigDocumentGeneratorClass, e); } } else { poolsConfigDocumentGenerator = null; } poolsReloadPeriodMs = conf.getPoolsReloadPeriodMs(); configReloadPeriodMs = conf.getConfigReloadPeriodMs(); LOG.info("ConfigManager: PoolsConfigDocumentGenerator class = " + poolsConfigDocumentGeneratorClass + ", poolsReloadPeriodMs = " + poolsReloadPeriodMs + ", configReloadPeriodMs = " + configReloadPeriodMs); typeToNodeWait = new EnumMap<ResourceType, Long>(ResourceType.class); typeToRackWait = new EnumMap<ResourceType, Long>(ResourceType.class); defaultComparator = DEFAULT_COMPARATOR; shareStarvingRatio = DEFAULT_SHARE_STARVING_RATIO; minPreemptPeriod = DEFAULT_MIN_PREEMPT_PERIOD; grantsPerIteration = DEFAULT_GRANTS_PER_ITERATION; starvingTimeForMinimum = DEFAULT_STARVING_TIME_FOR_MINIMUM; starvingTimeForShare = DEFAULT_STARVING_TIME_FOR_SHARE; preemptedTaskMaxRunningTime = DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME; preemptionRounds = DEFAULT_PREEMPTION_ROUNDS; scheduleFromNodeToSession = DEFAULT_SCHEDULE_FROM_NODE_TO_SESSION; reloadThread = new ReloadThread(); reloadThread.setName("Config reload thread"); reloadThread.setDaemon(true); for (ResourceType type : TYPES) { typeToNodeWait.put(type, 0L); typeToRackWait.put(type, 0L); } ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = ConfigManager.class.getClassLoader(); } classLoader = cl; if (poolsConfigDocumentGenerator != null) { if (generatePoolsConfigIfClassSet() == null) { throw new IllegalStateException("Failed to generate the pools " + "config. Must succeed on initialization of ConfigManager."); } } try { findConfigFiles(); reloadAllConfig(); } catch (IOException e) { LOG.error("Failed to load " + configFileName, e); } catch (SAXException e) { LOG.error("Failed to load " + configFileName, e); } catch (ParserConfigurationException e) { LOG.error("Failed to load " + configFileName, e); } } /** * Used for unit tests */ public ConfigManager() { TYPES = null; conf = new CoronaConf(new Configuration()); poolsReloadPeriodMs = conf.getPoolsReloadPeriodMs(); configReloadPeriodMs = conf.getConfigReloadPeriodMs(); classLoader = ConfigManager.class.getClassLoader(); } /** * Find the configuration files as set file names or in the classpath. */ private void findConfigFiles() { if (configFileName == null) { String configFileString = conf.getConfigFile(); File configFile = new File(configFileString); if (configFile.exists()) { configFileName = configFileString; } else { URL u = classLoader.getResource(configFileString); configFileName = (u != null) ? u.getPath() : null; } LOG.info("Attempt to find config file " + configFileString + " as a file and in class loader returned " + configFileName); } if (poolsConfigFileName == null) { String poolsConfigFileString = conf.getPoolsConfigFile(); File poolsConfigFile = new File(poolsConfigFileString); if (poolsConfigFile.exists()) { poolsConfigFileName = poolsConfigFileString; } else { URL u = classLoader.getResource(poolsConfigFileString); poolsConfigFileName = (u != null) ? u.getPath() : null; } LOG.info("Attempt to find pools config file " + poolsConfigFileString + " as a file and in class loader returned " + poolsConfigFileName); } } /** * Start monitoring the configuration for the updates */ public void start() { reloadThread.start(); } /** * Stop monitoring and reloading of the configuration */ public void close() { running = false; reloadThread.interrupt(); } /** * Is this a configured pool group? * @param poolGroup Name of the pool group to check * @return True if configured, false otherwise */ public synchronized boolean isConfiguredPoolGroup(String poolGroup) { if (poolGroupNameSet == null) { return false; } return poolGroupNameSet.contains(poolGroup); } /** * Is this a configured pool info? * @param poolInfo Pool info to check * @return True if configured, false otherwise */ public synchronized boolean isConfiguredPoolInfo(PoolInfo poolInfo) { if (poolInfoSet == null) { return false; } return poolInfoSet.contains(poolInfo); } /** * Get the configured pool infos so that the PoolGroupManager can make sure * they are created for stats and cm.jsp. */ public synchronized Collection<PoolInfo> getConfiguredPoolInfos() { return poolInfoSet; } /** * Get the configured maximum allocation for a given {@link ResourceType} * in a given pool group * @param poolGroupName the name of the pool group * @param type the type of the resource * @return the maximum allocation for the resource in a pool group */ public synchronized int getPoolGroupMaximum(String poolGroupName, ResourceType type) { Integer max = (typePoolGroupNameToMax == null) ? null : typePoolGroupNameToMax.get(type, poolGroupName); return max == null ? Integer.MAX_VALUE : max; } /** * Get the configured minimum allocation for a given {@link ResourceType} * in a given pool group * @param poolGroupName the name of the pool group * @param type the type of the resource * @return the minimum allocation for the resource in a pool group */ public synchronized int getPoolGroupMinimum(String poolGroupName, ResourceType type) { Integer min = (typePoolGroupNameToMin == null) ? null : typePoolGroupNameToMin.get(type, poolGroupName); return min == null ? 0 : min; } /** * Get the configured maximum allocation for a given {@link ResourceType} * in a given pool * @param poolInfo Pool info to check * @param type the type of the resource * @return the maximum allocation for the resource in a pool */ public synchronized int getPoolMaximum(PoolInfo poolInfo, ResourceType type) { Integer max = (typePoolInfoToMax == null) ? null : typePoolInfoToMax.get(type, poolInfo); return max == null ? Integer.MAX_VALUE : max; } /** * Get the configured minimum allocation for a given {@link ResourceType} * in a given pool * @param poolInfo Pool info to check * @param type the type of the resource * @return the minimum allocation for the resource in a pool */ public synchronized int getPoolMinimum(PoolInfo poolInfo, ResourceType type) { Integer min = (typePoolInfoToMin == null) ? null : typePoolInfoToMin.get(type, poolInfo); return min == null ? 0 : min; } public synchronized boolean isPoolPreemptable(PoolInfo poolInfo) { return !nonPreemptablePools.contains(poolInfo); } /** * Get the weight for the pool * @param poolInfo Pool info to check * @return the weight for the pool */ public synchronized double getWeight(PoolInfo poolInfo) { Double weight = (poolInfoToWeight == null) ? null : poolInfoToWeight.get(poolInfo); return weight == null ? 1.0 : weight; } /** * Get a redirected PoolInfo (destination) from the source. Only supports * one level of redirection. * @param poolInfo Pool info to check for a destination * @return Destination pool info if one exists, else return the input */ public synchronized PoolInfo getRedirect(PoolInfo poolInfo) { PoolInfo destination = (poolInfoToRedirect == null) ? poolInfo : poolInfoToRedirect.get(poolInfo); if (destination == null) { return poolInfo; } return destination; } /** * Get a copy of the map of redirects (used for cm.jsp) * * @return Map of redirects otherwise null if none exists */ public synchronized Map<PoolInfo, PoolInfo> getRedirects() { return (poolInfoToRedirect == null) ? null : new HashMap<PoolInfo, PoolInfo>(poolInfoToRedirect); } /** * Get the comparator to use for scheduling sessions within a pool * @param poolInfo Pool info to check * @return the scheduling comparator to use for the pool */ public synchronized ScheduleComparator getComparator(PoolInfo poolInfo) { ScheduleComparator comparator = (poolInfoToComparator == null) ? null : poolInfoToComparator.get(poolInfo); return comparator == null ? defaultComparator : comparator; } public synchronized long getPreemptedTaskMaxRunningTime() { return preemptedTaskMaxRunningTime; } public synchronized boolean getScheduleFromNodeToSession() { return scheduleFromNodeToSession; } public synchronized int getPreemptionRounds() { return preemptionRounds; } public synchronized double getShareStarvingRatio() { return shareStarvingRatio; } public synchronized long getStarvingTimeForShare() { return starvingTimeForShare; } public synchronized long getStarvingTimeForMinimum() { return starvingTimeForMinimum; } /** * Get the locality wait to be used by the scheduler for a given * ResourceType on a given LocalityLevel * @param type the type of the resource * @param level the locality level * @return the time in milliseconds to use as a locality wait for a resource * of a given type on a given level */ public synchronized long getLocalityWait(ResourceType type, LocalityLevel level) { if (level == LocalityLevel.ANY) { return 0L; } Long wait = level == LocalityLevel.NODE ? typeToNodeWait.get(type) : typeToRackWait.get(type); if (wait == null) { throw new IllegalArgumentException("Unknown type:" + type); } return wait; } public long getMinPreemptPeriod() { return minPreemptPeriod; } public int getGrantsPerIteration() { return grantsPerIteration; } /** * The thread that monitors the config file and reloads the configuration * when the file is updated */ private class ReloadThread extends Thread { @Override public void run() { long lastReloadAttempt = -1L; long lastGenerationAttempt = -1L; while (running) { boolean reloadAllConfig = false; long now = ClusterManager.clock.getTime(); if ((poolsConfigDocumentGenerator != null) && (now - lastGenerationAttempt > poolsReloadPeriodMs)) { lastGenerationAttempt = now; generatePoolsConfigIfClassSet(); reloadAllConfig = true; } if (now - lastReloadAttempt > configReloadPeriodMs) { lastReloadAttempt = now; reloadAllConfig = true; } if (reloadAllConfig) { findConfigFiles(); try { reloadAllConfig(); } catch (IOException e) { LOG.error("Failed to load " + configFileName, e); } catch (SAXException e) { LOG.error("Failed to load " + configFileName, e); } catch (ParserConfigurationException e) { LOG.error("Failed to load " + configFileName, e); } } try { Thread.sleep( Math.min(poolsReloadPeriodMs, configReloadPeriodMs) / 10); } catch (InterruptedException e) { LOG.warn("run: Interrupted", e); } } } } /** * Generate the new pools configuration using the configuration generator. * The generated configuration is written to a temporary file and then * atomically renamed to the specified destination file. * This function may be called concurrently and it is safe to do so because * of the atomic rename to the destination file. * * @return Md5 of the generated file or null if generation failed. */ public String generatePoolsConfigIfClassSet() { if (poolsConfigDocumentGenerator == null) { return null; } Document document = poolsConfigDocumentGenerator.generatePoolsDocument(); if (document == null) { LOG.warn("generatePoolsConfig: Did not generate a valid pools xml file"); return null; } // Write the content into a temporary xml file and rename to the // expected file. File tempXmlFile; try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setAttribute("indent-number", new Integer(2)); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); DOMSource source = new DOMSource(document); tempXmlFile = File.createTempFile("tmpPoolsConfig", "xml"); if (LOG.isDebugEnabled()) { StreamResult stdoutResult = new StreamResult(System.out); transformer.transform(source, stdoutResult); } StreamResult result = new StreamResult(tempXmlFile); transformer.transform(source, result); String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex( new FileInputStream(tempXmlFile)); File destXmlFile = new File(conf.getPoolsConfigFile()); boolean success = tempXmlFile.renameTo(destXmlFile); LOG.info("generatePoolConfig: Renamed generated file " + tempXmlFile.getAbsolutePath() + " to " + destXmlFile.getAbsolutePath() + " returned " + success + " with md5sum " + md5); return md5; } catch (TransformerConfigurationException e) { LOG.warn("generatePoolConfig: Failed to write file", e); } catch (IOException e) { LOG.warn("generatePoolConfig: Failed to write file", e); } catch (TransformerException e) { LOG.warn("generatePoolConfig: Failed to write file", e); } return null; } /** * Reload the general configuration and update all in-memory values. Should * be invoked under synchronization. * * @throws IOException * @throws SAXException * @throws ParserConfigurationException */ private void reloadConfig() throws IOException, SAXException, ParserConfigurationException { Map<ResourceType, Long> newTypeToNodeWait; Map<ResourceType, Long> newTypeToRackWait; ScheduleComparator newDefaultComparator = DEFAULT_COMPARATOR; double newShareStarvingRatio = DEFAULT_SHARE_STARVING_RATIO; long newMinPreemptPeriod = DEFAULT_MIN_PREEMPT_PERIOD; int newGrantsPerIteration = DEFAULT_GRANTS_PER_ITERATION; long newStarvingTimeForMinimum = DEFAULT_STARVING_TIME_FOR_MINIMUM; long newStarvingTimeForShare = DEFAULT_STARVING_TIME_FOR_SHARE; long newPreemptedTaskMaxRunningTime = DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME; int newPreemptionRounds = DEFAULT_PREEMPTION_ROUNDS; boolean newScheduleFromNodeToSession = DEFAULT_SCHEDULE_FROM_NODE_TO_SESSION; newTypeToNodeWait = new EnumMap<ResourceType, Long>(ResourceType.class); newTypeToRackWait = new EnumMap<ResourceType, Long>(ResourceType.class); Map<PoolInfo, PoolInfo> newPoolInfoToRedirect = new HashMap<PoolInfo, PoolInfo>(); for (ResourceType type : TYPES) { newTypeToNodeWait.put(type, 0L); newTypeToRackWait.put(type, 0L); } Element root = getRootElement(configFileName); NodeList elements = root.getChildNodes(); for (int i = 0; i < elements.getLength(); ++i) { Node node = elements.item(i); if (!(node instanceof Element)) { continue; } Element element = (Element) node; for (ResourceType type : TYPES) { if (matched(element, "nodeLocalityWait" + type)) { long val = Long.parseLong(getText(element)); newTypeToNodeWait.put(type, val); } if (matched(element, "rackLocalityWait" + type)) { long val = Long.parseLong(getText(element)); newTypeToRackWait.put(type, val); } } if (matched(element, "defaultSchedulingMode")) { newDefaultComparator = ScheduleComparator.valueOf(getText(element)); } if (matched(element, "shareStarvingRatio")) { newShareStarvingRatio = Double.parseDouble(getText(element)); if (newShareStarvingRatio < 0 || newShareStarvingRatio > 1.0) { LOG.error("Illegal shareStarvingRatio:" + newShareStarvingRatio); newShareStarvingRatio = DEFAULT_SHARE_STARVING_RATIO; } } if (matched(element, "grantsPerIteration")) { newGrantsPerIteration = Integer.parseInt(getText(element)); if (newMinPreemptPeriod < 0) { LOG.error("Illegal grantsPerIteration: " + newGrantsPerIteration); newGrantsPerIteration = DEFAULT_GRANTS_PER_ITERATION; } } if (matched(element, "minPreemptPeriod")) { newMinPreemptPeriod = Long.parseLong(getText(element)); if (newMinPreemptPeriod < 0) { LOG.error("Illegal minPreemptPeriod: " + newMinPreemptPeriod); newMinPreemptPeriod = DEFAULT_MIN_PREEMPT_PERIOD; } } if (matched(element, "starvingTimeForShare")) { newStarvingTimeForShare = Long.parseLong(getText(element)); if (newStarvingTimeForShare < 0) { LOG.error("Illegal starvingTimeForShare:" + newStarvingTimeForShare); newStarvingTimeForShare = DEFAULT_STARVING_TIME_FOR_SHARE; } } if (matched(element, "starvingTimeForMinimum")) { newStarvingTimeForMinimum = Long.parseLong(getText(element)); if (newStarvingTimeForMinimum < 0) { LOG.error("Illegal starvingTimeForMinimum:" + newStarvingTimeForMinimum); newStarvingTimeForMinimum = DEFAULT_STARVING_TIME_FOR_MINIMUM; } } if (matched(element, "preemptedTaskMaxRunningTime")) { newPreemptedTaskMaxRunningTime = Long.parseLong(getText(element)); if (newPreemptedTaskMaxRunningTime < 0) { LOG.error("Illegal preemptedTaskMaxRunningTime:" + newPreemptedTaskMaxRunningTime); newPreemptedTaskMaxRunningTime = DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME; } } if (matched(element, "preemptionRounds")) { newPreemptionRounds = Integer.parseInt(getText(element)); if (newPreemptionRounds < 0) { LOG.error("Illegal preemptedTaskMaxRunningTime:" + newPreemptionRounds); newPreemptionRounds = DEFAULT_PREEMPTION_ROUNDS; } } if (matched(element, "scheduleFromNodeToSession")) { newScheduleFromNodeToSession = Boolean.parseBoolean(getText(element)); } if (matched(element, REDIRECT_TAG_NAME)) { PoolInfo source = PoolInfo.createPoolInfo( element.getAttribute(SOURCE_ATTRIBUTE)); PoolInfo destination = PoolInfo.createPoolInfo( element.getAttribute(DESTINATION_ATTRIBUTE)); if (source == null || destination == null) { LOG.error("Illegal redirect source " + source + " or destination " + destination); } else { newPoolInfoToRedirect.put(source, destination); } } } synchronized (this) { this.typeToNodeWait = newTypeToNodeWait; this.typeToRackWait = newTypeToRackWait; this.defaultComparator = newDefaultComparator; this.shareStarvingRatio = newShareStarvingRatio; this.minPreemptPeriod = newMinPreemptPeriod; this.grantsPerIteration = newGrantsPerIteration; this.starvingTimeForMinimum = newStarvingTimeForMinimum; this.starvingTimeForShare = newStarvingTimeForShare; this.preemptedTaskMaxRunningTime = newPreemptedTaskMaxRunningTime; this.preemptionRounds = newPreemptionRounds; this.scheduleFromNodeToSession = newScheduleFromNodeToSession; this.poolInfoToRedirect = newPoolInfoToRedirect; } } /** * Reload the pools config and update all in-memory values * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ private void reloadPoolsConfig() throws IOException, SAXException, ParserConfigurationException { Set<String> newPoolGroupNameSet = new HashSet<String>(); Set<PoolInfo> newPoolInfoSet = new HashSet<PoolInfo>(); TypePoolGroupNameMap<Integer> newTypePoolNameGroupToMax = new TypePoolGroupNameMap<Integer>(); TypePoolGroupNameMap<Integer> newTypePoolNameGroupToMin = new TypePoolGroupNameMap<Integer>(); TypePoolInfoMap<Integer> newTypePoolInfoToMax = new TypePoolInfoMap<Integer>(); TypePoolInfoMap<Integer> newTypePoolInfoToMin = new TypePoolInfoMap<Integer>(); Map<PoolInfo, ScheduleComparator> newPoolInfoToComparator = new HashMap<PoolInfo, ScheduleComparator>(); Map<PoolInfo, Double> newPoolInfoToWeight = new HashMap<PoolInfo, Double>(); Element root = getRootElement(poolsConfigFileName); NodeList elements = root.getChildNodes(); for (int i = 0; i < elements.getLength(); ++i) { Node node = elements.item(i); if (!(node instanceof Element)) { continue; } Element element = (Element) node; if (matched(element, GROUP_TAG_NAME)) { String groupName = element.getAttribute(NAME_ATTRIBUTE); if (!newPoolGroupNameSet.add(groupName)) { LOG.debug("Already added group " + groupName); } NodeList groupFields = element.getChildNodes(); for (int j = 0; j < groupFields.getLength(); ++j) { Node groupNode = groupFields.item(j); if (!(groupNode instanceof Element)) { continue; } Element field = (Element) groupNode; for (ResourceType type : TYPES) { if (matched(field, MIN_TAG_NAME_PREFIX + type)) { int val = Integer.parseInt(getText(field)); newTypePoolNameGroupToMin.put(type, groupName, val); } if (matched(field, MAX_TAG_NAME_PREFIX + type)) { int val = Integer.parseInt(getText(field)); newTypePoolNameGroupToMax.put(type, groupName, val); } } if (matched(field, POOL_TAG_NAME)) { PoolInfo poolInfo = new PoolInfo(groupName, field.getAttribute("name")); if (!newPoolInfoSet.add(poolInfo)) { LOG.warn("Already added pool info " + poolInfo); } NodeList poolFields = field.getChildNodes(); for (int k = 0; k < poolFields.getLength(); ++k) { Node poolNode = poolFields.item(k); if (!(poolNode instanceof Element)) { continue; } Element poolField = (Element) poolNode; for (ResourceType type : TYPES) { if (matched(poolField, MIN_TAG_NAME_PREFIX + type)) { int val = Integer.parseInt(getText(poolField)); newTypePoolInfoToMin.put(type, poolInfo, val); } if (matched(poolField, MAX_TAG_NAME_PREFIX + type)) { int val = Integer.parseInt(getText(poolField)); newTypePoolInfoToMax.put(type, poolInfo, val); } } if (matched(poolField, PREEMPTABILITY_MODE_TAG_NAME)) { boolean val = Boolean.parseBoolean(getText(poolField)); if (!val) { nonPreemptablePools.add(poolInfo); } } if (matched(poolField, SCHEDULING_MODE_TAG_NAME)) { ScheduleComparator val = ScheduleComparator.valueOf(getText(poolField)); newPoolInfoToComparator.put(poolInfo, val); } if (matched(poolField, WEIGHT_TAG_NAME)) { double val = Double.parseDouble(getText(poolField)); newPoolInfoToWeight.put(poolInfo, val); } } } } } } synchronized (this) { this.poolGroupNameSet = newPoolGroupNameSet; this.poolInfoSet = Collections.unmodifiableSet(newPoolInfoSet); this.typePoolGroupNameToMax = newTypePoolNameGroupToMax; this.typePoolGroupNameToMin = newTypePoolNameGroupToMin; this.typePoolInfoToMax = newTypePoolInfoToMax; this.typePoolInfoToMin = newTypePoolInfoToMin; this.poolInfoToComparator = newPoolInfoToComparator; this.poolInfoToWeight = newPoolInfoToWeight; } } /** * Reload all the configuration files if the config changed and * set the last successful reload time. Synchronized due to potential * conflict from a fetch pools config http request. * * @return true if the config was reloaded, false otherwise * @throws IOException * @throws SAXException * @throws ParserConfigurationException */ public synchronized boolean reloadAllConfig() throws IOException, SAXException, ParserConfigurationException { if (!isConfigChanged()) { return false; } reloadConfig(); reloadPoolsConfig(); this.lastSuccessfulReload = ClusterManager.clock.getTime(); return true; } /** * Check if the config files have changed since they were last read * @return true if the modification time of the file is greater * than that of the last successful reload, false otherwise */ private boolean isConfigChanged() { if (configFileName == null && poolsConfigFileName == null) { return false; } File file = new File(configFileName); boolean configChanged = (file.lastModified() == 0 || file.lastModified() > lastSuccessfulReload); file = new File(poolsConfigFileName); boolean poolsConfigChanged = (file.lastModified() == 0 || file.lastModified() > lastSuccessfulReload); return configChanged || poolsConfigChanged; } /** * Get the root element of the XML document * @return the root element of the XML document * @throws IOException * @throws SAXException * @throws ParserConfigurationException */ private Element getRootElement(String fileName) throws IOException, SAXException, ParserConfigurationException { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setIgnoringComments(true); DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); Document doc = builder.parse(new File(fileName)); Element root = doc.getDocumentElement(); if (!matched(root, CONFIGURATION_TAG_NAME)) { throw new IOException("Bad " + fileName); } return root; } /** * Check if the element name matches the tagName provided * @param element the xml element * @param tagName the name to check against * @return true if the name of the element matches tagName, false otherwise */ private static boolean matched(Element element, String tagName) { return tagName.equals(element.getTagName()); } /** * Get the text inside of the Xml element * @param element xml element * @return the text inside of the xml element */ private static String getText(Element element) { return ((Text) element.getFirstChild()).getData().trim(); } }