/** * 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.raid; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; 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; import org.apache.hadoop.raid.protocol.PolicyInfo; import org.apache.hadoop.fs.Path; /** * Maintains the configuration xml file that is read into memory. */ class ConfigManager { public static final Log LOG = LogFactory.getLog( "org.apache.hadoop.raid.ConfigManager"); /** Time to wait between checks of the config file */ public static final long RELOAD_INTERVAL = 10 * 1000; /** Time to wait between successive runs of all policies */ public static final long RESCAN_INTERVAL = 3600 * 1000; public static final long HAR_PARTFILE_SIZE = 4 * 1024 * 1024 * 1024l; public static final int DISTRAID_MAX_JOBS = 10; public static final int DISTRAID_MAX_FILES = 5000; public static final String DIRRAID_BLOCK_LIMIT_KEY = "hdfs.raid.dir.raid.block.limit"; public static final int DEFAULT_DIRRAID_BLOCK_LIMIT = 5000; /** * Time to wait after the config file has been modified before reloading it * (this is done to prevent loading a file that hasn't been fully written). */ public static final long RELOAD_WAIT = 5 * 1000; private static final String DEFAULT_CONFIG_FILE = "raid.xml"; private Configuration conf; // Hadoop configuration private String configFileName; // Path to config XML file private long lastReloadAttempt; // Last time we tried to reload the config file private long lastSuccessfulReload; // Last time we successfully reloaded config private boolean lastReloadAttemptFailed = false; private long reloadInterval = RELOAD_INTERVAL; private long periodicity; // time between runs of all policies private long harPartfileSize; private int maxJobsPerPolicy; // Max no. of jobs running simultaneously for // a job. private int maxFilesPerJob; // Max no. of files raided by a job. private int maxBlocksPerDirRaidJob; // Max no. of blocks raided by a dir-raid job // the url of the read reconstruction metrics private String readReconstructionMetricsUrl; // Reload the configuration private boolean doReload; private Thread reloadThread; private volatile boolean running = false; // Collection of all configured policies. Collection<PolicyInfo> allPolicies = new ArrayList<PolicyInfo>(); // For unit test ConfigManager() { } public ConfigManager(Configuration conf) throws IOException, SAXException, RaidConfigurationException, ClassNotFoundException, ParserConfigurationException, JSONException { this.conf = conf; this.configFileName = conf.get("raid.config.file"); if (this.configFileName == null) { this.configFileName = DEFAULT_CONFIG_FILE; } this.doReload = conf.getBoolean("raid.config.reload", true); this.reloadInterval = conf.getLong("raid.config.reload.interval", RELOAD_INTERVAL); this.periodicity = conf.getLong("raid.policy.rescan.interval", RESCAN_INTERVAL); this.harPartfileSize = conf.getLong("raid.har.partfile.size", HAR_PARTFILE_SIZE); this.maxJobsPerPolicy = conf.getInt("raid.distraid.max.jobs", DISTRAID_MAX_JOBS); this.maxFilesPerJob = conf.getInt("raid.distraid.max.files", DISTRAID_MAX_FILES); this.maxBlocksPerDirRaidJob = conf.getInt(DIRRAID_BLOCK_LIMIT_KEY, DEFAULT_DIRRAID_BLOCK_LIMIT); this.readReconstructionMetricsUrl = conf.get( "raid.read.reconstruction.metrics.url", ""); reloadConfigs(); lastSuccessfulReload = RaidNode.now(); lastReloadAttempt = RaidNode.now(); running = true; } /** * Reload config file if it hasn't been loaded in a while * Returns true if the file was reloaded. */ public synchronized boolean reloadConfigsIfNecessary() { long time = RaidNode.now(); if (time > lastReloadAttempt + reloadInterval) { lastReloadAttempt = time; try { File file = new File(configFileName); long lastModified = file.lastModified(); if (lastModified > lastSuccessfulReload && time > lastModified + RELOAD_WAIT) { reloadConfigs(); lastSuccessfulReload = time; lastReloadAttemptFailed = false; return true; } } catch (Exception e) { if (!lastReloadAttemptFailed) { LOG.error("Failed to reload config file - " + "will use existing configuration.", e); } lastReloadAttemptFailed = true; } } return false; } /** * Updates the in-memory data structures from the config file. This file is * expected to be in the following whitespace-separated format: * <configuration> <policy name = RaidScanWeekly> <srcPath prefix="hdfs://hadoop.myhost.com:9000/user/warehouse/u_full/*"/> <parentPolicy> RaidScanMonthly</parentPolicy> <property> <name>targetReplication</name> <value>2</value> <description> after RAIDing, decrease the replication factor of the file to this value. </description> </property> <property> <name>metaReplication</name> <value>2</value> <description> the replication factor of the RAID meta file </description> </property> </policy> </configuration> * * Blank lines and lines starting with # are ignored. * * @throws IOException if the config file cannot be read. * @throws RaidConfigurationException if configuration entries are invalid. * @throws ClassNotFoundException if user-defined policy classes cannot be loaded * @throws ParserConfigurationException if XML parser is misconfigured. * @throws SAXException if config file is malformed. * @throws JSONException * @returns A new set of policy categories. */ void reloadConfigs() throws IOException, ParserConfigurationException, SAXException, ClassNotFoundException, RaidConfigurationException, JSONException { JSONObject json = (JSONObject) conf.getJsonConfig(configFileName); if (json != null) { reloadJSONConfigs(json); } else { reloadXmlConfigs(); } } void reloadXmlConfigs() throws IOException, ParserConfigurationException, SAXException, ClassNotFoundException, RaidConfigurationException { if (configFileName == null) { return; } File file = new File(configFileName); if (!file.exists()) { throw new RaidConfigurationException("Configuration file " + configFileName + " does not exist."); } // Create some temporary hashmaps to hold the new allocs, and we only save // them in our fields if we have parsed the entire allocs file successfully. List<PolicyInfo> all = new ArrayList<PolicyInfo>(); long periodicityValue = periodicity; // Read and parse the configuration file. // allow include files in configuration file DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setIgnoringComments(true); docBuilderFactory.setNamespaceAware(true); try { docBuilderFactory.setXIncludeAware(true); } catch (UnsupportedOperationException e) { LOG.error("Failed to set setXIncludeAware(true) for raid parser " + docBuilderFactory + ":" + e, e); } LOG.info("Reloading config file " + file); DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); Document doc = builder.parse(file); Element root = doc.getDocumentElement(); if (!"configuration".equalsIgnoreCase(root.getTagName())) throw new RaidConfigurationException("Bad configuration file: " + "top-level element not <configuration>"); NodeList policies = root.getChildNodes(); Map<String, PolicyInfo> existingPolicies = new HashMap<String, PolicyInfo>(); // loop through all the configured policies. for (int i = 0; i < policies.getLength(); i++) { Node node = policies.item(i); if (!(node instanceof Element)) { continue; } Element policy = (Element)node; if ("policy".equalsIgnoreCase(policy.getTagName())) { String policyName = policy.getAttribute("name"); PolicyInfo curr = new PolicyInfo(policyName, conf); PolicyInfo parent = null; NodeList policyElements = policy.getChildNodes(); for (int j = 0; j < policyElements.getLength(); j++) { Node node1 = policyElements.item(j); if (!(node1 instanceof Element)) { continue; } Element property = (Element) node1; String propertyName = property.getTagName(); if ("srcPath".equalsIgnoreCase(propertyName)) { String srcPathPrefix = property.getAttribute("prefix"); if (srcPathPrefix != null && srcPathPrefix.length() > 0) { curr.setSrcPath(srcPathPrefix); } } else if ("fileList".equalsIgnoreCase(propertyName)) { String text = ((Text)property.getFirstChild()).getData().trim(); LOG.info(policyName + ".fileList = " + text); curr.setFileListPath(new Path(text)); } else if ("codecId".equalsIgnoreCase(propertyName)) { String text = ((Text)property.getFirstChild()).getData().trim(); LOG.info(policyName + ".codecId = " + text); curr.setCodecId(text); } else if ("shouldRaid".equalsIgnoreCase(propertyName)) { String text = ((Text)property.getFirstChild()).getData().trim(); curr.setShouldRaid(Boolean.parseBoolean(text)); } else if ("description".equalsIgnoreCase(propertyName)) { String text = ((Text)property.getFirstChild()).getData().trim(); curr.setDescription(text); } else if ("parentPolicy".equalsIgnoreCase(propertyName)) { String text = ((Text)property.getFirstChild()).getData().trim(); parent = existingPolicies.get(text); } else if ("property".equalsIgnoreCase(propertyName)) { NodeList nl = property.getChildNodes(); String pname=null,pvalue=null; for (int l = 0; l < nl.getLength(); l++){ Node node3 = nl.item(l); if (!(node3 instanceof Element)) { continue; } Element item = (Element) node3; String itemName = item.getTagName(); if ("name".equalsIgnoreCase(itemName)){ pname = ((Text)item.getFirstChild()).getData().trim(); } else if ("value".equalsIgnoreCase(itemName)){ pvalue = ((Text)item.getFirstChild()).getData().trim(); } } if (pname != null && pvalue != null) { LOG.info(policyName + "." + pname + " = " + pvalue); curr.setProperty(pname,pvalue); } } else { LOG.info("Found bad property " + propertyName + " policy name " + policyName + ". Ignoring."); } } // done with all properties of this policy PolicyInfo pinfo; if (parent != null) { pinfo = new PolicyInfo(policyName, conf); pinfo.copyFrom(parent); pinfo.copyFrom(curr); } else { pinfo = curr; } if (pinfo.getSrcPath() != null || pinfo.getFileListPath() != null) { all.add(pinfo); } existingPolicies.put(policyName, pinfo); } } setAllPolicies(all); periodicity = periodicityValue; return; } private void reloadJSONConfigs(JSONObject json) throws JSONException, IOException { if (json == null) { json = (JSONObject) conf.getJsonConfig(configFileName); } ArrayList<PolicyInfo> newAllPolicies = new ArrayList<PolicyInfo>(); JSONArray policyArray = json.getJSONArray("fileListPolicies"); for (int i = 0; i < policyArray.length(); i++) { loadPolicyInfoFromJSON(policyArray.getJSONObject(i), newAllPolicies); } policyArray = json.getJSONArray("srcPathPolicies"); for (int i = 0; i < policyArray.length(); i++) { loadPolicyInfoFromJSON(policyArray.getJSONObject(i), newAllPolicies); } setAllPolicies(newAllPolicies); } private void loadPolicyInfoFromJSON(JSONObject json, Collection<PolicyInfo> policies) throws JSONException, IOException { PolicyInfo policyInfo = new PolicyInfo(json.getString("name"), conf); String key = null; String stringVal = null; Object value = null; for (Iterator<?> keys = json.keys(); keys.hasNext();) { key = (String) keys.next(); if (key == null || key.equals("")) continue; value = json.get(key); if (value instanceof String) { stringVal = (String)value; } else if (value instanceof Integer) { stringVal = new Integer((Integer)value).toString(); } else if (value instanceof Long) { stringVal = new Long((Long)value).toString(); } else if (value instanceof Double) { stringVal = new Double((Double)value).toString(); } else if (value instanceof Boolean) { stringVal = new Boolean((Boolean)value).toString(); } else { LOG.warn("unsupported value in json object: " + value); } if (key.equals("description")) { policyInfo.setDescription(stringVal); } else if (key.equals("codecId")) { policyInfo.setCodecId(stringVal); } else if (key.equals("fileListPath")) { policyInfo.setFileListPath(new Path(stringVal)); } else if (key.equals("srcPath")) { policyInfo.setSrcPath(stringVal); } else if (key.equals("shouldRaid")) { policyInfo.setShouldRaid((Boolean)value); } else { policyInfo.setProperty(key, stringVal); } } policies.add(policyInfo); } public synchronized long getPeriodicity() { return periodicity; } public synchronized long getHarPartfileSize() { return harPartfileSize; } public synchronized int getMaxJobsPerPolicy() { return maxJobsPerPolicy; } public synchronized int getMaxFilesPerJob() { return maxFilesPerJob; } public synchronized int getMaxBlocksPerDirRaidJob() { return maxBlocksPerDirRaidJob; } public synchronized String getReadReconstructionMetricsUrl() { return readReconstructionMetricsUrl; } /** * Get a collection of all policies */ public synchronized Collection<PolicyInfo> getAllPolicies() { return new ArrayList<PolicyInfo>(allPolicies); } /** * Set a collection of all policies */ protected synchronized void setAllPolicies(Collection<PolicyInfo> value) { this.allPolicies = value; } /** * Start a background thread to reload the config file */ void startReload() { if (doReload) { reloadThread = new UpdateThread(); reloadThread.start(); } } /** * Stop the background thread that reload the config file */ void stopReload() throws InterruptedException { if (reloadThread != null) { running = false; reloadThread.interrupt(); reloadThread.join(); reloadThread = null; } } /** * A thread which reloads the config file. */ private class UpdateThread extends Thread { private UpdateThread() { super("Raid update thread"); } public void run() { while (running) { try { Thread.sleep(reloadInterval); reloadConfigsIfNecessary(); } catch (InterruptedException e) { // do nothing } catch (Exception e) { LOG.error("Failed to reload config file ", e); } } } } /** * Find the PolicyInfo corresponding to a given policy name * @param policyName the name of a policy * @return PolicyInfo if there is a matched policy; null otherwise */ PolicyInfo getPolicy(String policyName) { for (PolicyInfo policy : allPolicies) { if (policyName.equals(policy.getName())) { return policy; } } return null; } }