blob: fb15926dce1764a76e432578dcec03d83bf00e42 [file] [log] [blame]
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.util;
import org.apache.hadoop.io.erasurecode.ECSchema;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A EC policy loading tool that loads user defined EC policies from XML file.
*/
@InterfaceAudience.Private
public class ECPolicyLoader {
private static final Logger LOG
= LoggerFactory.getLogger(ECPolicyLoader.class);
private static final int LAYOUT_VERSION = 1;
/**
* Load user defined EC policies from a XML configuration file.
* @param policyFilePath path of EC policy file
* @return all valid EC policies in EC policy file
*/
public List<ErasureCodingPolicy> loadPolicy(String policyFilePath) {
try {
File policyFile = getPolicyFile(policyFilePath);
if (!policyFile.exists()) {
LOG.warn("Not found any EC policy file");
return Collections.emptyList();
}
return loadECPolicies(policyFile);
} catch (ParserConfigurationException | IOException | SAXException e) {
throw new RuntimeException("Failed to load EC policy file: "
+ policyFilePath);
}
}
/**
* Load EC policies from a XML configuration file.
* @param policyFile EC policy file
* @return list of EC policies
* @throws ParserConfigurationException if ParserConfigurationException happen
* @throws IOException if no such EC policy file
* @throws SAXException if the xml file has some invalid elements
*/
private List<ErasureCodingPolicy> loadECPolicies(File policyFile)
throws ParserConfigurationException, IOException, SAXException {
LOG.info("Loading EC policy file " + policyFile);
// Read and parse the EC policy file.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setIgnoringComments(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(policyFile);
Element root = doc.getDocumentElement();
if (!"configuration".equals(root.getTagName())) {
throw new RuntimeException("Bad EC policy configuration file: "
+ "top-level element not <configuration>");
}
List<ErasureCodingPolicy> policies;
if (root.getElementsByTagName("layoutversion").getLength() > 0) {
if (loadLayoutVersion(root) == LAYOUT_VERSION) {
if (root.getElementsByTagName("schemas").getLength() > 0) {
Map<String, ECSchema> schemas = loadSchemas(root);
if (root.getElementsByTagName("policies").getLength() > 0) {
policies = loadPolicies(root, schemas);
} else {
throw new RuntimeException("Bad EC policy configuration file: "
+ "no <policies> element");
}
} else {
throw new RuntimeException("Bad EC policy configuration file: "
+ "no <schemas> element");
}
} else {
throw new RuntimeException("The parse failed because of "
+ "bad layoutversion value");
}
} else {
throw new RuntimeException("Bad EC policy configuration file: "
+ "no <layoutVersion> element");
}
return policies;
}
/**
* Load layoutVersion from root element in the XML configuration file.
* @param root root element
* @return layout version
*/
private int loadLayoutVersion(Element root) {
int layoutVersion;
Text text = (Text) root.getElementsByTagName("layoutversion")
.item(0).getFirstChild();
if (text != null) {
String value = text.getData().trim();
try {
layoutVersion = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Bad layoutVersion value "
+ value + " is found. It should be an integer");
}
} else {
throw new IllegalArgumentException("Value of <layoutVersion> is null");
}
return layoutVersion;
}
/**
* Load schemas from root element in the XML configuration file.
* @param root root element
* @return EC schema map
*/
private Map<String, ECSchema> loadSchemas(Element root) {
NodeList elements = root.getElementsByTagName("schemas")
.item(0).getChildNodes();
Map<String, ECSchema> schemas = new HashMap<String, ECSchema>();
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (node instanceof Element) {
Element element = (Element) node;
if ("schema".equals(element.getTagName())) {
String schemaId = element.getAttribute("id");
ECSchema schema = loadSchema(element);
if (!schemas.containsValue(schema)) {
schemas.put(schemaId, schema);
} else {
throw new RuntimeException("Repetitive schemas in EC policy"
+ " configuration file: " + schemaId);
}
} else {
throw new RuntimeException("Bad element in EC policy"
+ " configuration file: " + element.getTagName());
}
}
}
return schemas;
}
/**
* Load EC policies from root element in the XML configuration file.
* @param root root element
* @param schemas schema map
* @return EC policy list
*/
private List<ErasureCodingPolicy> loadPolicies(
Element root, Map<String, ECSchema> schemas) {
NodeList elements = root.getElementsByTagName("policies")
.item(0).getChildNodes();
List<ErasureCodingPolicy> policies = new ArrayList<ErasureCodingPolicy>();
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (node instanceof Element) {
Element element = (Element) node;
if ("policy".equals(element.getTagName())) {
ErasureCodingPolicy policy = loadPolicy(element, schemas);
if (!policies.contains(policy)) {
policies.add(policy);
} else {
LOG.warn("Repetitive policies in EC policy configuration file: "
+ policy.toString());
}
} else {
throw new RuntimeException("Bad element in EC policy configuration"
+ " file: " + element.getTagName());
}
}
}
return policies;
}
/**
* Path to the XML file containing user defined EC policies. If the path is
* relative, it is searched for in the classpath.
* @param policyFilePath path of EC policy file
* @return EC policy file
*/
private File getPolicyFile(String policyFilePath)
throws MalformedURLException {
File policyFile = new File(policyFilePath);
if (!policyFile.isAbsolute()) {
URL url = new URL(policyFilePath);
if (!url.getProtocol().equalsIgnoreCase("file")) {
throw new RuntimeException(
"EC policy file " + url
+ " found on the classpath is not on the local filesystem.");
} else {
policyFile = new File(url.getPath());
}
}
return policyFile;
}
/**
* Load a schema from a schema element in the XML configuration file.
* @param element EC schema element
* @return ECSchema
*/
private ECSchema loadSchema(Element element) {
Map<String, String> schemaOptions = new HashMap<String, String>();
NodeList fields = element.getChildNodes();
for (int i = 0; i < fields.getLength(); i++) {
Node fieldNode = fields.item(i);
if (fieldNode instanceof Element) {
Element field = (Element) fieldNode;
String tagName = field.getTagName();
if ("k".equals(tagName)) {
tagName = "numDataUnits";
} else if ("m".equals(tagName)) {
tagName = "numParityUnits";
}
// Get the nonnull text value.
Text text = (Text) field.getFirstChild();
if (text != null) {
String value = text.getData().trim();
schemaOptions.put(tagName, value);
} else {
throw new IllegalArgumentException("Value of <" + tagName
+ "> is null");
}
}
}
return new ECSchema(schemaOptions);
}
/**
* Load a EC policy from a policy element in the XML configuration file.
* @param element EC policy element
* @param schemas all valid schemas of the EC policy file
* @return EC policy
*/
private ErasureCodingPolicy loadPolicy(Element element,
Map<String, ECSchema> schemas) {
NodeList fields = element.getChildNodes();
ECSchema schema = null;
int cellSize = 0;
for (int i = 0; i < fields.getLength(); i++) {
Node fieldNode = fields.item(i);
if (fieldNode instanceof Element) {
Element field = (Element) fieldNode;
String tagName = field.getTagName();
// Get the nonnull text value.
Text text = (Text) field.getFirstChild();
if (text != null) {
if (!text.isElementContentWhitespace()) {
String value = text.getData().trim();
if ("schema".equals(tagName)) {
schema = schemas.get(value);
} else if ("cellsize".equals(tagName)) {
try {
cellSize = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Bad EC policy cellsize"
+ " value " + value + " is found. It should be an integer");
}
} else {
LOG.warn("Invalid tagName: " + tagName);
}
}
} else {
throw new IllegalArgumentException("Value of <" + tagName
+ "> is null");
}
}
}
if (schema != null && cellSize > 0) {
return new ErasureCodingPolicy(schema, cellSize);
} else {
throw new RuntimeException("Bad policy is found in"
+ " EC policy configuration file");
}
}
}