blob: d102375563e467dfffcdac980194112e7164207b [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
*
* 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.tools.offlineImageViewer;
import com.google.common.base.Preconditions;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.ACL_ENTRY_NAME_MASK;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.ACL_ENTRY_NAME_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.ACL_ENTRY_SCOPE_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.ACL_ENTRY_TYPE_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_MASK;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAME_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_EXT_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_EXT_MASK;
import static org.apache.hadoop.hdfs.tools.offlineImageViewer.PBImageXmlWriter.*;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import com.google.common.io.CountingOutputStream;
import com.google.common.primitives.Ints;
import com.google.protobuf.ByteString;
import com.google.protobuf.TextFormat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.ECSchemaProto;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.ErasureCodingPolicyProto;
import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoExpirationProto;
import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoProto;
import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CachePoolInfoProto;
import org.apache.hadoop.hdfs.protocol.proto.XAttrProtos;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SectionName;
import org.apache.hadoop.hdfs.server.namenode.FSImageUtil;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.CacheManagerSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.ErasureCodingSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FilesUnderConstructionSection.FileUnderConstructionEntry;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.AclFeatureProto;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.NameSystemSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.DiffEntry;
import org.apache.hadoop.hdfs.server.namenode.NameNodeLayoutVersion;
import org.apache.hadoop.hdfs.util.MD5FileUtils;
import org.apache.hadoop.hdfs.util.XMLUtils;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.MD5Hash;
import org.apache.hadoop.util.StringUtils;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
@InterfaceAudience.Private
@InterfaceStability.Unstable
class OfflineImageReconstructor {
public static final Log LOG =
LogFactory.getLog(OfflineImageReconstructor.class);
/**
* The output stream.
*/
private final CountingOutputStream out;
/**
* A source of XML events based on the input file.
*/
private final XMLEventReader events;
/**
* A map of section names to section handler objects.
*/
private final HashMap<String, SectionProcessor> sections;
/**
* The offset of the start of the current section.
*/
private long sectionStartOffset;
/**
* The FileSummary builder, where we gather information about each section
* we wrote.
*/
private final FileSummary.Builder fileSummaryBld =
FileSummary.newBuilder();
/**
* The string table. See registerStringId for details.
*/
private final HashMap<String, Integer> stringTable = new HashMap<>();
/**
* The date formatter to use with fsimage XML files.
*/
private final SimpleDateFormat isoDateFormat;
/**
* The latest string ID. See registerStringId for details.
*/
private int latestStringId = 0;
private static final String EMPTY_STRING = "";
private OfflineImageReconstructor(CountingOutputStream out,
InputStreamReader reader) throws XMLStreamException {
this.out = out;
XMLInputFactory factory = XMLInputFactory.newInstance();
this.events = factory.createXMLEventReader(reader);
this.sections = new HashMap<>();
this.sections.put(NameSectionProcessor.NAME, new NameSectionProcessor());
this.sections.put(ErasureCodingSectionProcessor.NAME,
new ErasureCodingSectionProcessor());
this.sections.put(INodeSectionProcessor.NAME, new INodeSectionProcessor());
this.sections.put(SecretManagerSectionProcessor.NAME,
new SecretManagerSectionProcessor());
this.sections.put(CacheManagerSectionProcessor.NAME,
new CacheManagerSectionProcessor());
this.sections.put(SnapshotDiffSectionProcessor.NAME,
new SnapshotDiffSectionProcessor());
this.sections.put(INodeReferenceSectionProcessor.NAME,
new INodeReferenceSectionProcessor());
this.sections.put(INodeDirectorySectionProcessor.NAME,
new INodeDirectorySectionProcessor());
this.sections.put(FilesUnderConstructionSectionProcessor.NAME,
new FilesUnderConstructionSectionProcessor());
this.sections.put(SnapshotSectionProcessor.NAME,
new SnapshotSectionProcessor());
this.isoDateFormat = PBImageXmlWriter.createSimpleDateFormat();
}
/**
* Read the next tag start or end event.
*
* @param expected The name of the next tag we expect.
* We will validate that the tag has this name,
* unless this string is enclosed in braces.
* @param allowEnd If true, we will also end events.
* If false, end events cause an exception.
*
* @return The next tag start or end event.
*/
private XMLEvent expectTag(String expected, boolean allowEnd)
throws IOException {
XMLEvent ev = null;
while (true) {
try {
ev = events.nextEvent();
} catch (XMLStreamException e) {
throw new IOException("Expecting " + expected +
", but got XMLStreamException", e);
}
switch (ev.getEventType()) {
case XMLEvent.ATTRIBUTE:
throw new IOException("Got unexpected attribute: " + ev);
case XMLEvent.CHARACTERS:
if (!ev.asCharacters().isWhiteSpace()) {
throw new IOException("Got unxpected characters while " +
"looking for " + expected + ": " +
ev.asCharacters().getData());
}
break;
case XMLEvent.END_ELEMENT:
if (!allowEnd) {
throw new IOException("Got unexpected end event " +
"while looking for " + expected);
}
return ev;
case XMLEvent.START_ELEMENT:
if (!expected.startsWith("[")) {
if (!ev.asStartElement().getName().getLocalPart().
equals(expected)) {
throw new IOException("Failed to find <" + expected + ">; " +
"got " + ev.asStartElement().getName().getLocalPart() +
" instead.");
}
}
return ev;
default:
// Ignore other event types like comment, etc.
if (LOG.isTraceEnabled()) {
LOG.trace("Skipping XMLEvent of type " +
ev.getEventType() + "(" + ev + ")");
}
break;
}
}
}
private void expectTagEnd(String expected) throws IOException {
XMLEvent ev = expectTag(expected, true);
if (ev.getEventType() != XMLStreamConstants.END_ELEMENT) {
throw new IOException("Expected tag end event for " + expected +
", but got: " + ev);
}
if (!expected.startsWith("[")) {
String tag = ev.asEndElement().getName().getLocalPart();
if (!tag.equals(expected)) {
throw new IOException("Expected tag end event for " + expected +
", but got tag end event for " + tag);
}
}
}
private static class Node {
private static final String EMPTY = "";
HashMap<String, LinkedList<Node>> children;
String val = EMPTY;
void addChild(String key, Node node) {
if (children == null) {
children = new HashMap<>();
}
LinkedList<Node> cur = children.get(key);
if (cur == null) {
cur = new LinkedList<>();
children.put(key, cur);
}
cur.add(node);
}
Node removeChild(String key) {
if (children == null) {
return null;
}
LinkedList<Node> cur = children.get(key);
if (cur == null) {
return null;
}
Node node = cur.remove();
if ((node == null) || cur.isEmpty()) {
children.remove(key);
}
return node;
}
String removeChildStr(String key) {
Node child = removeChild(key);
if (child == null) {
return null;
}
if ((child.children != null) && (!child.children.isEmpty())) {
throw new RuntimeException("Node " + key + " contains children " +
"of its own.");
}
return child.getVal();
}
Integer removeChildInt(String key) throws IOException {
String str = removeChildStr(key);
if (str == null) {
return null;
}
return Integer.valueOf(str);
}
Long removeChildLong(String key) throws IOException {
String str = removeChildStr(key);
if (str == null) {
return null;
}
return Long.valueOf(str);
}
boolean removeChildBool(String key) throws IOException {
String str = removeChildStr(key);
if (str == null) {
return false;
}
return true;
}
String getRemainingKeyNames() {
if (children == null) {
return "";
}
return StringUtils.join(", ", children.keySet());
}
void verifyNoRemainingKeys(String sectionName) throws IOException {
String remainingKeyNames = getRemainingKeyNames();
if (!remainingKeyNames.isEmpty()) {
throw new IOException("Found unknown XML keys in " +
sectionName + ": " + remainingKeyNames);
}
}
void setVal(String val) {
this.val = val;
}
String getVal() {
return val;
}
String dump() {
StringBuilder bld = new StringBuilder();
if ((children != null) && (!children.isEmpty())) {
bld.append("{");
}
if (val != null) {
bld.append("[").append(val).append("]");
}
if ((children != null) && (!children.isEmpty())) {
String prefix = "";
for (Map.Entry<String, LinkedList<Node>> entry : children.entrySet()) {
for (Node n : entry.getValue()) {
bld.append(prefix);
bld.append(entry.getKey()).append(": ");
bld.append(n.dump());
prefix = ", ";
}
}
bld.append("}");
}
return bld.toString();
}
}
private void loadNodeChildrenHelper(Node parent, String expected,
String terminators[]) throws IOException {
XMLEvent ev = null;
while (true) {
try {
ev = events.peek();
switch (ev.getEventType()) {
case XMLEvent.END_ELEMENT:
if (terminators.length != 0) {
return;
}
events.nextEvent();
return;
case XMLEvent.START_ELEMENT:
String key = ev.asStartElement().getName().getLocalPart();
for (String terminator : terminators) {
if (terminator.equals(key)) {
return;
}
}
events.nextEvent();
Node node = new Node();
parent.addChild(key, node);
loadNodeChildrenHelper(node, expected, new String[0]);
break;
case XMLEvent.CHARACTERS:
String val = XMLUtils.
unmangleXmlString(ev.asCharacters().getData(), true);
parent.setVal(val);
events.nextEvent();
break;
case XMLEvent.ATTRIBUTE:
throw new IOException("Unexpected XML event " + ev);
default:
// Ignore other event types like comment, etc.
if (LOG.isTraceEnabled()) {
LOG.trace("Skipping XMLEvent " + ev);
}
events.nextEvent();
break;
}
} catch (XMLStreamException e) {
throw new IOException("Expecting " + expected +
", but got XMLStreamException", e);
}
}
}
/**
* Load a subtree of the XML into a Node structure.
* We will keep consuming XML events until we exit the current subtree.
* If there are any terminators specified, we will always leave the
* terminating end tag event in the stream.
*
* @param parent The node to fill in.
* @param expected A string to display in exceptions.
* @param terminators Entering any one of these XML tags terminates our
* traversal.
* @throws IOException
*/
private void loadNodeChildren(Node parent, String expected,
String... terminators) throws IOException {
loadNodeChildrenHelper(parent, expected, terminators);
if (LOG.isTraceEnabled()) {
LOG.trace("loadNodeChildren(expected=" + expected +
", terminators=[" + StringUtils.join(",", terminators) +
"]):" + parent.dump());
}
}
/**
* A processor for an FSImage XML section.
*/
private interface SectionProcessor {
/**
* Process this section.
*/
void process() throws IOException;
}
/**
* Processes the NameSection containing last allocated block ID, etc.
*/
private class NameSectionProcessor implements SectionProcessor {
static final String NAME = "NameSection";
@Override
public void process() throws IOException {
Node node = new Node();
loadNodeChildren(node, "NameSection fields");
NameSystemSection.Builder b = NameSystemSection.newBuilder();
Integer namespaceId = node.removeChildInt(NAME_SECTION_NAMESPACE_ID);
if (namespaceId == null) {
throw new IOException("<NameSection> is missing <namespaceId>");
}
b.setNamespaceId(namespaceId);
Long lval = node.removeChildLong(NAME_SECTION_GENSTAMPV1);
if (lval != null) {
b.setGenstampV1(lval);
}
lval = node.removeChildLong(NAME_SECTION_GENSTAMPV2);
if (lval != null) {
b.setGenstampV2(lval);
}
lval = node.removeChildLong(NAME_SECTION_GENSTAMPV1_LIMIT);
if (lval != null) {
b.setGenstampV1Limit(lval);
}
lval = node.removeChildLong(NAME_SECTION_LAST_ALLOCATED_BLOCK_ID);
if (lval != null) {
b.setLastAllocatedBlockId(lval);
}
lval = node.removeChildLong(NAME_SECTION_TXID);
if (lval != null) {
b.setTransactionId(lval);
}
lval = node.removeChildLong(
NAME_SECTION_ROLLING_UPGRADE_START_TIME);
if (lval != null) {
b.setRollingUpgradeStartTime(lval);
}
lval = node.removeChildLong(
NAME_SECTION_LAST_ALLOCATED_STRIPED_BLOCK_ID);
if (lval != null) {
b.setLastAllocatedStripedBlockId(lval);
}
node.verifyNoRemainingKeys("NameSection");
NameSystemSection s = b.build();
if (LOG.isDebugEnabled()) {
LOG.debug(SectionName.NS_INFO.name() + " writing header: {" +
TextFormat.printToString(s) + "}");
}
s.writeDelimitedTo(out);
recordSectionLength(SectionName.NS_INFO.name());
}
}
private class ErasureCodingSectionProcessor implements SectionProcessor {
static final String NAME = "ErasureCodingSection";
@Override
public void process() throws IOException {
Node node = new Node();
loadNodeChildren(node, "ErasureCodingSection fields");
ErasureCodingSection.Builder builder = ErasureCodingSection.newBuilder();
while (true) {
ErasureCodingPolicyProto.Builder policyBuilder =
ErasureCodingPolicyProto.newBuilder();
Node ec = node.removeChild(ERASURE_CODING_SECTION_POLICY);
if (ec == null) {
break;
}
int policyId = ec.removeChildInt(ERASURE_CODING_SECTION_POLICY_ID);
policyBuilder.setId(policyId);
String name = ec.removeChildStr(ERASURE_CODING_SECTION_POLICY_NAME);
policyBuilder.setName(name);
Integer cellSize =
ec.removeChildInt(ERASURE_CODING_SECTION_POLICY_CELL_SIZE);
policyBuilder.setCellSize(cellSize);
String policyState =
ec.removeChildStr(ERASURE_CODING_SECTION_POLICY_STATE);
if (policyState != null) {
policyBuilder.setState(
HdfsProtos.ErasureCodingPolicyState.valueOf(policyState));
}
Node schema = ec.removeChild(ERASURE_CODING_SECTION_SCHEMA);
Preconditions.checkNotNull(schema);
ECSchemaProto.Builder schemaBuilder = ECSchemaProto.newBuilder();
String codecName =
schema.removeChildStr(ERASURE_CODING_SECTION_SCHEMA_CODEC_NAME);
schemaBuilder.setCodecName(codecName);
Integer dataUnits =
schema.removeChildInt(ERASURE_CODING_SECTION_SCHEMA_DATA_UNITS);
schemaBuilder.setDataUnits(dataUnits);
Integer parityUnits = schema.
removeChildInt(ERASURE_CODING_SECTION_SCHEMA_PARITY_UNITS);
schemaBuilder.setParityUnits(parityUnits);
Node options = schema
.removeChild(ERASURE_CODING_SECTION_SCHEMA_OPTIONS);
if (options != null) {
while (true) {
Node option =
options.removeChild(ERASURE_CODING_SECTION_SCHEMA_OPTION);
if (option == null) {
break;
}
String key = option
.removeChildStr(ERASURE_CODING_SECTION_SCHEMA_OPTION_KEY);
String value = option
.removeChildStr(ERASURE_CODING_SECTION_SCHEMA_OPTION_VALUE);
schemaBuilder.addOptions(HdfsProtos.ECSchemaOptionEntryProto
.newBuilder().setKey(key).setValue(value).build());
}
}
policyBuilder.setSchema(schemaBuilder.build());
builder.addPolicies(policyBuilder.build());
}
ErasureCodingSection section = builder.build();
section.writeDelimitedTo(out);
node.verifyNoRemainingKeys("ErasureCodingSection");
recordSectionLength(SectionName.ERASURE_CODING.name());
}
}
private class INodeSectionProcessor implements SectionProcessor {
static final String NAME = "INodeSection";
@Override
public void process() throws IOException {
Node headerNode = new Node();
loadNodeChildren(headerNode, "INodeSection fields", "inode");
INodeSection.Builder b = INodeSection.newBuilder();
Long lval = headerNode.removeChildLong(INODE_SECTION_LAST_INODE_ID);
if (lval != null) {
b.setLastInodeId(lval);
}
Integer expectedNumINodes =
headerNode.removeChildInt(INODE_SECTION_NUM_INODES);
if (expectedNumINodes == null) {
throw new IOException("Failed to find <numInodes> in INodeSection.");
}
b.setNumInodes(expectedNumINodes);
INodeSection s = b.build();
s.writeDelimitedTo(out);
headerNode.verifyNoRemainingKeys("INodeSection");
int actualNumINodes = 0;
while (actualNumINodes < expectedNumINodes) {
try {
expectTag(INODE_SECTION_INODE, false);
} catch (IOException e) {
throw new IOException("Only found " + actualNumINodes +
" <inode> entries out of " + expectedNumINodes, e);
}
actualNumINodes++;
Node inode = new Node();
loadNodeChildren(inode, "INode fields");
INodeSection.INode.Builder inodeBld = processINodeXml(inode);
inodeBld.build().writeDelimitedTo(out);
}
expectTagEnd(INODE_SECTION_NAME);
recordSectionLength(SectionName.INODE.name());
}
}
private INodeSection.INode.Builder processINodeXml(Node node)
throws IOException {
String type = node.removeChildStr(INODE_SECTION_TYPE);
if (type == null) {
throw new IOException("INode XML found with no <type> tag.");
}
INodeSection.INode.Builder inodeBld = INodeSection.INode.newBuilder();
Long id = node.removeChildLong(SECTION_ID);
if (id == null) {
throw new IOException("<inode> found without <id>");
}
inodeBld.setId(id);
String name = node.removeChildStr(SECTION_NAME);
if (name != null) {
inodeBld.setName(ByteString.copyFrom(name, "UTF8"));
}
switch (type) {
case "FILE":
processFileXml(node, inodeBld);
break;
case "DIRECTORY":
processDirectoryXml(node, inodeBld);
break;
case "SYMLINK":
processSymlinkXml(node, inodeBld);
break;
default:
throw new IOException("INode XML found with unknown <type> " +
"tag " + type);
}
node.verifyNoRemainingKeys("inode");
return inodeBld;
}
private void processFileXml(Node node, INodeSection.INode.Builder inodeBld)
throws IOException {
inodeBld.setType(INodeSection.INode.Type.FILE);
INodeSection.INodeFile.Builder bld = createINodeFileBuilder(node);
inodeBld.setFile(bld);
// Will check remaining keys and serialize in processINodeXml
}
private INodeSection.INodeFile.Builder createINodeFileBuilder(Node node)
throws IOException {
INodeSection.INodeFile.Builder bld = INodeSection.INodeFile.newBuilder();
Integer ival = node.removeChildInt(SECTION_REPLICATION);
if (ival != null) {
bld.setReplication(ival);
}
Long lval = node.removeChildLong(INODE_SECTION_MTIME);
if (lval != null) {
bld.setModificationTime(lval);
}
lval = node.removeChildLong(INODE_SECTION_ATIME);
if (lval != null) {
bld.setAccessTime(lval);
}
lval = node.removeChildLong(INODE_SECTION_PREFERRED_BLOCK_SIZE);
if (lval != null) {
bld.setPreferredBlockSize(lval);
}
String perm = node.removeChildStr(INODE_SECTION_PERMISSION);
if (perm != null) {
bld.setPermission(permissionXmlToU64(perm));
}
Node blocks = node.removeChild(INODE_SECTION_BLOCKS);
if (blocks != null) {
while (true) {
Node block = blocks.removeChild(INODE_SECTION_BLOCK);
if (block == null) {
break;
}
bld.addBlocks(createBlockBuilder(block));
}
}
Node fileUnderConstruction =
node.removeChild(INODE_SECTION_FILE_UNDER_CONSTRUCTION);
if (fileUnderConstruction != null) {
INodeSection.FileUnderConstructionFeature.Builder fb =
INodeSection.FileUnderConstructionFeature.newBuilder();
String clientName =
fileUnderConstruction.removeChildStr(INODE_SECTION_CLIENT_NAME);
if (clientName == null) {
throw new IOException("<file-under-construction> found without " +
"<clientName>");
}
fb.setClientName(clientName);
String clientMachine =
fileUnderConstruction
.removeChildStr(INODE_SECTION_CLIENT_MACHINE);
if (clientMachine == null) {
throw new IOException("<file-under-construction> found without " +
"<clientMachine>");
}
fb.setClientMachine(clientMachine);
bld.setFileUC(fb);
}
Node acls = node.removeChild(INODE_SECTION_ACLS);
if (acls != null) {
bld.setAcl(aclXmlToProto(acls));
}
Node xattrs = node.removeChild(INODE_SECTION_XATTRS);
if (xattrs != null) {
bld.setXAttrs(xattrsXmlToProto(xattrs));
}
ival = node.removeChildInt(INODE_SECTION_STORAGE_POLICY_ID);
if (ival != null) {
bld.setStoragePolicyID(ival);
}
String blockType = node.removeChildStr(INODE_SECTION_BLOCK_TYPE);
if(blockType != null) {
switch (blockType) {
case "CONTIGUOUS":
bld.setBlockType(HdfsProtos.BlockTypeProto.CONTIGUOUS);
break;
case "STRIPED":
bld.setBlockType(HdfsProtos.BlockTypeProto.STRIPED);
ival = node.removeChildInt(INODE_SECTION_EC_POLICY_ID);
if (ival != null) {
bld.setErasureCodingPolicyID(ival);
}
break;
default:
throw new IOException("INode XML found with unknown <blocktype> " +
blockType);
}
}
return bld;
}
private HdfsProtos.BlockProto.Builder createBlockBuilder(Node block)
throws IOException {
HdfsProtos.BlockProto.Builder blockBld =
HdfsProtos.BlockProto.newBuilder();
Long id = block.removeChildLong(SECTION_ID);
if (id == null) {
throw new IOException("<block> found without <id>");
}
blockBld.setBlockId(id);
Long genstamp = block.removeChildLong(INODE_SECTION_GENSTAMP);
if (genstamp == null) {
throw new IOException("<block> found without <genstamp>");
}
blockBld.setGenStamp(genstamp);
Long numBytes = block.removeChildLong(INODE_SECTION_NUM_BYTES);
if (numBytes == null) {
throw new IOException("<block> found without <numBytes>");
}
blockBld.setNumBytes(numBytes);
return blockBld;
}
private void processDirectoryXml(Node node,
INodeSection.INode.Builder inodeBld) throws IOException {
inodeBld.setType(INodeSection.INode.Type.DIRECTORY);
INodeSection.INodeDirectory.Builder bld =
createINodeDirectoryBuilder(node);
inodeBld.setDirectory(bld);
// Will check remaining keys and serialize in processINodeXml
}
private INodeSection.INodeDirectory.Builder
createINodeDirectoryBuilder(Node node) throws IOException {
INodeSection.INodeDirectory.Builder bld =
INodeSection.INodeDirectory.newBuilder();
Long lval = node.removeChildLong(INODE_SECTION_MTIME);
if (lval != null) {
bld.setModificationTime(lval);
}
lval = node.removeChildLong(INODE_SECTION_NS_QUOTA);
if (lval != null) {
bld.setNsQuota(lval);
}
lval = node.removeChildLong(INODE_SECTION_DS_QUOTA);
if (lval != null) {
bld.setDsQuota(lval);
}
String perm = node.removeChildStr(INODE_SECTION_PERMISSION);
if (perm != null) {
bld.setPermission(permissionXmlToU64(perm));
}
Node acls = node.removeChild(INODE_SECTION_ACLS);
if (acls != null) {
bld.setAcl(aclXmlToProto(acls));
}
Node xattrs = node.removeChild(INODE_SECTION_XATTRS);
if (xattrs != null) {
bld.setXAttrs(xattrsXmlToProto(xattrs));
}
INodeSection.QuotaByStorageTypeFeatureProto.Builder qf =
INodeSection.QuotaByStorageTypeFeatureProto.newBuilder();
while (true) {
Node typeQuota = node.removeChild(INODE_SECTION_TYPE_QUOTA);
if (typeQuota == null) {
break;
}
INodeSection.QuotaByStorageTypeEntryProto.Builder qbld =
INodeSection.QuotaByStorageTypeEntryProto.newBuilder();
String type = typeQuota.removeChildStr(INODE_SECTION_TYPE);
if (type == null) {
throw new IOException("<typeQuota> was missing <type>");
}
HdfsProtos.StorageTypeProto storageType =
HdfsProtos.StorageTypeProto.valueOf(type);
if (storageType == null) {
throw new IOException("<typeQuota> had unknown <type> " + type);
}
qbld.setStorageType(storageType);
Long quota = typeQuota.removeChildLong(INODE_SECTION_QUOTA);
if (quota == null) {
throw new IOException("<typeQuota> was missing <quota>");
}
qbld.setQuota(quota);
qf.addQuotas(qbld);
}
bld.setTypeQuotas(qf);
return bld;
}
private void processSymlinkXml(Node node,
INodeSection.INode.Builder inodeBld) throws IOException {
inodeBld.setType(INodeSection.INode.Type.SYMLINK);
INodeSection.INodeSymlink.Builder bld =
INodeSection.INodeSymlink.newBuilder();
String perm = node.removeChildStr(INODE_SECTION_PERMISSION);
if (perm != null) {
bld.setPermission(permissionXmlToU64(perm));
}
String target = node.removeChildStr(INODE_SECTION_TARGET);
if (target != null) {
bld.setTarget(ByteString.copyFrom(target, "UTF8"));
}
Long lval = node.removeChildLong(INODE_SECTION_MTIME);
if (lval != null) {
bld.setModificationTime(lval);
}
lval = node.removeChildLong(INODE_SECTION_ATIME);
if (lval != null) {
bld.setAccessTime(lval);
}
inodeBld.setSymlink(bld);
// Will check remaining keys and serialize in processINodeXml
}
private INodeSection.AclFeatureProto.Builder aclXmlToProto(Node acls)
throws IOException {
AclFeatureProto.Builder b = AclFeatureProto.newBuilder();
while (true) {
Node acl = acls.removeChild(INODE_SECTION_ACL);
if (acl == null) {
break;
}
String val = acl.getVal();
AclEntry entry = AclEntry.parseAclEntry(val, true);
int nameId = registerStringId(entry.getName() == null ? EMPTY_STRING
: entry.getName());
int v = ((nameId & ACL_ENTRY_NAME_MASK) << ACL_ENTRY_NAME_OFFSET)
| (entry.getType().ordinal() << ACL_ENTRY_TYPE_OFFSET)
| (entry.getScope().ordinal() << ACL_ENTRY_SCOPE_OFFSET)
| (entry.getPermission().ordinal());
b.addEntries(v);
}
return b;
}
private INodeSection.XAttrFeatureProto.Builder xattrsXmlToProto(Node xattrs)
throws IOException {
INodeSection.XAttrFeatureProto.Builder bld =
INodeSection.XAttrFeatureProto.newBuilder();
while (true) {
Node xattr = xattrs.removeChild(INODE_SECTION_XATTR);
if (xattr == null) {
break;
}
INodeSection.XAttrCompactProto.Builder b =
INodeSection.XAttrCompactProto.newBuilder();
String ns = xattr.removeChildStr(INODE_SECTION_NS);
if (ns == null) {
throw new IOException("<xattr> had no <ns> entry.");
}
int nsIdx = XAttrProtos.XAttrProto.
XAttrNamespaceProto.valueOf(ns).ordinal();
String name = xattr.removeChildStr(SECTION_NAME);
String valStr = xattr.removeChildStr(INODE_SECTION_VAL);
byte[] val = null;
if (valStr == null) {
String valHex = xattr.removeChildStr(INODE_SECTION_VAL_HEX);
if (valHex == null) {
throw new IOException("<xattr> had no <val> or <valHex> entry.");
}
val = new HexBinaryAdapter().unmarshal(valHex);
} else {
val = valStr.getBytes("UTF8");
}
b.setValue(ByteString.copyFrom(val));
// The XAttrCompactProto name field uses a fairly complex format
// to encode both the string table ID of the xattr name and the
// namespace ID. See the protobuf file for details.
int nameId = registerStringId(name);
int encodedName = (nameId << XATTR_NAME_OFFSET) |
((nsIdx & XATTR_NAMESPACE_MASK) << XATTR_NAMESPACE_OFFSET) |
(((nsIdx >> 2) & XATTR_NAMESPACE_EXT_MASK)
<< XATTR_NAMESPACE_EXT_OFFSET);
b.setName(encodedName);
xattr.verifyNoRemainingKeys("xattr");
bld.addXAttrs(b);
}
xattrs.verifyNoRemainingKeys("xattrs");
return bld;
}
private class SecretManagerSectionProcessor implements SectionProcessor {
static final String NAME = "SecretManagerSection";
@Override
public void process() throws IOException {
Node secretHeader = new Node();
loadNodeChildren(secretHeader, "SecretManager fields",
"delegationKey", "token");
SecretManagerSection.Builder b = SecretManagerSection.newBuilder();
Integer currentId =
secretHeader.removeChildInt(SECRET_MANAGER_SECTION_CURRENT_ID);
if (currentId == null) {
throw new IOException("SecretManager section had no <currentId>");
}
b.setCurrentId(currentId);
Integer tokenSequenceNumber = secretHeader.removeChildInt(
SECRET_MANAGER_SECTION_TOKEN_SEQUENCE_NUMBER);
if (tokenSequenceNumber == null) {
throw new IOException("SecretManager section had no " +
"<tokenSequenceNumber>");
}
b.setTokenSequenceNumber(tokenSequenceNumber);
Integer expectedNumKeys = secretHeader.removeChildInt(
SECRET_MANAGER_SECTION_NUM_DELEGATION_KEYS);
if (expectedNumKeys == null) {
throw new IOException("SecretManager section had no " +
"<numDelegationKeys>");
}
b.setNumKeys(expectedNumKeys);
Integer expectedNumTokens =
secretHeader.removeChildInt(SECRET_MANAGER_SECTION_NUM_TOKENS);
if (expectedNumTokens == null) {
throw new IOException("SecretManager section had no " +
"<numTokens>");
}
b.setNumTokens(expectedNumTokens);
secretHeader.verifyNoRemainingKeys("SecretManager");
b.build().writeDelimitedTo(out);
for (int actualNumKeys = 0; actualNumKeys < expectedNumKeys;
actualNumKeys++) {
try {
expectTag(SECRET_MANAGER_SECTION_DELEGATION_KEY, false);
} catch (IOException e) {
throw new IOException("Only read " + actualNumKeys +
" delegation keys out of " + expectedNumKeys, e);
}
SecretManagerSection.DelegationKey.Builder dbld =
SecretManagerSection.DelegationKey.newBuilder();
Node dkey = new Node();
loadNodeChildren(dkey, "Delegation key fields");
Integer id = dkey.removeChildInt(SECTION_ID);
if (id == null) {
throw new IOException("Delegation key stanza <delegationKey> " +
"lacked an <id> field.");
}
dbld.setId(id);
String expiry = dkey.removeChildStr(SECRET_MANAGER_SECTION_EXPIRY);
if (expiry == null) {
throw new IOException("Delegation key stanza <delegationKey> " +
"lacked an <expiry> field.");
}
dbld.setExpiryDate(dateStrToLong(expiry));
String keyHex = dkey.removeChildStr(SECRET_MANAGER_SECTION_KEY);
if (keyHex == null) {
throw new IOException("Delegation key stanza <delegationKey> " +
"lacked a <key> field.");
}
byte[] key = new HexBinaryAdapter().unmarshal(keyHex);
dkey.verifyNoRemainingKeys(SECRET_MANAGER_SECTION_DELEGATION_KEY);
dbld.setKey(ByteString.copyFrom(key));
dbld.build().writeDelimitedTo(out);
}
for (int actualNumTokens = 0; actualNumTokens < expectedNumTokens;
actualNumTokens++) {
try {
expectTag(SECRET_MANAGER_SECTION_TOKEN, false);
} catch (IOException e) {
throw new IOException("Only read " + actualNumTokens +
" tokens out of " + expectedNumTokens, e);
}
SecretManagerSection.PersistToken.Builder tbld =
SecretManagerSection.PersistToken.newBuilder();
Node token = new Node();
loadNodeChildren(token, "PersistToken key fields");
Integer version =
token.removeChildInt(SECRET_MANAGER_SECTION_VERSION);
if (version != null) {
tbld.setVersion(version);
}
String owner = token.removeChildStr(SECRET_MANAGER_SECTION_OWNER);
if (owner != null) {
tbld.setOwner(owner);
}
String renewer =
token.removeChildStr(SECRET_MANAGER_SECTION_RENEWER);
if (renewer != null) {
tbld.setRenewer(renewer);
}
String realUser =
token.removeChildStr(SECRET_MANAGER_SECTION_REAL_USER);
if (realUser != null) {
tbld.setRealUser(realUser);
}
String issueDateStr =
token.removeChildStr(SECRET_MANAGER_SECTION_ISSUE_DATE);
if (issueDateStr != null) {
tbld.setIssueDate(dateStrToLong(issueDateStr));
}
String maxDateStr =
token.removeChildStr(SECRET_MANAGER_SECTION_MAX_DATE);
if (maxDateStr != null) {
tbld.setMaxDate(dateStrToLong(maxDateStr));
}
Integer seqNo =
token.removeChildInt(SECRET_MANAGER_SECTION_SEQUENCE_NUMBER);
if (seqNo != null) {
tbld.setSequenceNumber(seqNo);
}
Integer masterKeyId =
token.removeChildInt(SECRET_MANAGER_SECTION_MASTER_KEY_ID);
if (masterKeyId != null) {
tbld.setMasterKeyId(masterKeyId);
}
String expiryDateStr =
token.removeChildStr(SECRET_MANAGER_SECTION_EXPIRY_DATE);
if (expiryDateStr != null) {
tbld.setExpiryDate(dateStrToLong(expiryDateStr));
}
token.verifyNoRemainingKeys("token");
tbld.build().writeDelimitedTo(out);
}
expectTagEnd(SECRET_MANAGER_SECTION_NAME);
recordSectionLength(SectionName.SECRET_MANAGER.name());
}
private long dateStrToLong(String dateStr) throws IOException {
try {
Date date = isoDateFormat.parse(dateStr);
return date.getTime();
} catch (ParseException e) {
throw new IOException("Failed to parse ISO date string " + dateStr, e);
}
}
}
private class CacheManagerSectionProcessor implements SectionProcessor {
static final String NAME = "CacheManagerSection";
@Override
public void process() throws IOException {
Node node = new Node();
loadNodeChildren(node, "CacheManager fields", "pool", "directive");
CacheManagerSection.Builder b = CacheManagerSection.newBuilder();
Long nextDirectiveId =
node.removeChildLong(CACHE_MANAGER_SECTION_NEXT_DIRECTIVE_ID);
if (nextDirectiveId == null) {
throw new IOException("CacheManager section had no <nextDirectiveId>");
}
b.setNextDirectiveId(nextDirectiveId);
Integer expectedNumPools =
node.removeChildInt(CACHE_MANAGER_SECTION_NUM_POOLS);
if (expectedNumPools == null) {
throw new IOException("CacheManager section had no <numPools>");
}
b.setNumPools(expectedNumPools);
Integer expectedNumDirectives =
node.removeChildInt(CACHE_MANAGER_SECTION_NUM_DIRECTIVES);
if (expectedNumDirectives == null) {
throw new IOException("CacheManager section had no <numDirectives>");
}
b.setNumDirectives(expectedNumDirectives);
b.build().writeDelimitedTo(out);
long actualNumPools = 0;
while (actualNumPools < expectedNumPools) {
try {
expectTag(CACHE_MANAGER_SECTION_POOL, false);
} catch (IOException e) {
throw new IOException("Only read " + actualNumPools +
" cache pools out of " + expectedNumPools, e);
}
actualNumPools++;
Node pool = new Node();
loadNodeChildren(pool, "pool fields", "");
processPoolXml(node);
}
long actualNumDirectives = 0;
while (actualNumDirectives < expectedNumDirectives) {
try {
expectTag(CACHE_MANAGER_SECTION_DIRECTIVE, false);
} catch (IOException e) {
throw new IOException("Only read " + actualNumDirectives +
" cache pools out of " + expectedNumDirectives, e);
}
actualNumDirectives++;
Node pool = new Node();
loadNodeChildren(pool, "directive fields", "");
processDirectiveXml(node);
}
expectTagEnd(CACHE_MANAGER_SECTION_NAME);
recordSectionLength(SectionName.CACHE_MANAGER.name());
}
private void processPoolXml(Node pool) throws IOException {
CachePoolInfoProto.Builder bld = CachePoolInfoProto.newBuilder();
String poolName =
pool.removeChildStr(CACHE_MANAGER_SECTION_POOL_NAME);
if (poolName == null) {
throw new IOException("<pool> found without <poolName>");
}
bld.setPoolName(poolName);
String ownerName =
pool.removeChildStr(CACHE_MANAGER_SECTION_OWNER_NAME);
if (ownerName == null) {
throw new IOException("<pool> found without <ownerName>");
}
bld.setOwnerName(ownerName);
String groupName =
pool.removeChildStr(CACHE_MANAGER_SECTION_GROUP_NAME);
if (groupName == null) {
throw new IOException("<pool> found without <groupName>");
}
bld.setGroupName(groupName);
Integer mode = pool.removeChildInt(CACHE_MANAGER_SECTION_MODE);
if (mode == null) {
throw new IOException("<pool> found without <mode>");
}
bld.setMode(mode);
Long limit = pool.removeChildLong(CACHE_MANAGER_SECTION_LIMIT);
if (limit == null) {
throw new IOException("<pool> found without <limit>");
}
bld.setLimit(limit);
Long maxRelativeExpiry =
pool.removeChildLong(CACHE_MANAGER_SECTION_MAX_RELATIVE_EXPIRY);
if (maxRelativeExpiry == null) {
throw new IOException("<pool> found without <maxRelativeExpiry>");
}
bld.setMaxRelativeExpiry(maxRelativeExpiry);
pool.verifyNoRemainingKeys("pool");
bld.build().writeDelimitedTo(out);
}
private void processDirectiveXml(Node directive) throws IOException {
CacheDirectiveInfoProto.Builder bld =
CacheDirectiveInfoProto.newBuilder();
Long id = directive.removeChildLong(SECTION_ID);
if (id == null) {
throw new IOException("<directive> found without <id>");
}
bld.setId(id);
String path = directive.removeChildStr(SECTION_PATH);
if (path == null) {
throw new IOException("<directive> found without <path>");
}
bld.setPath(path);
Integer replication = directive.removeChildInt(SECTION_REPLICATION);
if (replication == null) {
throw new IOException("<directive> found without <replication>");
}
bld.setReplication(replication);
String pool = directive.removeChildStr(CACHE_MANAGER_SECTION_POOL);
if (path == null) {
throw new IOException("<directive> found without <pool>");
}
bld.setPool(pool);
Node expiration =
directive.removeChild(CACHE_MANAGER_SECTION_EXPIRATION);
if (expiration != null) {
CacheDirectiveInfoExpirationProto.Builder ebld =
CacheDirectiveInfoExpirationProto.newBuilder();
Long millis =
expiration.removeChildLong(CACHE_MANAGER_SECTION_MILLIS);
if (millis == null) {
throw new IOException("cache directive <expiration> found " +
"without <millis>");
}
ebld.setMillis(millis);
if (expiration.removeChildBool(CACHE_MANAGER_SECTION_RELATIVE)) {
ebld.setIsRelative(true);
} else {
ebld.setIsRelative(false);
}
bld.setExpiration(ebld);
}
directive.verifyNoRemainingKeys("directive");
bld.build().writeDelimitedTo(out);
}
}
private class INodeReferenceSectionProcessor implements SectionProcessor {
static final String NAME = "INodeReferenceSection";
@Override
public void process() throws IOException {
// There is no header for this section.
// We process the repeated <ref> elements.
while (true) {
XMLEvent ev = expectTag(INODE_REFERENCE_SECTION_REF, true);
if (ev.isEndElement()) {
break;
}
Node inodeRef = new Node();
FsImageProto.INodeReferenceSection.INodeReference.Builder bld =
FsImageProto.INodeReferenceSection.INodeReference.newBuilder();
loadNodeChildren(inodeRef, "INodeReference");
Long referredId =
inodeRef.removeChildLong(INODE_REFERENCE_SECTION_REFERRED_ID);
if (referredId != null) {
bld.setReferredId(referredId);
}
String name = inodeRef.removeChildStr("name");
if (name != null) {
bld.setName(ByteString.copyFrom(name, "UTF8"));
}
Integer dstSnapshotId = inodeRef.removeChildInt(
INODE_REFERENCE_SECTION_DST_SNAPSHOT_ID);
if (dstSnapshotId != null) {
bld.setDstSnapshotId(dstSnapshotId);
}
Integer lastSnapshotId = inodeRef.removeChildInt(
INODE_REFERENCE_SECTION_LAST_SNAPSHOT_ID);
if (lastSnapshotId != null) {
bld.setLastSnapshotId(lastSnapshotId);
}
inodeRef.verifyNoRemainingKeys("ref");
bld.build().writeDelimitedTo(out);
}
recordSectionLength(SectionName.INODE_REFERENCE.name());
}
}
private class INodeDirectorySectionProcessor implements SectionProcessor {
static final String NAME = "INodeDirectorySection";
@Override
public void process() throws IOException {
// No header for this section
// Process the repeated <directory> elements.
while (true) {
XMLEvent ev = expectTag(INODE_DIRECTORY_SECTION_DIRECTORY, true);
if (ev.isEndElement()) {
break;
}
Node directory = new Node();
FsImageProto.INodeDirectorySection.DirEntry.Builder bld =
FsImageProto.INodeDirectorySection.DirEntry.newBuilder();
loadNodeChildren(directory, "directory");
Long parent = directory.removeChildLong(
INODE_DIRECTORY_SECTION_PARENT);
if (parent != null) {
bld.setParent(parent);
}
while (true) {
Node child = directory.removeChild(
INODE_DIRECTORY_SECTION_CHILD);
if (child == null) {
break;
}
bld.addChildren(Long.parseLong(child.getVal()));
}
while (true) {
Node refChild = directory.removeChild(
INODE_DIRECTORY_SECTION_REF_CHILD);
if (refChild == null) {
break;
}
bld.addRefChildren(Integer.parseInt(refChild.getVal()));
}
directory.verifyNoRemainingKeys("directory");
bld.build().writeDelimitedTo(out);
}
recordSectionLength(SectionName.INODE_DIR.name());
}
}
private class FilesUnderConstructionSectionProcessor
implements SectionProcessor {
static final String NAME = "FileUnderConstructionSection";
@Override
public void process() throws IOException {
// No header for this section type.
// Process the repeated files under construction elements.
while (true) {
XMLEvent ev = expectTag(INODE_SECTION_INODE, true);
if (ev.isEndElement()) {
break;
}
Node fileUnderConstruction = new Node();
loadNodeChildren(fileUnderConstruction, "file under construction");
FileUnderConstructionEntry.Builder bld =
FileUnderConstructionEntry.newBuilder();
Long id = fileUnderConstruction.removeChildLong(SECTION_ID);
if (id != null) {
bld.setInodeId(id);
}
String fullpath =
fileUnderConstruction.removeChildStr(SECTION_PATH);
if (fullpath != null) {
bld.setFullPath(fullpath);
}
fileUnderConstruction.verifyNoRemainingKeys("inode");
bld.build().writeDelimitedTo(out);
}
recordSectionLength(SectionName.FILES_UNDERCONSTRUCTION.name());
}
}
private class SnapshotSectionProcessor implements SectionProcessor {
static final String NAME = "SnapshotSection";
@Override
public void process() throws IOException {
FsImageProto.SnapshotSection.Builder bld =
FsImageProto.SnapshotSection.newBuilder();
Node header = new Node();
loadNodeChildren(header, "SnapshotSection fields", "snapshot");
Integer snapshotCounter = header.removeChildInt(
SNAPSHOT_SECTION_SNAPSHOT_COUNTER);
if (snapshotCounter == null) {
throw new IOException("No <snapshotCounter> entry found in " +
"SnapshotSection header");
}
bld.setSnapshotCounter(snapshotCounter);
Integer expectedNumSnapshots = header.removeChildInt(
SNAPSHOT_SECTION_NUM_SNAPSHOTS);
if (expectedNumSnapshots == null) {
throw new IOException("No <numSnapshots> entry found in " +
"SnapshotSection header");
}
bld.setNumSnapshots(expectedNumSnapshots);
while (true) {
Node sd = header.removeChild(SNAPSHOT_SECTION_SNAPSHOT_TABLE_DIR);
if (sd == null) {
break;
}
Long dir = sd.removeChildLong(SNAPSHOT_SECTION_DIR);
sd.verifyNoRemainingKeys("<dir>");
bld.addSnapshottableDir(dir);
}
header.verifyNoRemainingKeys("SnapshotSection");
bld.build().writeDelimitedTo(out);
int actualNumSnapshots = 0;
while (actualNumSnapshots < expectedNumSnapshots) {
try {
expectTag(SNAPSHOT_SECTION_SNAPSHOT, false);
} catch (IOException e) {
throw new IOException("Only read " + actualNumSnapshots +
" <snapshot> entries out of " + expectedNumSnapshots, e);
}
actualNumSnapshots++;
Node snapshot = new Node();
loadNodeChildren(snapshot, "snapshot fields");
FsImageProto.SnapshotSection.Snapshot.Builder s =
FsImageProto.SnapshotSection.Snapshot.newBuilder();
Integer snapshotId = snapshot.removeChildInt(SECTION_ID);
if (snapshotId == null) {
throw new IOException("<snapshot> section was missing <id>");
}
s.setSnapshotId(snapshotId);
Node snapshotRoot = snapshot.removeChild(SNAPSHOT_SECTION_ROOT);
INodeSection.INode.Builder inodeBld = processINodeXml(snapshotRoot);
s.setRoot(inodeBld);
s.build().writeDelimitedTo(out);
}
expectTagEnd(SNAPSHOT_SECTION_NAME);
recordSectionLength(SectionName.SNAPSHOT.name());
}
}
private class SnapshotDiffSectionProcessor implements SectionProcessor {
static final String NAME = "SnapshotDiffSection";
@Override
public void process() throws IOException {
// No header for this section type.
LOG.debug("Processing SnapshotDiffSection");
while (true) {
XMLEvent ev = expectTag("[diff start tag]", true);
if (ev.isEndElement()) {
String name = ev.asEndElement().getName().getLocalPart();
if (name.equals(SNAPSHOT_DIFF_SECTION_NAME)) {
break;
}
throw new IOException("Got unexpected end tag for " + name);
}
String tagName = ev.asStartElement().getName().getLocalPart();
if (tagName.equals(SNAPSHOT_DIFF_SECTION_DIR_DIFF_ENTRY)) {
processDirDiffEntry();
} else if (tagName.equals(SNAPSHOT_DIFF_SECTION_FILE_DIFF_ENTRY)) {
processFileDiffEntry();
} else {
throw new IOException("SnapshotDiffSection contained unexpected " +
"tag " + tagName);
}
}
recordSectionLength(SectionName.SNAPSHOT_DIFF.name());
}
private void processDirDiffEntry() throws IOException {
LOG.debug("Processing dirDiffEntry");
DiffEntry.Builder headerBld = DiffEntry.newBuilder();
headerBld.setType(DiffEntry.Type.DIRECTORYDIFF);
Node dirDiffHeader = new Node();
loadNodeChildren(dirDiffHeader, "dirDiffEntry fields", "dirDiff");
Long inodeId = dirDiffHeader.removeChildLong(
SNAPSHOT_DIFF_SECTION_INODE_ID);
if (inodeId == null) {
throw new IOException("<dirDiffEntry> contained no <inodeId> entry.");
}
headerBld.setInodeId(inodeId);
Integer expectedDiffs = dirDiffHeader.removeChildInt(
SNAPSHOT_DIFF_SECTION_COUNT);
if (expectedDiffs == null) {
throw new IOException("<dirDiffEntry> contained no <count> entry.");
}
headerBld.setNumOfDiff(expectedDiffs);
dirDiffHeader.verifyNoRemainingKeys("dirDiffEntry");
headerBld.build().writeDelimitedTo(out);
for (int actualDiffs = 0; actualDiffs < expectedDiffs; actualDiffs++) {
try {
expectTag(SNAPSHOT_DIFF_SECTION_DIR_DIFF, false);
} catch (IOException e) {
throw new IOException("Only read " + (actualDiffs + 1) +
" diffs out of " + expectedDiffs, e);
}
Node dirDiff = new Node();
loadNodeChildren(dirDiff, "dirDiff fields");
FsImageProto.SnapshotDiffSection.DirectoryDiff.Builder bld =
FsImageProto.SnapshotDiffSection.DirectoryDiff.newBuilder();
Integer snapshotId = dirDiff.removeChildInt(
SNAPSHOT_DIFF_SECTION_SNAPSHOT_ID);
if (snapshotId != null) {
bld.setSnapshotId(snapshotId);
}
Integer childrenSize = dirDiff.removeChildInt(
SNAPSHOT_DIFF_SECTION_CHILDREN_SIZE);
if (childrenSize == null) {
throw new IOException("Expected to find <childrenSize> in " +
"<dirDiff> section.");
}
bld.setIsSnapshotRoot(dirDiff.removeChildBool(
SNAPSHOT_DIFF_SECTION_IS_SNAPSHOT_ROOT));
bld.setChildrenSize(childrenSize);
String name = dirDiff.removeChildStr(SECTION_NAME);
if (name != null) {
bld.setName(ByteString.copyFrom(name, "UTF8"));
}
Node snapshotCopy = dirDiff.removeChild(
SNAPSHOT_DIFF_SECTION_SNAPSHOT_COPY);
if (snapshotCopy != null) {
bld.setSnapshotCopy(createINodeDirectoryBuilder(snapshotCopy));
}
Integer expectedCreatedListSize = dirDiff.removeChildInt(
SNAPSHOT_DIFF_SECTION_CREATED_LIST_SIZE);
if (expectedCreatedListSize == null) {
throw new IOException("Expected to find <createdListSize> in " +
"<dirDiff> section.");
}
bld.setCreatedListSize(expectedCreatedListSize);
while (true) {
Node deleted = dirDiff.removeChild(
SNAPSHOT_DIFF_SECTION_DELETED_INODE);
if (deleted == null){
break;
}
bld.addDeletedINode(Long.parseLong(deleted.getVal()));
}
while (true) {
Node deleted = dirDiff.removeChild(
SNAPSHOT_DIFF_SECTION_DELETED_INODE_REF);
if (deleted == null){
break;
}
bld.addDeletedINodeRef(Integer.parseInt(deleted.getVal()));
}
bld.build().writeDelimitedTo(out);
// After the DirectoryDiff header comes a list of CreatedListEntry PBs.
int actualCreatedListSize = 0;
while (true) {
Node created = dirDiff.removeChild(
SNAPSHOT_DIFF_SECTION_CREATED);
if (created == null){
break;
}
String cleName = created.removeChildStr(SECTION_NAME);
if (cleName == null) {
throw new IOException("Expected <created> entry to have " +
"a <name> field");
}
created.verifyNoRemainingKeys("created");
FsImageProto.SnapshotDiffSection.CreatedListEntry.newBuilder().
setName(ByteString.copyFrom(cleName, "UTF8")).
build().writeDelimitedTo(out);
actualCreatedListSize++;
}
if (actualCreatedListSize != expectedCreatedListSize) {
throw new IOException("<createdListSize> was " +
expectedCreatedListSize +", but there were " +
actualCreatedListSize + " <created> entries.");
}
dirDiff.verifyNoRemainingKeys("dirDiff");
}
expectTagEnd(SNAPSHOT_DIFF_SECTION_DIR_DIFF_ENTRY);
}
private void processFileDiffEntry() throws IOException {
LOG.debug("Processing fileDiffEntry");
DiffEntry.Builder headerBld = DiffEntry.newBuilder();
headerBld.setType(DiffEntry.Type.FILEDIFF);
Node fileDiffHeader = new Node();
loadNodeChildren(fileDiffHeader, "fileDiffEntry fields", "fileDiff");
Long inodeId = fileDiffHeader.removeChildLong(
SNAPSHOT_DIFF_SECTION_INODE_ID);
if (inodeId == null) {
throw new IOException("<fileDiffEntry> contained no <inodeid> entry.");
}
headerBld.setInodeId(inodeId);
Integer expectedDiffs = fileDiffHeader.removeChildInt(
SNAPSHOT_DIFF_SECTION_COUNT);
if (expectedDiffs == null) {
throw new IOException("<fileDiffEntry> contained no <count> entry.");
}
headerBld.setNumOfDiff(expectedDiffs);
fileDiffHeader.verifyNoRemainingKeys("fileDiffEntry");
headerBld.build().writeDelimitedTo(out);
for (int actualDiffs = 0; actualDiffs < expectedDiffs; actualDiffs++) {
try {
expectTag(SNAPSHOT_DIFF_SECTION_FILE_DIFF, false);
} catch (IOException e) {
throw new IOException("Only read " + (actualDiffs + 1) +
" diffs out of " + expectedDiffs, e);
}
Node fileDiff = new Node();
loadNodeChildren(fileDiff, "fileDiff fields");
FsImageProto.SnapshotDiffSection.FileDiff.Builder bld =
FsImageProto.SnapshotDiffSection.FileDiff.newBuilder();
Integer snapshotId = fileDiff.removeChildInt(
SNAPSHOT_DIFF_SECTION_SNAPSHOT_ID);
if (snapshotId != null) {
bld.setSnapshotId(snapshotId);
}
Long size = fileDiff.removeChildLong(
SNAPSHOT_DIFF_SECTION_SIZE);
if (size != null) {
bld.setFileSize(size);
}
String name = fileDiff.removeChildStr(SECTION_NAME);
if (name != null) {
bld.setName(ByteString.copyFrom(name, "UTF8"));
}
Node snapshotCopy = fileDiff.removeChild(
SNAPSHOT_DIFF_SECTION_SNAPSHOT_COPY);
if (snapshotCopy != null) {
bld.setSnapshotCopy(createINodeFileBuilder(snapshotCopy));
}
Node blocks = fileDiff.removeChild(INODE_SECTION_BLOCKS);
if (blocks != null) {
while (true) {
Node block = blocks.removeChild(INODE_SECTION_BLOCK);
if (block == null) {
break;
}
bld.addBlocks(createBlockBuilder(block));
}
}
fileDiff.verifyNoRemainingKeys("fileDiff");
bld.build().writeDelimitedTo(out);
}
expectTagEnd(SNAPSHOT_DIFF_SECTION_FILE_DIFF_ENTRY);
}
}
/**
* Permission is serialized as a 64-bit long. [0:24):[25:48):[48:64)
* (in Big Endian). The first and the second parts are the string ids
* of the user and group name, and the last 16 bits are the permission bits.
*
* @param perm The permission string from the XML.
* @return The 64-bit value to use in the fsimage for permission.
* @throws IOException If we run out of string IDs in the string table.
*/
private long permissionXmlToU64(String perm) throws IOException {
String components[] = perm.split(":");
if (components.length != 3) {
throw new IOException("Unable to parse permission string " + perm +
": expected 3 components, but only had " + components.length);
}
String userName = components[0];
String groupName = components[1];
String modeString = components[2];
long userNameId = registerStringId(userName);
long groupNameId = registerStringId(groupName);
long mode = new FsPermission(modeString).toShort();
return (userNameId << 40) | (groupNameId << 16) | mode;
}
/**
* The FSImage contains a string table which maps strings to IDs.
* This is a simple form of compression which takes advantage of the fact
* that the same strings tend to occur over and over again.
* This function will return an ID which we can use to represent the given
* string. If the string already exists in the string table, we will use
* that ID; otherwise, we will allocate a new one.
*
* @param str The string.
* @return The ID in the string table.
* @throws IOException If we run out of bits in the string table. We only
* have 25 bits.
*/
int registerStringId(String str) throws IOException {
Integer id = stringTable.get(str);
if (id != null) {
return id;
}
int latestId = latestStringId;
if (latestId >= 0x1ffffff) {
throw new IOException("Cannot have more than 2**25 " +
"strings in the fsimage, because of the limitation on " +
"the size of string table IDs.");
}
stringTable.put(str, latestId);
latestStringId++;
return latestId;
}
/**
* Record the length of a section of the FSImage in our FileSummary object.
* The FileSummary appears at the end of the FSImage and acts as a table of
* contents for the file.
*
* @param sectionNamePb The name of the section as it should appear in
* the fsimage. (This is different than the XML
* name.)
* @throws IOException
*/
void recordSectionLength(String sectionNamePb) throws IOException {
long curSectionStartOffset = sectionStartOffset;
long curPos = out.getCount();
//if (sectionNamePb.equals(SectionName.STRING_TABLE.name())) {
fileSummaryBld.addSections(FileSummary.Section.newBuilder().
setName(sectionNamePb).
setLength(curPos - curSectionStartOffset).
setOffset(curSectionStartOffset));
//}
sectionStartOffset = curPos;
}
/**
* Read the version tag which starts the XML file.
*/
private void readVersion() throws IOException {
try {
expectTag("version", false);
} catch (IOException e) {
// Handle the case where <version> does not exist.
// Note: fsimage XML files which are missing <version> are also missing
// many other fields that oiv needs to accurately reconstruct the
// fsimage.
throw new IOException("No <version> section found at the top of " +
"the fsimage XML. This XML file is too old to be processed " +
"by oiv.", e);
}
Node version = new Node();
loadNodeChildren(version, "version fields");
Integer onDiskVersion = version.removeChildInt("onDiskVersion");
if (onDiskVersion == null) {
throw new IOException("The <version> section doesn't contain " +
"the onDiskVersion.");
}
Integer layoutVersion = version.removeChildInt("layoutVersion");
if (layoutVersion == null) {
throw new IOException("The <version> section doesn't contain " +
"the layoutVersion.");
}
if (layoutVersion.intValue() !=
NameNodeLayoutVersion.CURRENT_LAYOUT_VERSION) {
throw new IOException("Layout version mismatch. This oiv tool " +
"handles layout version " +
NameNodeLayoutVersion.CURRENT_LAYOUT_VERSION + ", but the " +
"XML file has <layoutVersion> " + layoutVersion + ". Please " +
"either re-generate the XML file with the proper layout version, " +
"or manually edit the XML file to be usable with this version " +
"of the oiv tool.");
}
fileSummaryBld.setOndiskVersion(onDiskVersion);
fileSummaryBld.setLayoutVersion(layoutVersion);
if (LOG.isDebugEnabled()) {
LOG.debug("Loaded <version> with onDiskVersion=" + onDiskVersion +
", layoutVersion=" + layoutVersion + ".");
}
}
/**
* Write the string table to the fsimage.
* @throws IOException
*/
private void writeStringTableSection() throws IOException {
FsImageProto.StringTableSection sectionHeader =
FsImageProto.StringTableSection.newBuilder().
setNumEntry(stringTable.size()).build();
if (LOG.isDebugEnabled()) {
LOG.debug(SectionName.STRING_TABLE.name() + " writing header: {" +
TextFormat.printToString(sectionHeader) + "}");
}
sectionHeader.writeDelimitedTo(out);
// The entries don't have to be in any particular order, so iterating
// over the hash table is fine.
for (Map.Entry<String, Integer> entry : stringTable.entrySet()) {
FsImageProto.StringTableSection.Entry stEntry =
FsImageProto.StringTableSection.Entry.newBuilder().
setStr(entry.getKey()).
setId(entry.getValue()).
build();
if (LOG.isTraceEnabled()) {
LOG.trace("Writing string table entry: {" +
TextFormat.printToString(stEntry) + "}");
}
stEntry.writeDelimitedTo(out);
}
recordSectionLength(SectionName.STRING_TABLE.name());
}
/**
* Processes the XML file back into an fsimage.
*/
private void processXml() throws Exception {
LOG.debug("Loading <fsimage>.");
expectTag("fsimage", false);
// Read the <version> tag.
readVersion();
// Write the HDFSIMG1 magic number which begins the fsimage file.
out.write(FSImageUtil.MAGIC_HEADER);
// Write a series of fsimage sections.
sectionStartOffset = FSImageUtil.MAGIC_HEADER.length;
final HashSet<String> unprocessedSections =
new HashSet<>(sections.keySet());
while (!unprocessedSections.isEmpty()) {
XMLEvent ev = expectTag("[section header]", true);
if (ev.getEventType() == XMLStreamConstants.END_ELEMENT) {
if (ev.asEndElement().getName().getLocalPart().equals("fsimage")) {
throw new IOException("FSImage XML ended prematurely, without " +
"including section(s) " + StringUtils.join(", ",
unprocessedSections));
}
throw new IOException("Got unexpected tag end event for " +
ev.asEndElement().getName().getLocalPart() + " while looking " +
"for section header tag.");
} else if (ev.getEventType() != XMLStreamConstants.START_ELEMENT) {
throw new IOException("Expected section header START_ELEMENT; " +
"got event of type " + ev.getEventType());
}
String sectionName = ev.asStartElement().getName().getLocalPart();
if (!unprocessedSections.contains(sectionName)) {
throw new IOException("Unknown or duplicate section found for " +
sectionName);
}
SectionProcessor sectionProcessor = sections.get(sectionName);
if (sectionProcessor == null) {
throw new IOException("Unknown FSImage section " + sectionName +
". Valid section names are [" +
StringUtils.join(", ", sections.keySet()) + "]");
}
unprocessedSections.remove(sectionName);
sectionProcessor.process();
}
// Write the StringTable section to disk.
// This has to be done after the other sections, since some of them
// add entries to the string table.
writeStringTableSection();
// Write the FileSummary section to disk.
// This section is always last.
long prevOffset = out.getCount();
FileSummary fileSummary = fileSummaryBld.build();
if (LOG.isDebugEnabled()) {
LOG.debug("Writing FileSummary: {" +
TextFormat.printToString(fileSummary) + "}");
}
// Even though the last 4 bytes of the file gives the FileSummary length,
// we still write a varint first that also contains the length.
fileSummary.writeDelimitedTo(out);
// Write the length of the FileSummary section as a fixed-size big
// endian 4-byte quantity.
int summaryLen = Ints.checkedCast(out.getCount() - prevOffset);
byte[] summaryLenBytes = new byte[4];
ByteBuffer.wrap(summaryLenBytes).asIntBuffer().put(summaryLen);
out.write(summaryLenBytes);
}
/**
* Run the OfflineImageReconstructor.
*
* @param inputPath The input path to use.
* @param outputPath The output path to use.
*
* @throws Exception On error.
*/
public static void run(String inputPath, String outputPath)
throws Exception {
MessageDigest digester = MD5Hash.getDigester();
FileOutputStream fout = null;
File foutHash = new File(outputPath + ".md5");
Files.deleteIfExists(foutHash.toPath()); // delete any .md5 file that exists
CountingOutputStream out = null;
FileInputStream fis = null;
InputStreamReader reader = null;
try {
Files.deleteIfExists(Paths.get(outputPath));
fout = new FileOutputStream(outputPath);
fis = new FileInputStream(inputPath);
reader = new InputStreamReader(fis, Charset.forName("UTF-8"));
out = new CountingOutputStream(
new DigestOutputStream(
new BufferedOutputStream(fout), digester));
OfflineImageReconstructor oir =
new OfflineImageReconstructor(out, reader);
oir.processXml();
} finally {
IOUtils.cleanup(LOG, reader, fis, out, fout);
}
// Write the md5 file
MD5FileUtils.saveMD5File(new File(outputPath),
new MD5Hash(digester.digest()));
}
}