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

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hdfs.tools.offlineImageViewer.TestOfflineImageViewer.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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.protocol.FSConstants.SafeModeAction;
import org.apache.hadoop.security.token.Token;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Test function of OfflineImageViewer by:
 *   * confirming it can correctly process a valid fsimage file and that
 *     the processing generates a correct representation of the namespace
 *   * confirming it correctly fails to process an fsimage file with a layout
 *     version it shouldn't be able to handle
 *   * confirm it correctly bails on malformed image files, in particular, a
 *     file that ends suddenly.
 */
public class TestOfflineImageViewer {
    private static final Log LOG = LogFactory.getLog(OfflineImageViewer.class);
    private static final int NUM_DIRS = 3;
    private static final int FILES_PER_DIR = 4;
    private static final String TEST_RENEWER = "JobTracker";
    private static File originalFsimage = null;

    // Elements of lines of ls-file output to be compared to FileStatus instance
    private static class LsElements {
        public String perms;
        public int replication;
        public String username;
        public String groupname;
        public long filesize;
        public char dir; // d if dir, - otherwise
    }

    // namespace as written to dfs, to be compared with viewer's output
    final static HashMap<String, FileStatus> writtenFiles = new HashMap<String, FileStatus>();

    private static String ROOT = System.getProperty("test.build.data", "build/test/data");

    // Create a populated namespace for later testing.  Save its contents to a
    // data structure and store its fsimage location.
    // We only want to generate the fsimage file once and use it for
    // multiple tests.
    @BeforeClass
    public static void createOriginalFSImage() throws IOException {
        MiniDFSCluster cluster = null;
        try {
            Configuration conf = new Configuration();
            conf.setLong(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_MAX_LIFETIME_KEY, 10000);
            conf.setLong(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_RENEW_INTERVAL_KEY, 5000);
            conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_ALWAYS_USE_KEY, true);
            conf.set("hadoop.security.auth_to_local", "RULE:[2:$1@$0](JobTracker@.*FOO.COM)s/@.*//" + "DEFAULT");
            cluster = new MiniDFSCluster(conf, 4, true, null);
            cluster.waitActive();
            FileSystem hdfs = cluster.getFileSystem();

            int filesize = 256;

            // Create a reasonable namespace 
            for (int i = 0; i < NUM_DIRS; i++) {
                Path dir = new Path("/dir" + i);
                hdfs.mkdirs(dir);
                writtenFiles.put(dir.toString(), pathToFileEntry(hdfs, dir.toString()));
                for (int j = 0; j < FILES_PER_DIR; j++) {
                    Path file = new Path(dir, "file" + j);
                    FSDataOutputStream o = hdfs.create(file);
                    o.write(new byte[filesize++]);
                    o.close();

                    writtenFiles.put(file.toString(), pathToFileEntry(hdfs, file.toString()));
                }
            }

            // Get delegation tokens so we log the delegation token op
            Token<?> delegationToken = hdfs.getDelegationToken(TEST_RENEWER);
            LOG.debug("got token " + delegationToken);

            // Write results to the fsimage file
            cluster.getNameNode().setSafeMode(SafeModeAction.SAFEMODE_ENTER);
            cluster.getNameNode().saveNamespace();

            // Determine location of fsimage file
            File[] files = cluster.getNameDirs().toArray(new File[0]);
            originalFsimage = new File(files[0], "current/fsimage");

            if (!originalFsimage.exists())
                fail("Didn't generate or can't find fsimage.");

        } finally {
            if (cluster != null)
                cluster.shutdown();
        }
    }

    @AfterClass
    public static void deleteOriginalFSImage() throws IOException {
        if (originalFsimage != null && originalFsimage.exists()) {
            originalFsimage.delete();
        }
    }

    // Convenience method to generate a file status from file system for 
    // later comparison
    private static FileStatus pathToFileEntry(FileSystem hdfs, String file) throws IOException {
        return hdfs.getFileStatus(new Path(file));
    }

    // Verify that we can correctly generate an ls-style output for a valid 
    // fsimage
    @Test
    public void outputOfLSVisitor() throws IOException {
        File testFile = new File(ROOT, "/basicCheck");
        File outputFile = new File(ROOT, "/basicCheckOutput");

        try {
            copyFile(originalFsimage, testFile);

            ImageVisitor v = new LsImageVisitor(outputFile.getPath(), true);
            OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, false);

            oiv.go();

            HashMap<String, LsElements> fileOutput = readLsfile(outputFile);

            compareNamespaces(writtenFiles, fileOutput);
        } finally {
            if (testFile.exists())
                testFile.delete();
            if (outputFile.exists())
                outputFile.delete();
        }
        LOG.debug("Correctly generated ls-style output.");
    }

    // Confirm that attempting to read an fsimage file with an unsupported
    // layout results in an error
    @Test
    public void unsupportedFSLayoutVersion() throws IOException {
        File testFile = new File(ROOT, "/invalidLayoutVersion");
        File outputFile = new File(ROOT, "invalidLayoutVersionOutput");

        try {
            int badVersionNum = -432;
            changeLayoutVersion(originalFsimage, testFile, badVersionNum);
            ImageVisitor v = new LsImageVisitor(outputFile.getPath(), true);
            OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, false);

            try {
                oiv.go();
                fail("Shouldn't be able to read invalid laytout version");
            } catch (IOException e) {
                if (!e.getMessage().contains(Integer.toString(badVersionNum)))
                    throw e; // wasn't error we were expecting
                LOG.debug("Correctly failed at reading bad image version.");
            }
        } finally {
            if (testFile.exists())
                testFile.delete();
            if (outputFile.exists())
                outputFile.delete();
        }
    }

    // Verify that image viewer will bail on a file that ends unexpectedly
    @Test
    public void truncatedFSImage() throws IOException {
        File testFile = new File(ROOT, "/truncatedFSImage");
        File outputFile = new File(ROOT, "/trucnatedFSImageOutput");
        try {
            copyPartOfFile(originalFsimage, testFile);
            assertTrue("Created truncated fsimage", testFile.exists());

            ImageVisitor v = new LsImageVisitor(outputFile.getPath(), true);
            OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, false);

            try {
                oiv.go();
                fail("Managed to process a truncated fsimage file");
            } catch (EOFException e) {
                LOG.debug("Correctly handled EOF");
            }

        } finally {
            if (testFile.exists())
                testFile.delete();
            if (outputFile.exists())
                outputFile.delete();
        }
    }

    // Test that our ls file has all the same compenents of the original namespace
    private void compareNamespaces(HashMap<String, FileStatus> written, HashMap<String, LsElements> fileOutput) {
        assertEquals("Should be the same number of files in both, plus one for root" + " in fileoutput",
                fileOutput.keySet().size(), written.keySet().size() + 1);
        Set<String> inFile = fileOutput.keySet();

        // For each line in the output file, verify that the namespace had a
        // filestatus counterpart 
        for (String path : inFile) {
            if (path.equals("/")) // root's not included in output from system call
                continue;

            assertTrue("Path in file (" + path + ") was written to fs", written.containsKey(path));

            compareFiles(written.get(path), fileOutput.get(path));

            written.remove(path);
        }

        assertEquals("No more files were written to fs", 0, written.size());
    }

    // Compare two files as listed in the original namespace FileStatus and
    // the output of the ls file from the image processor
    private void compareFiles(FileStatus fs, LsElements elements) {
        assertEquals("directory listed as such", fs.isDir() ? 'd' : '-', elements.dir);
        assertEquals("perms string equal", fs.getPermission().toString(), elements.perms);
        assertEquals("replication equal", fs.getReplication(), elements.replication);
        assertEquals("owner equal", fs.getOwner(), elements.username);
        assertEquals("group equal", fs.getGroup(), elements.groupname);
        assertEquals("lengths equal", fs.getLen(), elements.filesize);
    }

    // Read the contents of the file created by the Ls processor
    private HashMap<String, LsElements> readLsfile(File lsFile) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(lsFile));
        String line = null;
        HashMap<String, LsElements> fileContents = new HashMap<String, LsElements>();

        while ((line = br.readLine()) != null)
            readLsLine(line, fileContents);

        return fileContents;
    }

    // Parse a line from the ls output.  Store permissions, replication, 
    // username, groupname and filesize in hashmap keyed to the path name
    private void readLsLine(String line, HashMap<String, LsElements> fileContents) {
        String elements[] = line.split("\\s+");

        assertEquals("Not enough elements in ls output", 8, elements.length);

        LsElements lsLine = new LsElements();

        lsLine.dir = elements[0].charAt(0);
        lsLine.perms = elements[0].substring(1);
        lsLine.replication = elements[1].equals("-") ? 0 : Integer.valueOf(elements[1]);
        lsLine.username = elements[2];
        lsLine.groupname = elements[3];
        lsLine.filesize = Long.valueOf(elements[4]);
        // skipping date and time 

        String path = elements[7];

        // Check that each file in the ls output was listed once
        assertFalse("LS file had duplicate file entries", fileContents.containsKey(path));

        fileContents.put(path, lsLine);
    }

    // Copy one fsimage to another, changing the layout version in the process
    private void changeLayoutVersion(File src, File dest, int newVersion) throws IOException {
        DataInputStream in = null;
        DataOutputStream out = null;

        try {
            in = new DataInputStream(new FileInputStream(src));
            out = new DataOutputStream(new FileOutputStream(dest));

            in.readInt();
            out.writeInt(newVersion);

            byte[] b = new byte[1024];
            while (in.read(b) > 0) {
                out.write(b);
            }
        } finally {
            if (in != null)
                in.close();
            if (out != null)
                out.close();
        }
    }

    // Only copy part of file into the other.  Used for testing truncated fsimage
    private void copyPartOfFile(File src, File dest) throws IOException {
        InputStream in = null;
        OutputStream out = null;

        byte[] b = new byte[256];
        int bytesWritten = 0;
        int count;
        int maxBytes = 700;

        try {
            in = new FileInputStream(src);
            out = new FileOutputStream(dest);

            while ((count = in.read(b)) > 0 && bytesWritten < maxBytes) {
                out.write(b);
                bytesWritten += count;
            }
        } finally {
            if (in != null)
                in.close();
            if (out != null)
                out.close();
        }
    }

    // Copy one file's contents into the other
    private void copyFile(File src, File dest) throws IOException {
        InputStream in = null;
        OutputStream out = null;

        try {
            in = new FileInputStream(src);
            out = new FileOutputStream(dest);

            byte[] b = new byte[1024];
            while (in.read(b) > 0) {
                out.write(b);
            }
        } finally {
            if (in != null)
                in.close();
            if (out != null)
                out.close();
        }
    }

    @Test
    public void outputOfFileDistributionVisitor() throws IOException {
        File testFile = new File(ROOT, "/basicCheck");
        File outputFile = new File(ROOT, "/fileDistributionCheckOutput");

        int totalFiles = 0;
        try {
            copyFile(originalFsimage, testFile);
            ImageVisitor v = new FileDistributionVisitor(outputFile.getPath(), 0, 0);
            OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, false);

            oiv.go();

            BufferedReader reader = new BufferedReader(new FileReader(outputFile));
            String line = reader.readLine();
            assertEquals(line, "Size\tNumFiles");
            while ((line = reader.readLine()) != null) {
                String[] row = line.split("\t");
                assertEquals(row.length, 2);
                totalFiles += Integer.parseInt(row[1]);
            }
        } finally {
            if (testFile.exists())
                testFile.delete();
            if (outputFile.exists())
                outputFile.delete();
        }
        assertEquals(totalFiles, NUM_DIRS * FILES_PER_DIR);
    }

    private static class TestImageVisitor extends ImageVisitor {
        private List<String> delegationTokenRenewers = new LinkedList<String>();

        TestImageVisitor() {
        }

        List<String> getDelegationTokenRenewers() {
            return delegationTokenRenewers;
        }

        @Override
        void start() throws IOException {
        }

        @Override
        void finish() throws IOException {
        }

        @Override
        void finishAbnormally() throws IOException {
        }

        @Override
        void visit(ImageElement element, String value) throws IOException {
            if (element == ImageElement.DELEGATION_TOKEN_IDENTIFIER_RENEWER) {
                delegationTokenRenewers.add(value);
            }
        }

        @Override
        void visitEnclosingElement(ImageElement element) throws IOException {
        }

        @Override
        void visitEnclosingElement(ImageElement element, ImageElement key, String value) throws IOException {
        }

        @Override
        void leaveEnclosingElement() throws IOException {
        }
    }

    @Test
    public void outputOfTestVisitor() throws IOException {
        File testFile = new File(ROOT, "/basicCheck");

        try {
            copyFile(originalFsimage, testFile);
            TestImageVisitor v = new TestImageVisitor();
            OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, true);
            oiv.go();

            // Validated stored delegation token identifiers.
            List<String> dtrs = v.getDelegationTokenRenewers();
            assertEquals(1, dtrs.size());
            assertEquals(TEST_RENEWER, dtrs.get(0));
        } finally {
            if (testFile.exists())
                testFile.delete();
        }
        LOG.debug("Passed TestVisitor validation.");
    }
}