/**
* 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.hdfs.server.hightidenode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.HashSet;
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.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.fs.Path;
import org.apache.hadoop.hdfs.protocol.PolicyInfo;
import org.apache.hadoop.hdfs.protocol.PolicyInfo.PathInfo;
/**
* Maintains the configuration xml file that is read into memory.
*/
class ConfigManager {
public static final Log LOG = LogFactory.getLog(
"org.apache.hadoop.hdfs.server.hightidenode.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;
/**
* 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 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
// 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>();
public ConfigManager(Configuration conf) throws IOException, SAXException,
HighTideConfigurationException, ClassNotFoundException, ParserConfigurationException {
this.conf = conf;
this.configFileName = conf.get("hightide.config.file");
this.doReload = conf.getBoolean("hightide.config.reload", true);
this.reloadInterval = conf.getLong("hightide.config.reload.interval", RELOAD_INTERVAL);
if (configFileName == null) {
String msg = "No hightide.config.file given in conf - " +
"the Hadoop HighTideNode cannot run. Aborting....";
LOG.warn(msg);
throw new IOException(msg);
}
reloadConfigs();
lastSuccessfulReload = HighTideNode.now();
lastReloadAttempt = HighTideNode.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 = HighTideNode.now();
if (time > lastReloadAttempt + reloadInterval) {
lastReloadAttempt = time;
File file = null;
try {
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 - " + 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:
* Blank lines and lines starting with # are ignored.
*
* @throws IOException if the config file cannot be read.
* @throws HighTideConfigurationException 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.
* @returns A new set of policy categories.
*/
void reloadConfigs() throws IOException, ParserConfigurationException,
SAXException, ClassNotFoundException, HighTideConfigurationException {
if (configFileName == null) {
return;
}
File file = new File(configFileName);
if (!file.exists()) {
throw new HighTideConfigurationException("Configuration file " + configFileName +
" does not exist.");
}
// 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.error("Reloading config file " + file);
DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
Document doc = builder.parse(file);
Element root = doc.getDocumentElement();
if (!"configuration".equalsIgnoreCase(root.getTagName()))
throw new HighTideConfigurationException("Bad configuration file: " +
"top-level element not <configuration>");
NodeList elements = root.getChildNodes();
Set<PolicyInfo> existingPolicies = new HashSet<PolicyInfo>();
// loop through all the configured source paths.
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (!(node instanceof Element)) {
continue;
}
Element element = (Element)node;
String elementTagName = element.getTagName();
String policyName = null;
if ("srcPath".equalsIgnoreCase(elementTagName)) {
String srcPathPrefix = element.getAttribute("name");
if (srcPathPrefix == null || srcPathPrefix.length() == 0) {
throw new HighTideConfigurationException("Bad configuration file: " +
"srcPath node does not have a path.");
}
PolicyInfo policyInfo = new PolicyInfo(srcPathPrefix, conf);
policyName = srcPathPrefix;
Properties policyProperties;
// loop through all elements of this policy
NodeList policies = element.getChildNodes();
for (int j = 0; j < policies.getLength(); j++) {
Node node1 = policies.item(j);
if (!(node1 instanceof Element)) {
continue;
}
Element policy = (Element)node1;
if ((!"property".equalsIgnoreCase(policy.getTagName())) &&
(!"destPath".equalsIgnoreCase(policy.getTagName()))) {
throw new HighTideConfigurationException("Bad configuration file: " +
"Expecting <property> or <destPath> for srcPath " + srcPathPrefix +
" but found " + policy.getTagName());
}
// parse the <destPath> items
if ("destPath".equalsIgnoreCase(policy.getTagName())) {
String destPath = policy.getAttribute("name");
if (destPath == null) {
throw new HighTideConfigurationException("Bad configuration file: " +
"<destPath> tag should have an attribute named 'name'.");
}
NodeList properties = policy.getChildNodes();
Properties destProperties = new Properties();
for (int k = 0; k < properties.getLength(); k++) {
Node node2 = properties.item(k);
if (!(node2 instanceof Element)) {
continue;
}
Element property = (Element)node2;
String propertyName = property.getTagName();
if (!("property".equalsIgnoreCase(propertyName))) {
throw new HighTideConfigurationException("Bad configuration file: " +
"<destPath> can have only <property> children." +
" but found " + 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) {
throw new HighTideConfigurationException("Bad configuration file: " +
"All property for destPath " + destPath +
" must have name and value ");
}
LOG.info(policyName + "." + pname + " = " + pvalue);
destProperties.setProperty(pname, pvalue);
}
policyInfo.addDestPath(destPath, destProperties);
} else if ("property".equalsIgnoreCase(policy.getTagName())) {
Element property = (Element)node1;
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) {
throw new HighTideConfigurationException("Bad configuration file: " +
"All property for srcPath " + srcPathPrefix +
" must have name and value ");
}
LOG.info(policyName + "." + pname + " = " + pvalue);
policyInfo.setProperty(pname,pvalue);
}
}
existingPolicies.add(policyInfo);
} else {
throw new HighTideConfigurationException("Bad configuration file: " +
"The top level item must be srcPath but found " + elementTagName);
}
}
validateAllPolicies(existingPolicies);
setAllPolicies(existingPolicies);
return;
}
/**
* Get a collection of all policies
*/
public synchronized Collection<PolicyInfo> getAllPolicies() {
return allPolicies;
}
/**
* Set a collection of all policies
*/
protected synchronized void setAllPolicies(Collection<PolicyInfo> value) {
this.allPolicies = value;
}
/**
* Validate a collection of policies
*/
private void validateAllPolicies(Collection<PolicyInfo> all)
throws IOException, NumberFormatException {
for (PolicyInfo pinfo: all) {
Path srcPath = pinfo.getSrcPath();
if (srcPath == null) {
throw new IOException("Unable to find srcPath in policy.");
}
if (pinfo.getProperty("replication") == null) {
throw new IOException("Unable to find replication in policy." +
srcPath);
}
int repl = Integer.parseInt(pinfo.getProperty("replication"));
if (pinfo.getProperty("modTimePeriod") == null) {
throw new IOException("Unable to find modTimePeriod in policy." +
srcPath);
}
long value = Long.parseLong(pinfo.getProperty("modTimePeriod"));
List<PathInfo> dpaths = pinfo.getDestPaths();
if (dpaths == null || dpaths.size() == 0) {
throw new IOException("Unable to find dest in policy." + srcPath);
}
for (PathInfo pp: dpaths) {
if (pp.getPath() == null) {
throw new IOException("Unable to find valid destPath in policy " +
srcPath);
}
if (pp.getProperty("replication") == null) {
throw new IOException("Unable to find dest replication in policy." +
srcPath);
}
repl = Integer.parseInt(pp.getProperty("replication"));
}
}
}
/**
* 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("HighTideNode config reload 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);
}
}
}
}
}