org.apache.hadoop.hdfs.tools.offlineImageViewer.FSImageLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hdfs.tools.offlineImageViewer.FSImageLoader.java

Source

/**
 * 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 java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import com.google.common.collect.ImmutableList;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.InvalidProtocolBufferException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.XAttr;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.fs.permission.PermissionStatus;
import org.apache.hadoop.hdfs.XAttrHelper;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf;
import org.apache.hadoop.hdfs.server.namenode.FSImageUtil;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto;
import org.apache.hadoop.hdfs.server.namenode.INodeId;
import org.apache.hadoop.hdfs.web.JsonUtil;
import org.apache.hadoop.hdfs.web.resources.XAttrEncodingParam;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.LimitInputStream;
import org.codehaus.jackson.map.ObjectMapper;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * FSImageLoader loads fsimage and provide methods to return JSON formatted
 * file status of the namespace of the fsimage.
 */
class FSImageLoader {
    public static final Log LOG = LogFactory.getLog(FSImageHandler.class);

    private final String[] stringTable;
    // byte representation of inodes, sorted by id
    private final byte[][] inodes;
    private final Map<Long, long[]> dirmap;
    private static final Comparator<byte[]> INODE_BYTES_COMPARATOR = new Comparator<byte[]>() {
        @Override
        public int compare(byte[] o1, byte[] o2) {
            try {
                final FsImageProto.INodeSection.INode l = FsImageProto.INodeSection.INode.parseFrom(o1);
                final FsImageProto.INodeSection.INode r = FsImageProto.INodeSection.INode.parseFrom(o2);
                if (l.getId() < r.getId()) {
                    return -1;
                } else if (l.getId() > r.getId()) {
                    return 1;
                } else {
                    return 0;
                }
            } catch (InvalidProtocolBufferException e) {
                throw new RuntimeException(e);
            }
        }
    };

    private FSImageLoader(String[] stringTable, byte[][] inodes, Map<Long, long[]> dirmap) {
        this.stringTable = stringTable;
        this.inodes = inodes;
        this.dirmap = dirmap;
    }

    /**
     * Load fsimage into the memory.
     * @param inputFile the filepath of the fsimage to load.
     * @return FSImageLoader
     * @throws IOException if failed to load fsimage.
     */
    static FSImageLoader load(String inputFile) throws IOException {
        Configuration conf = new Configuration();
        RandomAccessFile file = new RandomAccessFile(inputFile, "r");
        if (!FSImageUtil.checkFileFormat(file)) {
            throw new IOException("Unrecognized FSImage");
        }

        FsImageProto.FileSummary summary = FSImageUtil.loadSummary(file);

        try (FileInputStream fin = new FileInputStream(file.getFD())) {
            // Map to record INodeReference to the referred id
            ImmutableList<Long> refIdList = null;
            String[] stringTable = null;
            byte[][] inodes = null;
            Map<Long, long[]> dirmap = null;

            ArrayList<FsImageProto.FileSummary.Section> sections = Lists.newArrayList(summary.getSectionsList());
            Collections.sort(sections, new Comparator<FsImageProto.FileSummary.Section>() {
                @Override
                public int compare(FsImageProto.FileSummary.Section s1, FsImageProto.FileSummary.Section s2) {
                    FSImageFormatProtobuf.SectionName n1 = FSImageFormatProtobuf.SectionName
                            .fromString(s1.getName());
                    FSImageFormatProtobuf.SectionName n2 = FSImageFormatProtobuf.SectionName
                            .fromString(s2.getName());
                    if (n1 == null) {
                        return n2 == null ? 0 : -1;
                    } else if (n2 == null) {
                        return -1;
                    } else {
                        return n1.ordinal() - n2.ordinal();
                    }
                }
            });

            for (FsImageProto.FileSummary.Section s : sections) {
                fin.getChannel().position(s.getOffset());
                InputStream is = FSImageUtil.wrapInputStreamForCompression(conf, summary.getCodec(),
                        new BufferedInputStream(new LimitInputStream(fin, s.getLength())));

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Loading section " + s.getName() + " length: " + s.getLength());
                }
                switch (FSImageFormatProtobuf.SectionName.fromString(s.getName())) {
                case STRING_TABLE:
                    stringTable = loadStringTable(is);
                    break;
                case INODE:
                    inodes = loadINodeSection(is);
                    break;
                case INODE_REFERENCE:
                    refIdList = loadINodeReferenceSection(is);
                    break;
                case INODE_DIR:
                    dirmap = loadINodeDirectorySection(is, refIdList);
                    break;
                default:
                    break;
                }
            }
            return new FSImageLoader(stringTable, inodes, dirmap);
        }
    }

    private static Map<Long, long[]> loadINodeDirectorySection(InputStream in, List<Long> refIdList)
            throws IOException {
        LOG.info("Loading inode directory section");
        Map<Long, long[]> dirs = Maps.newHashMap();
        long counter = 0;
        while (true) {
            FsImageProto.INodeDirectorySection.DirEntry e = FsImageProto.INodeDirectorySection.DirEntry
                    .parseDelimitedFrom(in);
            // note that in is a LimitedInputStream
            if (e == null) {
                break;
            }
            ++counter;

            long[] l = new long[e.getChildrenCount() + e.getRefChildrenCount()];
            for (int i = 0; i < e.getChildrenCount(); ++i) {
                l[i] = e.getChildren(i);
            }
            for (int i = e.getChildrenCount(); i < l.length; i++) {
                int refId = e.getRefChildren(i - e.getChildrenCount());
                l[i] = refIdList.get(refId);
            }
            dirs.put(e.getParent(), l);
        }
        LOG.info("Loaded " + counter + " directories");
        return dirs;
    }

    private static ImmutableList<Long> loadINodeReferenceSection(InputStream in) throws IOException {
        LOG.info("Loading inode references");
        ImmutableList.Builder<Long> builder = ImmutableList.builder();
        long counter = 0;
        while (true) {
            FsImageProto.INodeReferenceSection.INodeReference e = FsImageProto.INodeReferenceSection.INodeReference
                    .parseDelimitedFrom(in);
            if (e == null) {
                break;
            }
            ++counter;
            builder.add(e.getReferredId());
        }
        LOG.info("Loaded " + counter + " inode references");
        return builder.build();
    }

    private static byte[][] loadINodeSection(InputStream in) throws IOException {
        FsImageProto.INodeSection s = FsImageProto.INodeSection.parseDelimitedFrom(in);
        LOG.info("Loading " + s.getNumInodes() + " inodes.");
        final byte[][] inodes = new byte[(int) s.getNumInodes()][];

        for (int i = 0; i < s.getNumInodes(); ++i) {
            int size = CodedInputStream.readRawVarint32(in.read(), in);
            byte[] bytes = new byte[size];
            IOUtils.readFully(in, bytes, 0, size);
            inodes[i] = bytes;
        }
        LOG.debug("Sorting inodes");
        Arrays.sort(inodes, INODE_BYTES_COMPARATOR);
        LOG.debug("Finished sorting inodes");
        return inodes;
    }

    static String[] loadStringTable(InputStream in) throws IOException {
        FsImageProto.StringTableSection s = FsImageProto.StringTableSection.parseDelimitedFrom(in);
        LOG.info("Loading " + s.getNumEntry() + " strings");
        String[] stringTable = new String[s.getNumEntry() + 1];
        for (int i = 0; i < s.getNumEntry(); ++i) {
            FsImageProto.StringTableSection.Entry e = FsImageProto.StringTableSection.Entry.parseDelimitedFrom(in);
            stringTable[e.getId()] = e.getStr();
        }
        return stringTable;
    }

    /**
     * Return the JSON formatted FileStatus of the specified file.
     * @param path a path specifies a file
     * @return JSON formatted FileStatus
     * @throws IOException if failed to serialize fileStatus to JSON.
     */
    String getFileStatus(String path) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        FsImageProto.INodeSection.INode inode = fromINodeId(lookup(path));
        return "{\"FileStatus\":\n" + mapper.writeValueAsString(getFileStatus(inode, false)) + "\n}\n";
    }

    /**
     * Return the JSON formatted list of the files in the specified directory.
     * @param path a path specifies a directory to list
     * @return JSON formatted file list in the directory
     * @throws IOException if failed to serialize fileStatus to JSON.
     */
    String listStatus(String path) throws IOException {
        StringBuilder sb = new StringBuilder();
        ObjectMapper mapper = new ObjectMapper();
        List<Map<String, Object>> fileStatusList = getFileStatusList(path);
        sb.append("{\"FileStatuses\":{\"FileStatus\":[\n");
        int i = 0;
        for (Map<String, Object> fileStatusMap : fileStatusList) {
            if (i++ != 0) {
                sb.append(',');
            }
            sb.append(mapper.writeValueAsString(fileStatusMap));
        }
        sb.append("\n]}}\n");
        return sb.toString();
    }

    private List<Map<String, Object>> getFileStatusList(String path) throws IOException {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        long id = lookup(path);
        FsImageProto.INodeSection.INode inode = fromINodeId(id);
        if (inode.getType() == FsImageProto.INodeSection.INode.Type.DIRECTORY) {
            if (!dirmap.containsKey(id)) {
                // if the directory is empty, return empty list
                return list;
            }
            long[] children = dirmap.get(id);
            for (long cid : children) {
                list.add(getFileStatus(fromINodeId(cid), true));
            }
        } else {
            list.add(getFileStatus(inode, false));
        }
        return list;
    }

    /**
     * Return the JSON formatted XAttrNames of the specified file.
     *
     * @param path
     *          a path specifies a file
     * @return JSON formatted XAttrNames
     * @throws IOException
     *           if failed to serialize fileStatus to JSON.
     */
    String listXAttrs(String path) throws IOException {
        return JsonUtil.toJsonString(getXAttrList(path));
    }

    /**
     * Return the JSON formatted XAttrs of the specified file.
     *
     * @param path
     *          a path specifies a file
     * @return JSON formatted XAttrs
     * @throws IOException
     *           if failed to serialize fileStatus to JSON.
     */
    String getXAttrs(String path, List<String> names, String encoder) throws IOException {

        List<XAttr> xAttrs = getXAttrList(path);
        List<XAttr> filtered;
        if (names == null || names.size() == 0) {
            filtered = xAttrs;
        } else {
            filtered = Lists.newArrayListWithCapacity(names.size());
            for (String name : names) {
                XAttr search = XAttrHelper.buildXAttr(name);

                boolean found = false;
                for (XAttr aXAttr : xAttrs) {
                    if (aXAttr.getNameSpace() == search.getNameSpace()
                            && aXAttr.getName().equals(search.getName())) {

                        filtered.add(aXAttr);
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    throw new IOException("At least one of the attributes provided was not found.");
                }
            }

        }
        return JsonUtil.toJsonString(filtered, new XAttrEncodingParam(encoder).getEncoding());
    }

    private List<XAttr> getXAttrList(String path) throws IOException {
        long id = lookup(path);
        FsImageProto.INodeSection.INode inode = fromINodeId(id);
        switch (inode.getType()) {
        case FILE:
            return FSImageFormatPBINode.Loader.loadXAttrs(inode.getFile().getXAttrs(), stringTable);
        case DIRECTORY:
            return FSImageFormatPBINode.Loader.loadXAttrs(inode.getDirectory().getXAttrs(), stringTable);
        default:
            return null;
        }
    }

    /**
     * Return the JSON formatted ACL status of the specified file.
     * @param path a path specifies a file
     * @return JSON formatted AclStatus
     * @throws IOException if failed to serialize fileStatus to JSON.
     */
    String getAclStatus(String path) throws IOException {
        PermissionStatus p = getPermissionStatus(path);
        List<AclEntry> aclEntryList = getAclEntryList(path);
        FsPermission permission = p.getPermission();
        AclStatus.Builder builder = new AclStatus.Builder();
        builder.owner(p.getUserName()).group(p.getGroupName()).addEntries(aclEntryList).setPermission(permission)
                .stickyBit(permission.getStickyBit());
        AclStatus aclStatus = builder.build();
        return JsonUtil.toJsonString(aclStatus);
    }

    private List<AclEntry> getAclEntryList(String path) throws IOException {
        long id = lookup(path);
        FsImageProto.INodeSection.INode inode = fromINodeId(id);
        switch (inode.getType()) {
        case FILE: {
            FsImageProto.INodeSection.INodeFile f = inode.getFile();
            return FSImageFormatPBINode.Loader.loadAclEntries(f.getAcl(), stringTable);
        }
        case DIRECTORY: {
            FsImageProto.INodeSection.INodeDirectory d = inode.getDirectory();
            return FSImageFormatPBINode.Loader.loadAclEntries(d.getAcl(), stringTable);
        }
        default: {
            return new ArrayList<AclEntry>();
        }
        }
    }

    private PermissionStatus getPermissionStatus(String path) throws IOException {
        long id = lookup(path);
        FsImageProto.INodeSection.INode inode = fromINodeId(id);
        switch (inode.getType()) {
        case FILE: {
            FsImageProto.INodeSection.INodeFile f = inode.getFile();
            return FSImageFormatPBINode.Loader.loadPermission(f.getPermission(), stringTable);
        }
        case DIRECTORY: {
            FsImageProto.INodeSection.INodeDirectory d = inode.getDirectory();
            return FSImageFormatPBINode.Loader.loadPermission(d.getPermission(), stringTable);
        }
        case SYMLINK: {
            FsImageProto.INodeSection.INodeSymlink s = inode.getSymlink();
            return FSImageFormatPBINode.Loader.loadPermission(s.getPermission(), stringTable);
        }
        default: {
            return null;
        }
        }
    }

    /**
     * Return the INodeId of the specified path.
     */
    private long lookup(String path) throws IOException {
        Preconditions.checkArgument(path.startsWith("/"));
        long id = INodeId.ROOT_INODE_ID;
        for (int offset = 0, next; offset < path.length(); offset = next) {
            next = path.indexOf('/', offset + 1);
            if (next == -1) {
                next = path.length();
            }
            if (offset + 1 > next) {
                break;
            }

            final String component = path.substring(offset + 1, next);

            if (component.isEmpty()) {
                continue;
            }

            final long[] children = dirmap.get(id);
            if (children == null) {
                throw new FileNotFoundException(path);
            }

            boolean found = false;
            for (long cid : children) {
                FsImageProto.INodeSection.INode child = fromINodeId(cid);
                if (component.equals(child.getName().toStringUtf8())) {
                    found = true;
                    id = child.getId();
                    break;
                }
            }
            if (!found) {
                throw new FileNotFoundException(path);
            }
        }
        return id;
    }

    private Map<String, Object> getFileStatus(FsImageProto.INodeSection.INode inode, boolean printSuffix) {
        Map<String, Object> map = Maps.newHashMap();
        switch (inode.getType()) {
        case FILE: {
            FsImageProto.INodeSection.INodeFile f = inode.getFile();
            PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(f.getPermission(), stringTable);
            map.put("accessTime", f.getAccessTime());
            map.put("blockSize", f.getPreferredBlockSize());
            map.put("group", p.getGroupName());
            map.put("length", getFileSize(f));
            map.put("modificationTime", f.getModificationTime());
            map.put("owner", p.getUserName());
            map.put("pathSuffix", printSuffix ? inode.getName().toStringUtf8() : "");
            map.put("permission", toString(p.getPermission()));
            map.put("replication", f.getReplication());
            map.put("type", inode.getType());
            map.put("fileId", inode.getId());
            map.put("childrenNum", 0);
            return map;
        }
        case DIRECTORY: {
            FsImageProto.INodeSection.INodeDirectory d = inode.getDirectory();
            PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(d.getPermission(), stringTable);
            map.put("accessTime", 0);
            map.put("blockSize", 0);
            map.put("group", p.getGroupName());
            map.put("length", 0);
            map.put("modificationTime", d.getModificationTime());
            map.put("owner", p.getUserName());
            map.put("pathSuffix", printSuffix ? inode.getName().toStringUtf8() : "");
            map.put("permission", toString(p.getPermission()));
            map.put("replication", 0);
            map.put("type", inode.getType());
            map.put("fileId", inode.getId());
            map.put("childrenNum", dirmap.containsKey(inode.getId()) ? dirmap.get(inode.getId()).length : 0);
            return map;
        }
        case SYMLINK: {
            FsImageProto.INodeSection.INodeSymlink d = inode.getSymlink();
            PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(d.getPermission(), stringTable);
            map.put("accessTime", d.getAccessTime());
            map.put("blockSize", 0);
            map.put("group", p.getGroupName());
            map.put("length", 0);
            map.put("modificationTime", d.getModificationTime());
            map.put("owner", p.getUserName());
            map.put("pathSuffix", printSuffix ? inode.getName().toStringUtf8() : "");
            map.put("permission", toString(p.getPermission()));
            map.put("replication", 0);
            map.put("type", inode.getType());
            map.put("symlink", d.getTarget().toStringUtf8());
            map.put("fileId", inode.getId());
            map.put("childrenNum", 0);
            return map;
        }
        default:
            return null;
        }
    }

    static long getFileSize(FsImageProto.INodeSection.INodeFile f) {
        long size = 0;
        for (HdfsProtos.BlockProto p : f.getBlocksList()) {
            size += p.getNumBytes();
        }
        return size;
    }

    private String toString(FsPermission permission) {
        return String.format("%o", permission.toShort());
    }

    private FsImageProto.INodeSection.INode fromINodeId(final long id) throws IOException {
        int l = 0, r = inodes.length;
        while (l < r) {
            int mid = l + (r - l) / 2;
            FsImageProto.INodeSection.INode n = FsImageProto.INodeSection.INode.parseFrom(inodes[mid]);
            long nid = n.getId();
            if (id > nid) {
                l = mid + 1;
            } else if (id < nid) {
                r = mid;
            } else {
                return n;
            }
        }
        return null;
    }
}