HDFS-16655. OIV: print out erasure coding policy name in oiv Delimited output (#4541). Contributed by Max Xie.

Reviewed-by: He Xiaoqiao <hexiaoqiao@apache.org>
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageViewerPB.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageViewerPB.java
index 05e687a..2233a3c3 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageViewerPB.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageViewerPB.java
@@ -80,6 +80,7 @@
       + "    delimiter. The default delimiter is \\t, though this may be\n"
       + "    changed via the -delimiter argument.\n"
       + "    -sp print storage policy, used by delimiter only.\n"
+      + "    -ec print erasure coding policy, used by delimiter only.\n"
       + "  * DetectCorruption: Detect potential corruption of the image by\n"
       + "    selectively loading parts of it and actively searching for\n"
       + "    inconsistencies. Outputs a summary of the found corruptions\n"
@@ -132,6 +133,7 @@
     options.addOption("addr", true, "");
     options.addOption("delimiter", true, "");
     options.addOption("sp", false, "");
+    options.addOption("ec", false, "");
     options.addOption("t", "temp", true, "");
     options.addOption("m", "multiThread", true, "");
 
@@ -228,9 +230,11 @@
         break;
       case "DELIMITED":
         boolean printStoragePolicy = cmd.hasOption("sp");
+        boolean printECPolicy = cmd.hasOption("ec");
         try (PBImageDelimitedTextWriter writer =
             new PBImageDelimitedTextWriter(out, delimiter,
-                tempPath, printStoragePolicy, threads, outputFile)) {
+                tempPath, printStoragePolicy, printECPolicy, threads,
+                outputFile, conf)) {
           writer.visit(inputFile);
         }
         break;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageDelimitedTextWriter.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageDelimitedTextWriter.java
index 3e080ec..39fd765 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageDelimitedTextWriter.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageDelimitedTextWriter.java
@@ -17,9 +17,12 @@
  */
 package org.apache.hadoop.hdfs.tools.offlineImageViewer;
 
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.PermissionStatus;
+import org.apache.hadoop.hdfs.protocol.ErasureCodingPolicy;
 import org.apache.hadoop.hdfs.protocol.HdfsConstants;
+import org.apache.hadoop.hdfs.server.namenode.ErasureCodingPolicyManager;
 import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INode;
 import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeDirectory;
 import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeFile;
@@ -46,6 +49,8 @@
 public class PBImageDelimitedTextWriter extends PBImageTextWriter {
   private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm";
   private boolean printStoragePolicy;
+  private boolean printECPolicy;
+  private ErasureCodingPolicyManager ecManager;
 
   static class OutputEntryBuilder {
     private final SimpleDateFormat dateFormatter =
@@ -62,6 +67,7 @@
     private long nsQuota = 0;
     private long dsQuota = 0;
     private int storagePolicy = 0;
+    private String ecPolicy = "-";
 
     private String dirPermission = "-";
     private PermissionStatus permissionStatus;
@@ -83,6 +89,13 @@
           aclPermission = "+";
         }
         storagePolicy = file.getStoragePolicyID();
+        if (writer.printECPolicy && file.hasErasureCodingPolicyID()) {
+          ErasureCodingPolicy policy = writer.ecManager.
+              getByID((byte) file.getErasureCodingPolicyID());
+          if (policy != null) {
+            ecPolicy = policy.getName();
+          }
+        }
         break;
       case DIRECTORY:
         INodeDirectory dir = inode.getDirectory();
@@ -95,6 +108,12 @@
           aclPermission = "+";
         }
         storagePolicy = writer.getStoragePolicy(dir.getXAttrs());
+        if (writer.printECPolicy) {
+          String name= writer.getErasureCodingPolicyName(dir.getXAttrs());
+          if (name != null) {
+            ecPolicy = name;
+          }
+        }
         break;
       case SYMLINK:
         INodeSymlink s = inode.getSymlink();
@@ -134,6 +153,9 @@
       if (writer.printStoragePolicy) {
         writer.append(buffer, storagePolicy);
       }
+      if (writer.printECPolicy) {
+        writer.append(buffer, ecPolicy);
+      }
       return buffer.substring(1);
     }
   }
@@ -146,14 +168,21 @@
   PBImageDelimitedTextWriter(PrintStream out, String delimiter,
                              String tempPath, boolean printStoragePolicy)
       throws IOException {
-    this(out, delimiter, tempPath, printStoragePolicy, 1, "-");
+    this(out, delimiter, tempPath, printStoragePolicy, false, 1, "-", null);
   }
 
   PBImageDelimitedTextWriter(PrintStream out, String delimiter,
-      String tempPath, boolean printStoragePolicy, int threads,
-      String parallelOut) throws IOException {
+                             String tempPath, boolean printStoragePolicy,
+                             boolean printECPolicy, int threads,
+                             String parallelOut, Configuration conf)
+      throws IOException {
     super(out, delimiter, tempPath, threads, parallelOut);
     this.printStoragePolicy = printStoragePolicy;
+    if (printECPolicy && conf != null) {
+      this.printECPolicy = true;
+      ecManager = ErasureCodingPolicyManager.getInstance();
+      ecManager.init(conf);
+    }
   }
 
   @Override
@@ -187,6 +216,9 @@
     if (printStoragePolicy) {
       append(buffer, "StoragePolicyId");
     }
+    if (printECPolicy) {
+      append(buffer, "ErasureCodingPolicy");
+    }
     return buffer.toString();
   }
 
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageTextWriter.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageTextWriter.java
index 2dab44a..5773d7f 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageTextWriter.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageTextWriter.java
@@ -27,6 +27,8 @@
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
 import java.io.UnsupportedEncodingException;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
@@ -63,6 +65,7 @@
 import org.apache.hadoop.hdfs.server.namenode.INodeId;
 import org.apache.hadoop.hdfs.server.namenode.SerialNumberManager;
 import org.apache.hadoop.io.IOUtils;
+import org.apache.hadoop.io.WritableUtils;
 import org.apache.hadoop.util.LimitInputStream;
 import org.apache.hadoop.util.Lists;
 import org.apache.hadoop.util.Time;
@@ -77,6 +80,8 @@
 import org.apache.hadoop.util.Preconditions;
 import org.apache.hadoop.thirdparty.com.google.common.collect.ImmutableList;
 
+import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.XATTR_ERASURECODING_POLICY;
+
 /**
  * This class reads the protobuf-based fsimage and generates text output
  * for each inode to {@link PBImageTextWriter#out}. The sub-class can override
@@ -1029,4 +1034,23 @@
       }
     }
   }
+
+  public String getErasureCodingPolicyName
+      (INodeSection.XAttrFeatureProto xattrFeatureProto) {
+    List<XAttr> xattrs =
+        FSImageFormatPBINode.Loader.loadXAttrs(xattrFeatureProto, stringTable);
+    for (XAttr xattr : xattrs) {
+      if (XATTR_ERASURECODING_POLICY.contains(xattr.getName())){
+        try{
+          ByteArrayInputStream bIn = new ByteArrayInputStream(xattr.getValue());
+          DataInputStream dIn = new DataInputStream(bIn);
+          return WritableUtils.readString(dIn);
+        } catch (IOException ioException){
+          return null;
+        }
+      }
+    }
+    return null;
+  }
+
 }
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerForErasureCodingPolicy.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerForErasureCodingPolicy.java
new file mode 100644
index 0000000..c4e5622
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewerForErasureCodingPolicy.java
@@ -0,0 +1,186 @@
+/**
+ * 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 org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.DFSTestUtil;
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.protocol.HdfsConstants;
+import org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests OfflineImageViewer if the input fsimage has HDFS ErasureCodingPolicy
+ * entries.
+ */
+public class TestOfflineImageViewerForErasureCodingPolicy {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestOfflineImageViewerForErasureCodingPolicy.class);
+
+  private static File originalFsimage = null;
+  private static File tempDir;
+
+  /**
+   * Create a populated namespace for later testing. Save its contents to a
+   * data structure and store its fsimage location.
+   */
+  @BeforeClass
+  public static void createOriginalFSImage() throws IOException {
+    MiniDFSCluster cluster = null;
+    try {
+      Configuration conf = new Configuration();
+      conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY, true);
+      conf.setBoolean(DFSConfigKeys.DFS_STORAGE_POLICY_ENABLED_KEY, true);
+
+      File[] nnDirs = MiniDFSCluster.getNameNodeDirectory(
+          MiniDFSCluster.getBaseDirectory(), 0, 0);
+      tempDir = nnDirs[0];
+
+      cluster = new MiniDFSCluster.Builder(conf).numDataNodes(10).build();
+      cluster.waitActive();
+      DistributedFileSystem hdfs = cluster.getFileSystem();
+
+      hdfs.enableErasureCodingPolicy("RS-6-3-1024k");
+      hdfs.enableErasureCodingPolicy("RS-3-2-1024k");
+
+      Path dir = new Path("/dir_wo_ec_rs63");
+      hdfs.mkdirs(dir);
+      hdfs.setErasureCodingPolicy(dir, "RS-6-3-1024k");
+
+      dir = new Path("/dir_wo_ec_rs63/sub_dir_1");
+      hdfs.mkdirs(dir);
+
+      dir = new Path("/dir_wo_ec_rs63/sub_dir_2");
+      hdfs.mkdirs(dir);
+
+      Path file = new Path("/dir_wo_ec_rs63/file_wo_ec_1");
+      try (FSDataOutputStream  o = hdfs.create(file)) {
+        o.write(123);
+      }
+
+      file = new Path("/dir_wo_ec_rs63/file_wo_ec_2");
+      try (FSDataOutputStream  o = hdfs.create(file)) {
+        o.write(123);
+      }
+
+      dir = new Path("/dir_wo_ec_rs32");
+      hdfs.mkdirs(dir);
+      hdfs.setErasureCodingPolicy(dir, "RS-3-2-1024k");
+
+      dir = new Path("/dir_wo_ec_rs32/sub_dir_1");
+      hdfs.mkdirs(dir);
+
+      file = new Path("/dir_wo_ec_rs32/file_wo_ec");
+      try (FSDataOutputStream  o = hdfs.create(file)) {
+        o.write(123);
+      }
+
+      dir = new Path("/dir_wo_rep");
+      hdfs.mkdirs(dir);
+
+      dir = new Path("/dir_wo_rep/sub_dir_1");
+      hdfs.mkdirs(dir);
+
+      file = new Path("/dir_wo_rep/file_rep");
+      try (FSDataOutputStream  o = hdfs.create(file)) {
+        o.write(123);
+      }
+
+      // Write results to the fsimage file
+      hdfs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_ENTER, false);
+      hdfs.saveNamespace();
+
+      // Determine the location of the fsimage file
+      originalFsimage = FSImageTestUtil.findLatestImageFile(FSImageTestUtil
+          .getFSImage(cluster.getNameNode()).getStorage().getStorageDir(0));
+      if (originalFsimage == null) {
+        throw new RuntimeException("Didn't generate or can't find fsimage");
+      }
+      LOG.debug("original FS image file is " + originalFsimage);
+    } finally {
+      if (cluster != null) {
+        cluster.shutdown();
+      }
+    }
+  }
+
+  @AfterClass
+  public static void deleteOriginalFSImage() throws IOException {
+    if (originalFsimage != null && originalFsimage.exists()) {
+      originalFsimage.delete();
+    }
+  }
+
+  @Test
+  public void testPBDelimitedWriterForErasureCodingPolicy() throws Exception {
+    String expected = DFSTestUtil.readResoucePlainFile(
+        "testErasureCodingPolicy.csv");
+    String result = readECPolicyFromFsimageFile();
+    assertEquals(expected, result);
+  }
+
+  private String readECPolicyFromFsimageFile() throws Exception {
+    StringBuilder builder = new StringBuilder();
+    String delemiter = "\t";
+
+    File delimitedOutput = new File(tempDir, "delimitedOutput");
+
+    if (OfflineImageViewerPB.run(new String[] {"-p", "Delimited",
+        "-i", originalFsimage.getAbsolutePath(),
+        "-o", delimitedOutput.getAbsolutePath(),
+        "-ec"}) != 0) {
+      throw new IOException("oiv returned failure creating " +
+          "delimited output with ec.");
+    }
+
+    try (InputStream input = new FileInputStream(delimitedOutput);
+         BufferedReader reader =
+             new BufferedReader(new InputStreamReader(input))) {
+      String line;
+      boolean header = true;
+      while ((line = reader.readLine()) != null) {
+        String[] fields = line.split(delemiter);
+        if (!header) {
+          String path = fields[0];
+          String ecPolicy = fields[12];
+          builder.append(path).append(",").append(ecPolicy).append("\n");
+        }
+        header = false;
+      }
+    }
+    return builder.toString();
+  }
+}
\ No newline at end of file
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingPolicy.csv b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingPolicy.csv
new file mode 100644
index 0000000..862a19c
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testErasureCodingPolicy.csv
@@ -0,0 +1,28 @@
+# 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.
+
+#dir,erasure coding policy
+/,-
+/dir_wo_ec_rs63,RS-6-3-1024k
+/dir_wo_ec_rs63/sub_dir_1,-
+/dir_wo_ec_rs63/sub_dir_2,-
+/dir_wo_ec_rs63/file_wo_ec_1,RS-6-3-1024k
+/dir_wo_ec_rs63/file_wo_ec_2,RS-6-3-1024k
+/dir_wo_ec_rs32,RS-3-2-1024k
+/dir_wo_ec_rs32/sub_dir_1,-
+/dir_wo_ec_rs32/file_wo_ec,RS-3-2-1024k
+/dir_wo_rep,-
+/dir_wo_rep/sub_dir_1,-
+/dir_wo_rep/file_rep,-
\ No newline at end of file