diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 47f931f2215..22b201627a1 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -286,6 +286,55 @@ Trunk (Unreleased)
HDFS-5794. Fix the inconsistency of layout version number of
ADD_DATANODE_AND_STORAGE_UUIDS between trunk and branch-2. (jing9)
+ BREAKDOWN OF HDFS-5698 SUBTASKS AND RELATED JIRAS
+
+ HDFS-5717. Save FSImage header in protobuf. (Haohui Mai via jing9)
+
+ HDFS-5738. Serialize INode information in protobuf. (Haohui Mai via jing9)
+
+ HDFS-5772. Serialize under-construction file information in FSImage. (jing9)
+
+ HDFS-5783. Compute the digest before loading FSImage. (Haohui Mai via jing9)
+
+ HDFS-5785. Serialize symlink in protobuf. (Haohui Mai via jing9)
+
+ HDFS-5793. Optimize the serialization of PermissionStatus. (Haohui Mai via
+ jing9)
+
+ HDFS-5743. Use protobuf to serialize snapshot information. (jing9)
+
+ HDFS-5774. Serialize CachePool directives in protobuf. (Haohui Mai via jing9)
+
+ HDFS-5744. Serialize information for token managers in protobuf. (Haohui Mai
+ via jing9)
+
+ HDFS-5824. Add a Type field in Snapshot DiffEntry's protobuf definition.
+ (jing9)
+
+ HDFS-5808. Implement cancellation when saving FSImage. (Haohui Mai via jing9)
+
+ HDFS-5826. Update the stored edit logs to be consistent with the changes in
+ HDFS-5698 branch. (Haohui Mai via jing9)
+
+ HDFS-5797. Implement offline image viewer. (Haohui Mai via jing9)
+
+ HDFS-5771. Track progress when loading fsimage. (Haohui Mai via cnauroth)
+
+ HDFS-5871. Use PBHelper to serialize CacheDirectiveInfoExpirationProto.
+ (Haohui Mai via jing9)
+
+ HDFS-5884. LoadDelegator should use IOUtils.readFully() to read the magic
+ header. (Haohui Mai via jing9)
+
+ HDFS-5885. Add annotation for repeated fields in the protobuf definition.
+ (Haohui Mai via jing9)
+
+ HDFS-5906. Fixing findbugs and javadoc warnings in the HDFS-5698 branch.
+ (Haohui Mai via jing9)
+
+ HDFS-5911. The id of a CacheDirective instance does not get serialized in
+ the protobuf-fsimage. (Haohui Mai via jing9)
+
Release 2.4.0 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-hdfs-project/hadoop-hdfs/dev-support/findbugsExcludeFile.xml b/hadoop-hdfs-project/hadoop-hdfs/dev-support/findbugsExcludeFile.xml
index 028e64cad94..70b7e65f842 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/dev-support/findbugsExcludeFile.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/dev-support/findbugsExcludeFile.xml
@@ -8,6 +8,9 @@
+
+
+
diff --git a/hadoop-hdfs-project/hadoop-hdfs/pom.xml b/hadoop-hdfs-project/hadoop-hdfs/pom.xml
index 0b1e55d46c5..6cd9fea1dd7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/pom.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/pom.xml
@@ -458,6 +458,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
ClientDatanodeProtocol.proto
DatanodeProtocol.proto
+ fsimage.proto
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/bin/hdfs b/hadoop-hdfs-project/hadoop-hdfs/src/main/bin/hdfs
index fa00cd47d0e..5d823b7dd21 100755
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/bin/hdfs
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/bin/hdfs
@@ -139,7 +139,7 @@ elif [ "$COMMAND" = "balancer" ] ; then
elif [ "$COMMAND" = "jmxget" ] ; then
CLASS=org.apache.hadoop.hdfs.tools.JMXGet
elif [ "$COMMAND" = "oiv" ] ; then
- CLASS=org.apache.hadoop.hdfs.tools.offlineImageViewer.OfflineImageViewer
+ CLASS=org.apache.hadoop.hdfs.tools.offlineImageViewer.OfflineImageViewerPB
elif [ "$COMMAND" = "oev" ] ; then
CLASS=org.apache.hadoop.hdfs.tools.offlineEditsViewer.OfflineEditsViewer
elif [ "$COMMAND" = "fetchdt" ] ; then
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/LayoutVersion.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/LayoutVersion.java
index 923ed70ac8f..9842b53fbd3 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/LayoutVersion.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/LayoutVersion.java
@@ -112,7 +112,8 @@ public class LayoutVersion {
ADD_DATANODE_AND_STORAGE_UUIDS(-49, "Replace StorageID with DatanodeUuid."
+ " Use distinct StorageUuid per storage directory."),
ADD_LAYOUT_FLAGS(-50, "Add support for layout flags."),
- CACHING(-51, "Support for cache pools and path-based caching");
+ CACHING(-51, "Support for cache pools and path-based caching"),
+ PROTOBUF_FORMAT(-52, "Use protobuf to serialize FSImage");
final int lv;
final int ancestorLV;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/security/token/delegation/DelegationTokenSecretManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/security/token/delegation/DelegationTokenSecretManager.java
index e291204cc23..b9fce60446b 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/security/token/delegation/DelegationTokenSecretManager.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/security/token/delegation/DelegationTokenSecretManager.java
@@ -23,12 +23,16 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
+import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.hadoop.hdfs.server.namenode.NameNode.OperationCategory;
import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
@@ -46,6 +50,10 @@ import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
import org.apache.hadoop.security.token.delegation.DelegationKey;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.protobuf.ByteString;
+
/**
* A HDFS specific delegation token secret manager.
* The secret manager is responsible for generating and accepting the password
@@ -167,7 +175,45 @@ public class DelegationTokenSecretManager
}
serializerCompat.load(in);
}
-
+
+ public static class SecretManagerState {
+ public final SecretManagerSection section;
+ public final List keys;
+ public final List tokens;
+
+ public SecretManagerState(
+ SecretManagerSection s,
+ List keys,
+ List tokens) {
+ this.section = s;
+ this.keys = keys;
+ this.tokens = tokens;
+ }
+ }
+
+ public synchronized void loadSecretManagerState(SecretManagerState state)
+ throws IOException {
+ Preconditions.checkState(!running,
+ "Can't load state from image in a running SecretManager.");
+
+ currentId = state.section.getCurrentId();
+ delegationTokenSequenceNumber = state.section.getTokenSequenceNumber();
+ for (SecretManagerSection.DelegationKey k : state.keys) {
+ addKey(new DelegationKey(k.getId(), k.getExpiryDate(), k.hasKey() ? k
+ .getKey().toByteArray() : null));
+ }
+
+ for (SecretManagerSection.PersistToken t : state.tokens) {
+ DelegationTokenIdentifier id = new DelegationTokenIdentifier(new Text(
+ t.getOwner()), new Text(t.getRenewer()), new Text(t.getRealUser()));
+ id.setIssueDate(t.getIssueDate());
+ id.setMaxDate(t.getMaxDate());
+ id.setSequenceNumber(t.getSequenceNumber());
+ id.setMasterKeyId(t.getMasterKeyId());
+ addPersistedDelegationToken(id, t.getExpiryDate());
+ }
+ }
+
/**
* Store the current state of the SecretManager for persistence
*
@@ -179,7 +225,43 @@ public class DelegationTokenSecretManager
String sdPath) throws IOException {
serializerCompat.save(out, sdPath);
}
-
+
+ public synchronized SecretManagerState saveSecretManagerState() {
+ SecretManagerSection s = SecretManagerSection.newBuilder()
+ .setCurrentId(currentId)
+ .setTokenSequenceNumber(delegationTokenSequenceNumber)
+ .setNumKeys(allKeys.size()).setNumTokens(currentTokens.size()).build();
+ ArrayList keys = Lists
+ .newArrayListWithCapacity(allKeys.size());
+ ArrayList tokens = Lists
+ .newArrayListWithCapacity(currentTokens.size());
+
+ for (DelegationKey v : allKeys.values()) {
+ SecretManagerSection.DelegationKey.Builder b = SecretManagerSection.DelegationKey
+ .newBuilder().setId(v.getKeyId()).setExpiryDate(v.getExpiryDate());
+ if (v.getEncodedKey() != null) {
+ b.setKey(ByteString.copyFrom(v.getEncodedKey()));
+ }
+ keys.add(b.build());
+ }
+
+ for (Entry e : currentTokens
+ .entrySet()) {
+ DelegationTokenIdentifier id = e.getKey();
+ SecretManagerSection.PersistToken.Builder b = SecretManagerSection.PersistToken
+ .newBuilder().setOwner(id.getOwner().toString())
+ .setRenewer(id.getRenewer().toString())
+ .setRealUser(id.getRealUser().toString())
+ .setIssueDate(id.getIssueDate()).setMaxDate(id.getMaxDate())
+ .setSequenceNumber(id.getSequenceNumber())
+ .setMasterKeyId(id.getMasterKeyId())
+ .setExpiryDate(e.getValue().getRenewDate());
+ tokens.add(b.build());
+ }
+
+ return new SecretManagerState(s, keys, tokens);
+ }
+
/**
* This method is intended to be used only while reading edit logs.
*
@@ -431,4 +513,5 @@ public class DelegationTokenSecretManager
prog.endStep(Phase.LOADING_FSIMAGE, step);
}
}
+
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CacheManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CacheManager.java
index ba3936ca997..de536b30d79 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CacheManager.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CacheManager.java
@@ -50,8 +50,10 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BatchedRemoteIterator.BatchedListEntries;
import org.apache.hadoop.fs.CacheFlag;
import org.apache.hadoop.fs.InvalidRequestException;
+import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.UnresolvedLinkException;
import org.apache.hadoop.fs.permission.FsAction;
+import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.protocol.CacheDirective;
import org.apache.hadoop.hdfs.protocol.CacheDirectiveEntry;
@@ -62,11 +64,15 @@ import org.apache.hadoop.hdfs.protocol.CachePoolEntry;
import org.apache.hadoop.hdfs.protocol.CachePoolInfo;
import org.apache.hadoop.hdfs.protocol.DatanodeID;
import org.apache.hadoop.hdfs.protocol.LocatedBlock;
+import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoProto;
+import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CachePoolInfoProto;
+import org.apache.hadoop.hdfs.protocolPB.PBHelper;
import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
import org.apache.hadoop.hdfs.server.blockmanagement.CacheReplicationMonitor;
import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor.CachedBlocksList;
import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor.CachedBlocksList.Type;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.CacheManagerSection;
import org.apache.hadoop.hdfs.server.namenode.metrics.NameNodeMetrics;
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
@@ -81,6 +87,7 @@ import org.apache.hadoop.util.LightWeightGSet;
import org.apache.hadoop.util.Time;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
/**
* The Cache Manager handles caching on DataNodes.
@@ -167,6 +174,19 @@ public final class CacheManager {
*/
private CacheReplicationMonitor monitor;
+ public static final class PersistState {
+ public final CacheManagerSection section;
+ public final List pools;
+ public final List directives;
+
+ public PersistState(CacheManagerSection section,
+ List pools, List directives) {
+ this.section = section;
+ this.pools = pools;
+ this.directives = directives;
+ }
+ }
+
CacheManager(FSNamesystem namesystem, Configuration conf,
BlockManager blockManager) {
this.namesystem = namesystem;
@@ -944,6 +964,64 @@ public final class CacheManager {
serializerCompat.save(out, sdPath);
}
+ public PersistState saveState() throws IOException {
+ ArrayList pools = Lists
+ .newArrayListWithCapacity(cachePools.size());
+ ArrayList directives = Lists
+ .newArrayListWithCapacity(directivesById.size());
+
+ for (CachePool pool : cachePools.values()) {
+ CachePoolInfo p = pool.getInfo(true);
+ CachePoolInfoProto.Builder b = CachePoolInfoProto.newBuilder()
+ .setPoolName(p.getPoolName());
+
+ if (p.getOwnerName() != null)
+ b.setOwnerName(p.getOwnerName());
+
+ if (p.getGroupName() != null)
+ b.setGroupName(p.getGroupName());
+
+ if (p.getMode() != null)
+ b.setMode(p.getMode().toShort());
+
+ if (p.getLimit() != null)
+ b.setLimit(p.getLimit());
+
+ pools.add(b.build());
+ }
+
+ for (CacheDirective directive : directivesById.values()) {
+ CacheDirectiveInfo info = directive.toInfo();
+ CacheDirectiveInfoProto.Builder b = CacheDirectiveInfoProto.newBuilder()
+ .setId(info.getId());
+
+ if (info.getPath() != null) {
+ b.setPath(info.getPath().toUri().getPath());
+ }
+
+ if (info.getReplication() != null) {
+ b.setReplication(info.getReplication());
+ }
+
+ if (info.getPool() != null) {
+ b.setPool(info.getPool());
+ }
+
+ Expiration expiry = info.getExpiration();
+ if (expiry != null) {
+ assert (!expiry.isRelative());
+ b.setExpiration(PBHelper.convert(expiry));
+ }
+
+ directives.add(b.build());
+ }
+ CacheManagerSection s = CacheManagerSection.newBuilder()
+ .setNextDirectiveId(nextDirectiveId).setNumPools(pools.size())
+ .setNumDirectives(directives.size()).build();
+
+ return new PersistState(s, pools, directives);
+ }
+
/**
* Reloads CacheManager state from the passed DataInput. Used during namenode
* startup to restore CacheManager state from an FSImage.
@@ -954,6 +1032,56 @@ public final class CacheManager {
serializerCompat.load(in);
}
+ public void loadState(PersistState s) throws IOException {
+ nextDirectiveId = s.section.getNextDirectiveId();
+ for (CachePoolInfoProto p : s.pools) {
+ CachePoolInfo info = new CachePoolInfo(p.getPoolName());
+ if (p.hasOwnerName())
+ info.setOwnerName(p.getOwnerName());
+
+ if (p.hasGroupName())
+ info.setGroupName(p.getGroupName());
+
+ if (p.hasMode())
+ info.setMode(new FsPermission((short) p.getMode()));
+
+ if (p.hasLimit())
+ info.setLimit(p.getLimit());
+
+ addCachePool(info);
+ }
+
+ for (CacheDirectiveInfoProto p : s.directives) {
+ // Get pool reference by looking it up in the map
+ final String poolName = p.getPool();
+ CacheDirective directive = new CacheDirective(p.getId(), new Path(
+ p.getPath()).toUri().getPath(), (short) p.getReplication(), p
+ .getExpiration().getMillis());
+ addCacheDirective(poolName, directive);
+ }
+ }
+
+ private void addCacheDirective(final String poolName,
+ final CacheDirective directive) throws IOException {
+ CachePool pool = cachePools.get(poolName);
+ if (pool == null) {
+ throw new IOException("Directive refers to pool " + poolName
+ + ", which does not exist.");
+ }
+ boolean addedDirective = pool.getDirectiveList().add(directive);
+ assert addedDirective;
+ if (directivesById.put(directive.getId(), directive) != null) {
+ throw new IOException("A directive with ID " + directive.getId()
+ + " already exists");
+ }
+ List directives = directivesByPath.get(directive.getPath());
+ if (directives == null) {
+ directives = new LinkedList();
+ directivesByPath.put(directive.getPath(), directives);
+ }
+ directives.add(directive);
+ }
+
private final class SerializerCompat {
private void save(DataOutputStream out, String sdPath) throws IOException {
out.writeLong(nextDirectiveId);
@@ -1036,27 +1164,10 @@ public final class CacheManager {
CacheDirectiveInfo info = FSImageSerialization.readCacheDirectiveInfo(in);
// Get pool reference by looking it up in the map
final String poolName = info.getPool();
- CachePool pool = cachePools.get(poolName);
- if (pool == null) {
- throw new IOException("Directive refers to pool " + poolName +
- ", which does not exist.");
- }
CacheDirective directive =
new CacheDirective(info.getId(), info.getPath().toUri().getPath(),
info.getReplication(), info.getExpiration().getAbsoluteMillis());
- boolean addedDirective = pool.getDirectiveList().add(directive);
- assert addedDirective;
- if (directivesById.put(directive.getId(), directive) != null) {
- throw new IOException("A directive with ID " + directive.getId() +
- " already exists");
- }
- List directives =
- directivesByPath.get(directive.getPath());
- if (directives == null) {
- directives = new LinkedList();
- directivesByPath.put(directive.getPath(), directives);
- }
- directives.add(directive);
+ addCacheDirective(poolName, directive);
counter.increment();
}
prog.endStep(Phase.LOADING_FSIMAGE, step);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
index 166ffb2fd9b..62020173f4c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
@@ -797,8 +797,7 @@ public class FSImage implements Closeable {
*/
private void loadFSImage(File curFile, MD5Hash expectedMd5,
FSNamesystem target, MetaRecoveryContext recovery) throws IOException {
- FSImageFormat.Loader loader = new FSImageFormat.Loader(
- conf, target);
+ FSImageFormat.LoaderDelegator loader = FSImageFormat.newLoader(conf, target);
loader.load(curFile);
target.setBlockPoolId(this.getBlockPoolID());
@@ -827,7 +826,7 @@ public class FSImage implements Closeable {
File newFile = NNStorage.getStorageFile(sd, NameNodeFile.IMAGE_NEW, txid);
File dstFile = NNStorage.getStorageFile(sd, NameNodeFile.IMAGE, txid);
- FSImageFormat.Saver saver = new FSImageFormat.Saver(context);
+ FSImageFormatProtobuf.Saver saver = new FSImageFormatProtobuf.Saver(context);
FSImageCompression compression = FSImageCompression.createCompression(conf);
saver.save(newFile, compression);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageCompression.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageCompression.java
index e0a46f15445..872ee74c802 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageCompression.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageCompression.java
@@ -57,6 +57,10 @@ class FSImageCompression {
imageCodec = codec;
}
+ public CompressionCodec getImageCodec() {
+ return imageCodec;
+ }
+
/**
* Create a "noop" compression - i.e. uncompressed
*/
@@ -89,7 +93,7 @@ class FSImageCompression {
* Create a compression instance using the codec specified by
* codecClassName
*/
- private static FSImageCompression createCompression(Configuration conf,
+ static FSImageCompression createCompression(Configuration conf,
String codecClassName)
throws IOException {
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormat.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormat.java
index 3ad258a4512..bcbad75d810 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormat.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormat.java
@@ -68,12 +68,13 @@ import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress.Co
import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
import org.apache.hadoop.hdfs.util.ReadOnlyList;
+import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.MD5Hash;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.util.StringUtils;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.annotations.VisibleForTesting;
/**
* Contains inner classes for reading or writing the on-disk format for
@@ -180,16 +181,74 @@ import com.google.common.base.Preconditions;
@InterfaceStability.Evolving
public class FSImageFormat {
private static final Log LOG = FSImage.LOG;
-
+
// Static-only class
private FSImageFormat() {}
-
+
+ interface AbstractLoader {
+ MD5Hash getLoadedImageMd5();
+ long getLoadedImageTxId();
+ }
+
+ static class LoaderDelegator implements AbstractLoader {
+ private AbstractLoader impl;
+ private final Configuration conf;
+ private final FSNamesystem fsn;
+
+ LoaderDelegator(Configuration conf, FSNamesystem fsn) {
+ this.conf = conf;
+ this.fsn = fsn;
+ }
+
+ @Override
+ public MD5Hash getLoadedImageMd5() {
+ return impl.getLoadedImageMd5();
+ }
+
+ @Override
+ public long getLoadedImageTxId() {
+ return impl.getLoadedImageTxId();
+ }
+
+ public void load(File file) throws IOException {
+ Preconditions.checkState(impl == null, "Image already loaded!");
+
+ FileInputStream is = null;
+ try {
+ is = new FileInputStream(file);
+ byte[] magic = new byte[FSImageUtil.MAGIC_HEADER.length];
+ IOUtils.readFully(is, magic, 0, magic.length);
+ if (Arrays.equals(magic, FSImageUtil.MAGIC_HEADER)) {
+ FSImageFormatProtobuf.Loader loader = new FSImageFormatProtobuf.Loader(
+ conf, fsn);
+ impl = loader;
+ loader.load(file);
+ } else {
+ Loader loader = new Loader(conf, fsn);
+ impl = loader;
+ loader.load(file);
+ }
+
+ } finally {
+ IOUtils.cleanup(LOG, is);
+ }
+ }
+ }
+
+ /**
+ * Construct a loader class to load the image. It chooses the loader based on
+ * the layout version.
+ */
+ public static LoaderDelegator newLoader(Configuration conf, FSNamesystem fsn) {
+ return new LoaderDelegator(conf, fsn);
+ }
+
/**
* A one-shot class responsible for loading an image. The load() function
* should be called once, after which the getter methods may be used to retrieve
* information about the image that was loaded, if loading was successful.
*/
- public static class Loader {
+ public static class Loader implements AbstractLoader {
private final Configuration conf;
/** which namesystem this loader is working for */
private final FSNamesystem namesystem;
@@ -214,12 +273,14 @@ public class FSImageFormat {
* Return the MD5 checksum of the image that has been loaded.
* @throws IllegalStateException if load() has not yet been called.
*/
- MD5Hash getLoadedImageMd5() {
+ @Override
+ public MD5Hash getLoadedImageMd5() {
checkLoaded();
return imgDigest;
}
- long getLoadedImageTxId() {
+ @Override
+ public long getLoadedImageTxId() {
checkLoaded();
return imgTxId;
}
@@ -242,7 +303,7 @@ public class FSImageFormat {
}
}
- void load(File curFile) throws IOException {
+ public void load(File curFile) throws IOException {
checkNotLoaded();
assert curFile != null : "curFile is null";
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatPBINode.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatPBINode.java
new file mode 100644
index 00000000000..5ade5cec6a3
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatPBINode.java
@@ -0,0 +1,466 @@
+/**
+ * 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.server.namenode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.HadoopIllegalArgumentException;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.fs.permission.PermissionStatus;
+import org.apache.hadoop.hdfs.protocol.Block;
+import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.BlockProto;
+import org.apache.hadoop.hdfs.protocolPB.PBHelper;
+import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo;
+import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoUnderConstruction;
+import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
+import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.StringMap;
+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.INodeDirectorySection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
+import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference;
+import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
+import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
+import org.apache.hadoop.hdfs.util.ReadOnlyList;
+
+import com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
+
+@InterfaceAudience.Private
+public final class FSImageFormatPBINode {
+ private final static long USER_GROUP_STRID_MASK = (1 << 24) - 1;
+ private final static int USER_STRID_OFFSET = 40;
+ private final static int GROUP_STRID_OFFSET = 16;
+ private static final Log LOG = LogFactory.getLog(FSImageFormatProtobuf.class);
+
+ public final static class Loader {
+ public static PermissionStatus loadPermission(long id,
+ final String[] stringTable) {
+ short perm = (short) (id & ((1 << GROUP_STRID_OFFSET) - 1));
+ int gsid = (int) ((id >> GROUP_STRID_OFFSET) & USER_GROUP_STRID_MASK);
+ int usid = (int) ((id >> USER_STRID_OFFSET) & USER_GROUP_STRID_MASK);
+ return new PermissionStatus(stringTable[usid], stringTable[gsid],
+ new FsPermission(perm));
+ }
+
+ public static INodeReference loadINodeReference(
+ INodeSection.INodeReference r, FSDirectory dir) throws IOException {
+ long referredId = r.getReferredId();
+ INode referred = dir.getInode(referredId);
+ WithCount withCount = (WithCount) referred.getParentReference();
+ if (withCount == null) {
+ withCount = new INodeReference.WithCount(null, referred);
+ }
+ final INodeReference ref;
+ if (r.hasDstSnapshotId()) { // DstReference
+ ref = new INodeReference.DstReference(null, withCount,
+ r.getDstSnapshotId());
+ } else {
+ ref = new INodeReference.WithName(null, withCount, r.getName()
+ .toByteArray(), r.getLastSnapshotId());
+ }
+ return ref;
+ }
+
+ public static INodeDirectory loadINodeDirectory(INodeSection.INode n,
+ final String[] stringTable) {
+ assert n.getType() == INodeSection.INode.Type.DIRECTORY;
+ INodeSection.INodeDirectory d = n.getDirectory();
+
+ final PermissionStatus permissions = loadPermission(d.getPermission(),
+ stringTable);
+ final INodeDirectory dir = new INodeDirectory(n.getId(), n.getName()
+ .toByteArray(), permissions, d.getModificationTime());
+
+ final long nsQuota = d.getNsQuota(), dsQuota = d.getDsQuota();
+ if (nsQuota >= 0 || dsQuota >= 0) {
+ dir.addDirectoryWithQuotaFeature(nsQuota, dsQuota);
+ }
+ return dir;
+ }
+
+ public static void updateBlocksMap(INodeFile file, BlockManager bm) {
+ // Add file->block mapping
+ final BlockInfo[] blocks = file.getBlocks();
+ if (blocks != null) {
+ for (int i = 0; i < blocks.length; i++) {
+ file.setBlock(i, bm.addBlockCollection(blocks[i], file));
+ }
+ }
+ }
+
+ private final FSDirectory dir;
+ private final FSNamesystem fsn;
+ private final FSImageFormatProtobuf.Loader parent;
+
+ Loader(FSNamesystem fsn, final FSImageFormatProtobuf.Loader parent) {
+ this.fsn = fsn;
+ this.dir = fsn.dir;
+ this.parent = parent;
+ }
+
+ void loadINodeDirectorySection(InputStream in) throws IOException {
+ while (true) {
+ INodeDirectorySection.DirEntry e = INodeDirectorySection.DirEntry
+ .parseDelimitedFrom(in);
+ // note that in is a LimitedInputStream
+ if (e == null) {
+ break;
+ }
+ INodeDirectory p = dir.getInode(e.getParent()).asDirectory();
+ for (long id : e.getChildrenList()) {
+ INode child = dir.getInode(id);
+ addToParent(p, child);
+ }
+ for (int i = 0; i < e.getNumOfRef(); i++) {
+ INodeReference ref = loadINodeReference(in);
+ addToParent(p, ref);
+ }
+ }
+ }
+
+ private INodeReference loadINodeReference(InputStream in)
+ throws IOException {
+ INodeSection.INodeReference ref = INodeSection.INodeReference
+ .parseDelimitedFrom(in);
+ return loadINodeReference(ref, dir);
+ }
+
+ void loadINodeSection(InputStream in) throws IOException {
+ INodeSection s = INodeSection.parseDelimitedFrom(in);
+ fsn.resetLastInodeId(s.getLastInodeId());
+ LOG.info("Loading " + s.getNumInodes() + " INodes.");
+ for (int i = 0; i < s.getNumInodes(); ++i) {
+ INodeSection.INode p = INodeSection.INode.parseDelimitedFrom(in);
+ if (p.getId() == INodeId.ROOT_INODE_ID) {
+ loadRootINode(p);
+ } else {
+ INode n = loadINode(p);
+ dir.addToInodeMap(n);
+ }
+ }
+ }
+
+ /**
+ * Load the under-construction files section, and update the lease map
+ */
+ void loadFilesUnderConstructionSection(InputStream in) throws IOException {
+ while (true) {
+ FileUnderConstructionEntry entry = FileUnderConstructionEntry
+ .parseDelimitedFrom(in);
+ if (entry == null) {
+ break;
+ }
+ // update the lease manager
+ INodeFile file = dir.getInode(entry.getInodeId()).asFile();
+ FileUnderConstructionFeature uc = file.getFileUnderConstructionFeature();
+ Preconditions.checkState(uc != null); // file must be under-construction
+ fsn.leaseManager.addLease(uc.getClientName(), entry.getFullPath());
+ }
+ }
+
+ private void addToParent(INodeDirectory parent, INode child) {
+ if (parent == dir.rootDir && FSDirectory.isReservedName(child)) {
+ throw new HadoopIllegalArgumentException("File name \""
+ + child.getLocalName() + "\" is reserved. Please "
+ + " change the name of the existing file or directory to another "
+ + "name before upgrading to this release.");
+ }
+ // NOTE: This does not update space counts for parents
+ if (!parent.addChild(child)) {
+ return;
+ }
+ dir.cacheName(child);
+
+ if (child.isFile()) {
+ updateBlocksMap(child.asFile(), fsn.getBlockManager());
+ }
+ }
+
+ private INode loadINode(INodeSection.INode n) {
+ switch (n.getType()) {
+ case FILE:
+ return loadINodeFile(n);
+ case DIRECTORY:
+ return loadINodeDirectory(n, parent.getStringTable());
+ case SYMLINK:
+ return loadINodeSymlink(n);
+ default:
+ break;
+ }
+ return null;
+ }
+
+ private INodeFile loadINodeFile(INodeSection.INode n) {
+ assert n.getType() == INodeSection.INode.Type.FILE;
+ INodeSection.INodeFile f = n.getFile();
+ List bp = f.getBlocksList();
+ short replication = (short) f.getReplication();
+
+ BlockInfo[] blocks = new BlockInfo[bp.size()];
+ for (int i = 0, e = bp.size(); i < e; ++i) {
+ blocks[i] = new BlockInfo(PBHelper.convert(bp.get(i)), replication);
+ }
+ final PermissionStatus permissions = loadPermission(f.getPermission(),
+ parent.getStringTable());
+
+ final INodeFile file = new INodeFile(n.getId(),
+ n.getName().toByteArray(), permissions, f.getModificationTime(),
+ f.getAccessTime(), blocks, replication, f.getPreferredBlockSize());
+ // under-construction information
+ if (f.hasFileUC()) {
+ INodeSection.FileUnderConstructionFeature uc = f.getFileUC();
+ file.toUnderConstruction(uc.getClientName(), uc.getClientMachine(),
+ null);
+ if (blocks.length > 0) {
+ BlockInfo lastBlk = file.getLastBlock();
+ // replace the last block of file
+ file.setBlock(file.numBlocks() - 1, new BlockInfoUnderConstruction(
+ lastBlk, replication));
+ }
+ }
+ return file;
+ }
+
+
+ private INodeSymlink loadINodeSymlink(INodeSection.INode n) {
+ assert n.getType() == INodeSection.INode.Type.SYMLINK;
+ INodeSection.INodeSymlink s = n.getSymlink();
+ final PermissionStatus permissions = loadPermission(s.getPermission(),
+ parent.getStringTable());
+ return new INodeSymlink(n.getId(), n.getName().toByteArray(), permissions,
+ 0, 0, s.getTarget().toStringUtf8());
+ }
+
+ private void loadRootINode(INodeSection.INode p) {
+ INodeDirectory root = loadINodeDirectory(p, parent.getStringTable());
+ final Quota.Counts q = root.getQuotaCounts();
+ final long nsQuota = q.get(Quota.NAMESPACE);
+ final long dsQuota = q.get(Quota.DISKSPACE);
+ if (nsQuota != -1 || dsQuota != -1) {
+ dir.rootDir.getDirectoryWithQuotaFeature().setQuota(nsQuota, dsQuota);
+ }
+ dir.rootDir.cloneModificationTime(root);
+ dir.rootDir.clonePermissionStatus(root);
+ }
+ }
+
+ public final static class Saver {
+ private static long buildPermissionStatus(INodeAttributes n,
+ final StringMap stringMap) {
+ long userId = stringMap.getStringId(n.getUserName());
+ long groupId = stringMap.getStringId(n.getGroupName());
+ return ((userId & USER_GROUP_STRID_MASK) << USER_STRID_OFFSET)
+ | ((groupId & USER_GROUP_STRID_MASK) << GROUP_STRID_OFFSET)
+ | n.getFsPermissionShort();
+ }
+
+ public static INodeSection.INodeFile.Builder buildINodeFile(
+ INodeFileAttributes file, final StringMap stringMap) {
+ INodeSection.INodeFile.Builder b = INodeSection.INodeFile.newBuilder()
+ .setAccessTime(file.getAccessTime())
+ .setModificationTime(file.getModificationTime())
+ .setPermission(buildPermissionStatus(file, stringMap))
+ .setPreferredBlockSize(file.getPreferredBlockSize())
+ .setReplication(file.getFileReplication());
+ return b;
+ }
+
+ public static INodeSection.INodeDirectory.Builder buildINodeDirectory(
+ INodeDirectoryAttributes dir, final StringMap stringMap) {
+ Quota.Counts quota = dir.getQuotaCounts();
+ INodeSection.INodeDirectory.Builder b = INodeSection.INodeDirectory
+ .newBuilder().setModificationTime(dir.getModificationTime())
+ .setNsQuota(quota.get(Quota.NAMESPACE))
+ .setDsQuota(quota.get(Quota.DISKSPACE))
+ .setPermission(buildPermissionStatus(dir, stringMap));
+ return b;
+ }
+
+ public static INodeSection.INodeReference.Builder buildINodeReference(
+ INodeReference ref) throws IOException {
+ INodeSection.INodeReference.Builder rb = INodeSection.INodeReference
+ .newBuilder().setReferredId(ref.getId());
+ if (ref instanceof WithName) {
+ rb.setLastSnapshotId(((WithName) ref).getLastSnapshotId()).setName(
+ ByteString.copyFrom(ref.getLocalNameBytes()));
+ } else if (ref instanceof DstReference) {
+ rb.setDstSnapshotId(((DstReference) ref).getDstSnapshotId());
+ }
+ return rb;
+ }
+
+ private final FSNamesystem fsn;
+ private final FileSummary.Builder summary;
+ private final SaveNamespaceContext context;
+ private final FSImageFormatProtobuf.Saver parent;
+
+ Saver(FSImageFormatProtobuf.Saver parent, FileSummary.Builder summary) {
+ this.parent = parent;
+ this.summary = summary;
+ this.context = parent.getContext();
+ this.fsn = context.getSourceNamesystem();
+ }
+
+ void serializeINodeDirectorySection(OutputStream out) throws IOException {
+ Iterator iter = fsn.getFSDirectory()
+ .getINodeMap().getMapIterator();
+ int i = 0;
+ while (iter.hasNext()) {
+ INodeWithAdditionalFields n = iter.next();
+ if (!n.isDirectory()) {
+ continue;
+ }
+
+ ReadOnlyList children = n.asDirectory().getChildrenList(
+ Snapshot.CURRENT_STATE_ID);
+ if (children.size() > 0) {
+ INodeDirectorySection.DirEntry.Builder b = INodeDirectorySection.
+ DirEntry.newBuilder().setParent(n.getId());
+ List refs = new ArrayList();
+ for (INode inode : children) {
+ if (!inode.isReference()) {
+ b.addChildren(inode.getId());
+ } else {
+ refs.add(inode.asReference());
+ }
+ }
+ b.setNumOfRef(refs.size());
+ INodeDirectorySection.DirEntry e = b.build();
+ e.writeDelimitedTo(out);
+ for (INodeReference ref : refs) {
+ INodeSection.INodeReference.Builder rb = buildINodeReference(ref);
+ rb.build().writeDelimitedTo(out);
+ }
+ }
+
+ ++i;
+ if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
+ context.checkCancelled();
+ }
+ }
+ parent.commitSection(summary,
+ FSImageFormatProtobuf.SectionName.INODE_DIR);
+ }
+
+ void serializeINodeSection(OutputStream out) throws IOException {
+ INodeMap inodesMap = fsn.dir.getINodeMap();
+
+ INodeSection.Builder b = INodeSection.newBuilder()
+ .setLastInodeId(fsn.getLastInodeId()).setNumInodes(inodesMap.size());
+ INodeSection s = b.build();
+ s.writeDelimitedTo(out);
+
+ int i = 0;
+ Iterator iter = inodesMap.getMapIterator();
+ while (iter.hasNext()) {
+ INodeWithAdditionalFields n = iter.next();
+ save(out, n);
+ ++i;
+ if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
+ context.checkCancelled();
+ }
+ }
+ parent.commitSection(summary, FSImageFormatProtobuf.SectionName.INODE);
+ }
+
+ void serializeFilesUCSection(OutputStream out) throws IOException {
+ Map ucMap = fsn.getFilesUnderConstruction();
+ for (Map.Entry entry : ucMap.entrySet()) {
+ String path = entry.getKey();
+ INodeFile file = entry.getValue();
+ FileUnderConstructionEntry.Builder b = FileUnderConstructionEntry
+ .newBuilder().setInodeId(file.getId()).setFullPath(path);
+ FileUnderConstructionEntry e = b.build();
+ e.writeDelimitedTo(out);
+ }
+ parent.commitSection(summary,
+ FSImageFormatProtobuf.SectionName.FILES_UNDERCONSTRUCTION);
+ }
+
+ private void save(OutputStream out, INode n) throws IOException {
+ if (n.isDirectory()) {
+ save(out, n.asDirectory());
+ } else if (n.isFile()) {
+ save(out, n.asFile());
+ } else if (n.isSymlink()) {
+ save(out, n.asSymlink());
+ }
+ }
+
+ private void save(OutputStream out, INodeDirectory n) throws IOException {
+ INodeSection.INodeDirectory.Builder b = buildINodeDirectory(n,
+ parent.getStringMap());
+ INodeSection.INode r = buildINodeCommon(n)
+ .setType(INodeSection.INode.Type.DIRECTORY).setDirectory(b).build();
+ r.writeDelimitedTo(out);
+ }
+
+ private void save(OutputStream out, INodeFile n) throws IOException {
+ INodeSection.INodeFile.Builder b = buildINodeFile(n,
+ parent.getStringMap());
+
+ for (Block block : n.getBlocks()) {
+ b.addBlocks(PBHelper.convert(block));
+ }
+
+ FileUnderConstructionFeature uc = n.getFileUnderConstructionFeature();
+ if (uc != null) {
+ INodeSection.FileUnderConstructionFeature f =
+ INodeSection.FileUnderConstructionFeature
+ .newBuilder().setClientName(uc.getClientName())
+ .setClientMachine(uc.getClientMachine()).build();
+ b.setFileUC(f);
+ }
+
+ INodeSection.INode r = buildINodeCommon(n)
+ .setType(INodeSection.INode.Type.FILE).setFile(b).build();
+ r.writeDelimitedTo(out);
+ }
+
+ private void save(OutputStream out, INodeSymlink n) throws IOException {
+ INodeSection.INodeSymlink.Builder b = INodeSection.INodeSymlink
+ .newBuilder()
+ .setPermission(buildPermissionStatus(n, parent.getStringMap()))
+ .setTarget(ByteString.copyFrom(n.getSymlink()));
+ INodeSection.INode r = buildINodeCommon(n)
+ .setType(INodeSection.INode.Type.SYMLINK).setSymlink(b).build();
+ r.writeDelimitedTo(out);
+ }
+
+ private final INodeSection.INode.Builder buildINodeCommon(INode n) {
+ return INodeSection.INode.newBuilder()
+ .setId(n.getId())
+ .setName(ByteString.copyFrom(n.getLocalNameBytes()));
+ }
+ }
+
+ private FSImageFormatPBINode() {
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatProtobuf.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatProtobuf.java
new file mode 100644
index 00000000000..2edc57b18d7
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatProtobuf.java
@@ -0,0 +1,551 @@
+/**
+ * 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.server.namenode;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.protocol.LayoutVersion;
+import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoProto;
+import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CachePoolInfoProto;
+import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSecretManager;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.CacheManagerSection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
+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.StringTableSection;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot;
+import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
+import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
+import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
+import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
+import org.apache.hadoop.hdfs.util.MD5FileUtils;
+import org.apache.hadoop.io.MD5Hash;
+import org.apache.hadoop.io.compress.CompressionCodec;
+import org.apache.hadoop.io.compress.CompressorStream;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.LimitInputStream;
+import com.google.protobuf.CodedOutputStream;
+
+/**
+ * Utility class to read / write fsimage in protobuf format.
+ */
+@InterfaceAudience.Private
+public final class FSImageFormatProtobuf {
+ private static final Log LOG = LogFactory.getLog(FSImageFormatProtobuf.class);
+
+ public static final class Loader implements FSImageFormat.AbstractLoader {
+ static final int MINIMUM_FILE_LENGTH = 8;
+ private final Configuration conf;
+ private final FSNamesystem fsn;
+
+ private String[] stringTable;
+
+ /** The MD5 sum of the loaded file */
+ private MD5Hash imgDigest;
+ /** The transaction ID of the last edit represented by the loaded file */
+ private long imgTxId;
+
+ Loader(Configuration conf, FSNamesystem fsn) {
+ this.conf = conf;
+ this.fsn = fsn;
+ }
+
+ @Override
+ public MD5Hash getLoadedImageMd5() {
+ return imgDigest;
+ }
+
+ @Override
+ public long getLoadedImageTxId() {
+ return imgTxId;
+ }
+
+ public String[] getStringTable() {
+ return stringTable;
+ }
+
+ void load(File file) throws IOException {
+ long start = System.currentTimeMillis();
+ imgDigest = MD5FileUtils.computeMd5ForFile(file);
+ RandomAccessFile raFile = new RandomAccessFile(file, "r");
+ FileInputStream fin = new FileInputStream(file);
+ try {
+ loadInternal(raFile, fin);
+ long end = System.currentTimeMillis();
+ LOG.info("Loaded FSImage in " + (end - start) / 1000 + " seconds.");
+ } finally {
+ fin.close();
+ raFile.close();
+ }
+ }
+
+ private void loadInternal(RandomAccessFile raFile, FileInputStream fin)
+ throws IOException {
+ if (!FSImageUtil.checkFileFormat(raFile)) {
+ throw new IOException("Unrecognized file format");
+ }
+ FileSummary summary = FSImageUtil.loadSummary(raFile);
+
+ FileChannel channel = fin.getChannel();
+
+ FSImageFormatPBINode.Loader inodeLoader = new FSImageFormatPBINode.Loader(
+ fsn, this);
+ FSImageFormatPBSnapshot.Loader snapshotLoader = new FSImageFormatPBSnapshot.Loader(
+ fsn, this);
+
+ ArrayList sections = Lists.newArrayList(summary
+ .getSectionsList());
+ Collections.sort(sections, new Comparator() {
+ @Override
+ public int compare(FileSummary.Section s1, FileSummary.Section s2) {
+ SectionName n1 = SectionName.fromString(s1.getName());
+ SectionName n2 = SectionName.fromString(s2.getName());
+ if (n1 == null) {
+ return n2 == null ? 0 : -1;
+ } else if (n2 == null) {
+ return -1;
+ } else {
+ return n1.ordinal() - n2.ordinal();
+ }
+ }
+ });
+
+ StartupProgress prog = NameNode.getStartupProgress();
+ /**
+ * beginStep() and the endStep() calls do not match the boundary of the
+ * sections. This is because that the current implementation only allows
+ * a particular step to be started for once.
+ */
+ Step currentStep = null;
+
+ for (FileSummary.Section s : sections) {
+ channel.position(s.getOffset());
+ InputStream in = new BufferedInputStream(new LimitInputStream(fin,
+ s.getLength()));
+
+ in = FSImageUtil.wrapInputStreamForCompression(conf,
+ summary.getCodec(), in);
+
+ String n = s.getName();
+
+ switch (SectionName.fromString(n)) {
+ case NS_INFO:
+ loadNameSystemSection(in);
+ break;
+ case STRING_TABLE:
+ loadStringTableSection(in);
+ break;
+ case INODE: {
+ currentStep = new Step(StepType.INODES);
+ prog.beginStep(Phase.LOADING_FSIMAGE, currentStep);
+ inodeLoader.loadINodeSection(in);
+ }
+ break;
+ case INODE_DIR:
+ inodeLoader.loadINodeDirectorySection(in);
+ break;
+ case FILES_UNDERCONSTRUCTION:
+ inodeLoader.loadFilesUnderConstructionSection(in);
+ break;
+ case SNAPSHOT:
+ snapshotLoader.loadSnapshotSection(in);
+ break;
+ case SNAPSHOT_DIFF:
+ snapshotLoader.loadSnapshotDiffSection(in);
+ break;
+ case SECRET_MANAGER: {
+ prog.endStep(Phase.LOADING_FSIMAGE, currentStep);
+ Step step = new Step(StepType.DELEGATION_TOKENS);
+ prog.beginStep(Phase.LOADING_FSIMAGE, step);
+ loadSecretManagerSection(in);
+ prog.endStep(Phase.LOADING_FSIMAGE, step);
+ }
+ break;
+ case CACHE_MANAGER: {
+ Step step = new Step(StepType.CACHE_POOLS);
+ prog.beginStep(Phase.LOADING_FSIMAGE, step);
+ loadCacheManagerSection(in);
+ prog.endStep(Phase.LOADING_FSIMAGE, step);
+ }
+ break;
+ default:
+ LOG.warn("Unregconized section " + n);
+ break;
+ }
+ }
+ }
+
+ private void loadNameSystemSection(InputStream in) throws IOException {
+ NameSystemSection s = NameSystemSection.parseDelimitedFrom(in);
+ fsn.setGenerationStampV1(s.getGenstampV1());
+ fsn.setGenerationStampV2(s.getGenstampV2());
+ fsn.setGenerationStampV1Limit(s.getGenstampV1Limit());
+ fsn.setLastAllocatedBlockId(s.getLastAllocatedBlockId());
+ imgTxId = s.getTransactionId();
+ }
+
+ private void loadStringTableSection(InputStream in) throws IOException {
+ StringTableSection s = StringTableSection.parseDelimitedFrom(in);
+ stringTable = new String[s.getNumEntry() + 1];
+ for (int i = 0; i < s.getNumEntry(); ++i) {
+ StringTableSection.Entry e = StringTableSection.Entry
+ .parseDelimitedFrom(in);
+ stringTable[e.getId()] = e.getStr();
+ }
+ }
+
+ private void loadSecretManagerSection(InputStream in) throws IOException {
+ SecretManagerSection s = SecretManagerSection.parseDelimitedFrom(in);
+ int numKeys = s.getNumKeys(), numTokens = s.getNumTokens();
+ ArrayList keys = Lists
+ .newArrayListWithCapacity(numKeys);
+ ArrayList tokens = Lists
+ .newArrayListWithCapacity(numTokens);
+
+ for (int i = 0; i < numKeys; ++i)
+ keys.add(SecretManagerSection.DelegationKey.parseDelimitedFrom(in));
+
+ for (int i = 0; i < numTokens; ++i)
+ tokens.add(SecretManagerSection.PersistToken.parseDelimitedFrom(in));
+
+ fsn.loadSecretManagerState(s, keys, tokens);
+ }
+
+ private void loadCacheManagerSection(InputStream in) throws IOException {
+ CacheManagerSection s = CacheManagerSection.parseDelimitedFrom(in);
+ ArrayList pools = Lists.newArrayListWithCapacity(s
+ .getNumPools());
+ ArrayList directives = Lists
+ .newArrayListWithCapacity(s.getNumDirectives());
+ for (int i = 0; i < s.getNumPools(); ++i)
+ pools.add(CachePoolInfoProto.parseDelimitedFrom(in));
+ for (int i = 0; i < s.getNumDirectives(); ++i)
+ directives.add(CacheDirectiveInfoProto.parseDelimitedFrom(in));
+ fsn.getCacheManager().loadState(
+ new CacheManager.PersistState(s, pools, directives));
+ }
+
+ }
+
+ public static final class Saver {
+ private final SaveNamespaceContext context;
+ private long currentOffset = FSImageUtil.MAGIC_HEADER.length;
+ private MD5Hash savedDigest;
+ private StringMap stringMap = new StringMap();
+
+ private FileChannel fileChannel;
+ // OutputStream for the section data
+ private OutputStream sectionOutputStream;
+ private CompressionCodec codec;
+ private OutputStream underlyingOutputStream;
+ public static final int CHECK_CANCEL_INTERVAL = 4096;
+
+ Saver(SaveNamespaceContext context) {
+ this.context = context;
+ }
+
+ public MD5Hash getSavedDigest() {
+ return savedDigest;
+ }
+
+ public SaveNamespaceContext getContext() {
+ return context;
+ }
+
+ public void commitSection(FileSummary.Builder summary, SectionName name)
+ throws IOException {
+ long oldOffset = currentOffset;
+ flushSectionOutputStream();
+
+ if (codec != null) {
+ sectionOutputStream = codec.createOutputStream(underlyingOutputStream);
+ } else {
+ sectionOutputStream = underlyingOutputStream;
+ }
+ long length = fileChannel.position() - oldOffset;
+ summary.addSections(FileSummary.Section.newBuilder().setName(name.name)
+ .setLength(length).setOffset(currentOffset));
+ currentOffset += length;
+ }
+
+ private void flushSectionOutputStream() throws IOException {
+ if (codec != null) {
+ ((CompressorStream) sectionOutputStream).finish();
+ }
+ sectionOutputStream.flush();
+ }
+
+ void save(File file, FSImageCompression compression) throws IOException {
+ FileOutputStream fout = new FileOutputStream(file);
+ fileChannel = fout.getChannel();
+ try {
+ saveInternal(fout, compression, file.getAbsolutePath().toString());
+ } finally {
+ fout.close();
+ }
+ }
+
+ private static void saveFileSummary(OutputStream out, FileSummary summary)
+ throws IOException {
+ summary.writeDelimitedTo(out);
+ int length = getOndiskTrunkSize(summary);
+ byte[] lengthBytes = new byte[4];
+ ByteBuffer.wrap(lengthBytes).asIntBuffer().put(length);
+ out.write(lengthBytes);
+ }
+
+ private void saveInodes(FileSummary.Builder summary) throws IOException {
+ FSImageFormatPBINode.Saver saver = new FSImageFormatPBINode.Saver(this,
+ summary);
+
+ saver.serializeINodeSection(sectionOutputStream);
+ saver.serializeINodeDirectorySection(sectionOutputStream);
+ saver.serializeFilesUCSection(sectionOutputStream);
+ }
+
+ private void saveSnapshots(FileSummary.Builder summary) throws IOException {
+ FSImageFormatPBSnapshot.Saver snapshotSaver = new FSImageFormatPBSnapshot.Saver(
+ this, summary, context, context.getSourceNamesystem());
+
+ snapshotSaver.serializeSnapshotSection(sectionOutputStream);
+ snapshotSaver.serializeSnapshotDiffSection(sectionOutputStream);
+ }
+
+ private void saveInternal(FileOutputStream fout,
+ FSImageCompression compression, String filePath) throws IOException {
+ StartupProgress prog = NameNode.getStartupProgress();
+ MessageDigest digester = MD5Hash.getDigester();
+
+ underlyingOutputStream = new DigestOutputStream(new BufferedOutputStream(
+ fout), digester);
+ underlyingOutputStream.write(FSImageUtil.MAGIC_HEADER);
+
+ fileChannel = fout.getChannel();
+
+ FileSummary.Builder b = FileSummary.newBuilder()
+ .setOndiskVersion(FSImageUtil.FILE_VERSION)
+ .setLayoutVersion(LayoutVersion.getCurrentLayoutVersion());
+
+ codec = compression.getImageCodec();
+ if (codec != null) {
+ b.setCodec(codec.getClass().getCanonicalName());
+ sectionOutputStream = codec.createOutputStream(underlyingOutputStream);
+ } else {
+ sectionOutputStream = underlyingOutputStream;
+ }
+
+ saveNameSystemSection(b);
+ // Check for cancellation right after serializing the name system section.
+ // Some unit tests, such as TestSaveNamespace#testCancelSaveNameSpace
+ // depends on this behavior.
+ context.checkCancelled();
+
+ Step step = new Step(StepType.INODES, filePath);
+ prog.beginStep(Phase.SAVING_CHECKPOINT, step);
+ saveInodes(b);
+ saveSnapshots(b);
+ prog.endStep(Phase.SAVING_CHECKPOINT, step);
+
+ step = new Step(StepType.DELEGATION_TOKENS, filePath);
+ prog.beginStep(Phase.SAVING_CHECKPOINT, step);
+ saveSecretManagerSection(b);
+ prog.endStep(Phase.SAVING_CHECKPOINT, step);
+
+ step = new Step(StepType.CACHE_POOLS, filePath);
+ prog.beginStep(Phase.SAVING_CHECKPOINT, step);
+ saveCacheManagerSection(b);
+ prog.endStep(Phase.SAVING_CHECKPOINT, step);
+
+ saveStringTableSection(b);
+
+ // We use the underlyingOutputStream to write the header. Therefore flush
+ // the buffered stream (which is potentially compressed) first.
+ flushSectionOutputStream();
+
+ FileSummary summary = b.build();
+ saveFileSummary(underlyingOutputStream, summary);
+ underlyingOutputStream.close();
+ savedDigest = new MD5Hash(digester.digest());
+ }
+
+ private void saveSecretManagerSection(FileSummary.Builder summary)
+ throws IOException {
+ final FSNamesystem fsn = context.getSourceNamesystem();
+ DelegationTokenSecretManager.SecretManagerState state = fsn
+ .saveSecretManagerState();
+ state.section.writeDelimitedTo(sectionOutputStream);
+ for (SecretManagerSection.DelegationKey k : state.keys)
+ k.writeDelimitedTo(sectionOutputStream);
+
+ for (SecretManagerSection.PersistToken t : state.tokens)
+ t.writeDelimitedTo(sectionOutputStream);
+
+ commitSection(summary, SectionName.SECRET_MANAGER);
+ }
+
+ private void saveCacheManagerSection(FileSummary.Builder summary)
+ throws IOException {
+ final FSNamesystem fsn = context.getSourceNamesystem();
+ CacheManager.PersistState state = fsn.getCacheManager().saveState();
+ state.section.writeDelimitedTo(sectionOutputStream);
+
+ for (CachePoolInfoProto p : state.pools)
+ p.writeDelimitedTo(sectionOutputStream);
+
+ for (CacheDirectiveInfoProto p : state.directives)
+ p.writeDelimitedTo(sectionOutputStream);
+
+ commitSection(summary, SectionName.CACHE_MANAGER);
+ }
+
+ private void saveNameSystemSection(FileSummary.Builder summary)
+ throws IOException {
+ final FSNamesystem fsn = context.getSourceNamesystem();
+ OutputStream out = sectionOutputStream;
+ NameSystemSection.Builder b = NameSystemSection.newBuilder()
+ .setGenstampV1(fsn.getGenerationStampV1())
+ .setGenstampV1Limit(fsn.getGenerationStampV1Limit())
+ .setGenstampV2(fsn.getGenerationStampV2())
+ .setLastAllocatedBlockId(fsn.getLastAllocatedBlockId())
+ .setTransactionId(context.getTxId());
+
+ // We use the non-locked version of getNamespaceInfo here since
+ // the coordinating thread of saveNamespace already has read-locked
+ // the namespace for us. If we attempt to take another readlock
+ // from the actual saver thread, there's a potential of a
+ // fairness-related deadlock. See the comments on HDFS-2223.
+ b.setNamespaceId(fsn.unprotectedGetNamespaceInfo().getNamespaceID());
+ NameSystemSection s = b.build();
+ s.writeDelimitedTo(out);
+
+ commitSection(summary, SectionName.NS_INFO);
+ }
+
+ private void saveStringTableSection(FileSummary.Builder summary)
+ throws IOException {
+ OutputStream out = sectionOutputStream;
+ StringTableSection.Builder b = StringTableSection.newBuilder()
+ .setNumEntry(stringMap.size());
+ b.build().writeDelimitedTo(out);
+ for (Entry e : stringMap.entrySet()) {
+ StringTableSection.Entry.Builder eb = StringTableSection.Entry
+ .newBuilder().setId(e.getValue()).setStr(e.getKey());
+ eb.build().writeDelimitedTo(out);
+ }
+ commitSection(summary, SectionName.STRING_TABLE);
+ }
+
+ public StringMap getStringMap() {
+ return stringMap;
+ }
+ }
+
+ public static class StringMap {
+ private final Map stringMap;
+
+ public StringMap() {
+ stringMap = Maps.newHashMap();
+ }
+
+ int getStringId(String str) {
+ if (str == null) {
+ return 0;
+ }
+ Integer v = stringMap.get(str);
+ if (v == null) {
+ int nv = stringMap.size() + 1;
+ stringMap.put(str, nv);
+ return nv;
+ }
+ return v;
+ }
+
+ int size() {
+ return stringMap.size();
+ }
+
+ Set> entrySet() {
+ return stringMap.entrySet();
+ }
+ }
+
+ /**
+ * Supported section name. The order of the enum determines the order of
+ * loading.
+ */
+ public enum SectionName {
+ NS_INFO("NS_INFO"),
+ STRING_TABLE("STRING_TABLE"),
+ INODE("INODE"),
+ SNAPSHOT("SNAPSHOT"),
+ INODE_DIR("INODE_DIR"),
+ FILES_UNDERCONSTRUCTION("FILES_UNDERCONSTRUCTION"),
+ SNAPSHOT_DIFF("SNAPSHOT_DIFF"),
+ SECRET_MANAGER("SECRET_MANAGER"),
+ CACHE_MANAGER("CACHE_MANAGER");
+
+ private static final SectionName[] values = SectionName.values();
+
+ public static SectionName fromString(String name) {
+ for (SectionName n : values) {
+ if (n.name.equals(name))
+ return n;
+ }
+ return null;
+ }
+
+ private final String name;
+
+ private SectionName(String name) {
+ this.name = name;
+ }
+ }
+
+ private static int getOndiskTrunkSize(com.google.protobuf.GeneratedMessage s) {
+ return CodedOutputStream.computeRawVarint32Size(s.getSerializedSize())
+ + s.getSerializedSize();
+ }
+
+ private FSImageFormatProtobuf() {
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageUtil.java
new file mode 100644
index 00000000000..b9953480f26
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageUtil.java
@@ -0,0 +1,93 @@
+/**
+ * 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.server.namenode;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.protocol.LayoutVersion;
+import org.apache.hadoop.hdfs.protocol.LayoutVersion.Feature;
+import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.Loader;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
+import org.apache.hadoop.io.compress.CompressionCodec;
+
+@InterfaceAudience.Private
+public final class FSImageUtil {
+ public static final byte[] MAGIC_HEADER = "HDFSIMG1".getBytes();
+ public static final int FILE_VERSION = 1;
+
+ public static boolean checkFileFormat(RandomAccessFile file)
+ throws IOException {
+ if (file.length() < Loader.MINIMUM_FILE_LENGTH)
+ return false;
+
+ byte[] magic = new byte[MAGIC_HEADER.length];
+ file.readFully(magic);
+ if (!Arrays.equals(MAGIC_HEADER, magic))
+ return false;
+
+ return true;
+ }
+
+ public static FileSummary loadSummary(RandomAccessFile file)
+ throws IOException {
+ final int FILE_LENGTH_FIELD_SIZE = 4;
+ long fileLength = file.length();
+ file.seek(fileLength - FILE_LENGTH_FIELD_SIZE);
+ int summaryLength = file.readInt();
+
+ if (summaryLength <= 0) {
+ throw new IOException("Negative length of the file");
+ }
+ file.seek(fileLength - FILE_LENGTH_FIELD_SIZE - summaryLength);
+
+ byte[] summaryBytes = new byte[summaryLength];
+ file.readFully(summaryBytes);
+
+ FileSummary summary = FileSummary
+ .parseDelimitedFrom(new ByteArrayInputStream(summaryBytes));
+ if (summary.getOndiskVersion() != FILE_VERSION) {
+ throw new IOException("Unsupported file version "
+ + summary.getOndiskVersion());
+ }
+
+ if (!LayoutVersion.supports(Feature.PROTOBUF_FORMAT,
+ summary.getLayoutVersion())) {
+ throw new IOException("Unsupported layout version "
+ + summary.getLayoutVersion());
+ }
+ return summary;
+ }
+
+ public static InputStream wrapInputStreamForCompression(
+ Configuration conf, String codec, InputStream in) throws IOException {
+ if (codec.isEmpty())
+ return in;
+
+ FSImageCompression compression = FSImageCompression.createCompression(
+ conf, codec);
+ CompressionCodec imageCodec = compression.getImageCodec();
+ return imageCodec.createInputStream(in);
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java
index 4e209767dfc..f91c41c7610 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java
@@ -179,6 +179,7 @@ import org.apache.hadoop.hdfs.security.token.block.BlockTokenSecretManager;
import org.apache.hadoop.hdfs.security.token.block.BlockTokenSecretManager.AccessMode;
import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSecretManager;
+import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSecretManager.SecretManagerState;
import org.apache.hadoop.hdfs.server.blockmanagement.BlockCollection;
import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo;
import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoUnderConstruction;
@@ -196,6 +197,8 @@ import org.apache.hadoop.hdfs.server.common.Storage;
import org.apache.hadoop.hdfs.server.common.Storage.StorageDirType;
import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory;
import org.apache.hadoop.hdfs.server.common.Util;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection.PersistToken;
import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo;
import org.apache.hadoop.hdfs.server.namenode.JournalSet.JournalAndStream;
import org.apache.hadoop.hdfs.server.namenode.LeaseManager.Lease;
@@ -6012,6 +6015,15 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
}
}
+ /**
+ * @return all the under-construction files in the lease map
+ */
+ Map getFilesUnderConstruction() {
+ synchronized (leaseManager) {
+ return leaseManager.getINodesUnderConstruction();
+ }
+ }
+
/**
* Register a Backup name-node, verifying that it belongs
* to the correct namespace, and adding it to the set of
@@ -6288,6 +6300,10 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
dtSecretManager.saveSecretManagerStateCompat(out, sdPath);
}
+ SecretManagerState saveSecretManagerState() {
+ return dtSecretManager.saveSecretManagerState();
+ }
+
/**
* @param in load the state of secret manager from input stream
*/
@@ -6295,6 +6311,12 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
dtSecretManager.loadSecretManagerStateCompat(in);
}
+ void loadSecretManagerState(SecretManagerSection s,
+ List keys,
+ List tokens) throws IOException {
+ dtSecretManager.loadSecretManagerState(new SecretManagerState(s, keys, tokens));
+ }
+
/**
* Log the updateMasterKey operation to edit logs
*
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java
index 83cb0a4eb94..f9a06f1e5bd 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java
@@ -171,7 +171,7 @@ public class INodeDirectory extends INodeWithAdditionalFields
return children == null? -1: Collections.binarySearch(children, name);
}
- protected DirectoryWithSnapshotFeature addSnapshotFeature(
+ public DirectoryWithSnapshotFeature addSnapshotFeature(
DirectoryDiffList diffs) {
Preconditions.checkState(!isWithSnapshot(),
"Directory is already with snapshot");
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeFile.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeFile.java
index 500405e09d4..80abb5268dc 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeFile.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeFile.java
@@ -252,7 +252,7 @@ public class INodeFile extends INodeWithAdditionalFields
/* Start of Snapshot Feature */
- private FileWithSnapshotFeature addSnapshotFeature(FileDiffList diffs) {
+ public FileWithSnapshotFeature addSnapshotFeature(FileDiffList diffs) {
Preconditions.checkState(!isWithSnapshot(),
"File is already with snapshot");
FileWithSnapshotFeature sf = new FileWithSnapshotFeature(diffs);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeMap.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeMap.java
index 5ffcc21f5bb..bd0355b6618 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeMap.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeMap.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.hdfs.server.namenode;
+import java.util.Iterator;
import java.util.List;
import org.apache.hadoop.fs.permission.FsPermission;
@@ -46,6 +47,10 @@ public class INodeMap {
/** Synchronized by external lock. */
private final GSet map;
+ public Iterator getMapIterator() {
+ return map.iterator();
+ }
+
private INodeMap(GSet map) {
Preconditions.checkArgument(map != null);
this.map = map;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SaveNamespaceContext.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SaveNamespaceContext.java
index 67ee88e11de..a7c4c75f005 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SaveNamespaceContext.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SaveNamespaceContext.java
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory;
import org.apache.hadoop.hdfs.util.Canceler;
@@ -32,7 +33,8 @@ import com.google.common.base.Preconditions;
* allows cancellation, and also is responsible for accumulating
* failed storage directories.
*/
-class SaveNamespaceContext {
+@InterfaceAudience.Private
+public class SaveNamespaceContext {
private final FSNamesystem sourceNamesystem;
private final long txid;
private final List errorSDs =
@@ -72,7 +74,7 @@ class SaveNamespaceContext {
completionLatch.countDown();
}
- void checkCancelled() throws SaveNamespaceCancelledException {
+ public void checkCancelled() throws SaveNamespaceCancelledException {
if (canceller.isCancelled()) {
throw new SaveNamespaceCancelledException(
canceller.getCancellationReason());
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/DirectoryWithSnapshotFeature.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/DirectoryWithSnapshotFeature.java
index 06f7a89e33a..a9cad94f0b2 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/DirectoryWithSnapshotFeature.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/DirectoryWithSnapshotFeature.java
@@ -244,7 +244,7 @@ public class DirectoryWithSnapshotFeature implements INode.Feature {
this.isSnapshotRoot = isSnapshotRoot;
}
- ChildrenDiff getChildrenDiff() {
+ public ChildrenDiff getChildrenDiff() {
return diff;
}
@@ -343,6 +343,10 @@ public class DirectoryWithSnapshotFeature implements INode.Feature {
return super.toString() + " childrenSize=" + childrenSize + ", " + diff;
}
+ int getChildrenSize() {
+ return childrenSize;
+ }
+
@Override
void write(DataOutput out, ReferenceMap referenceMap) throws IOException {
writeSnapshot(out);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/FSImageFormatPBSnapshot.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/FSImageFormatPBSnapshot.java
new file mode 100644
index 00000000000..06cc1d0ac1f
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/FSImageFormatPBSnapshot.java
@@ -0,0 +1,437 @@
+/**
+ * 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.server.namenode.snapshot;
+
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadINodeDirectory;
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadINodeReference;
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadPermission;
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.updateBlocksMap;
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeDirectory;
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeFile;
+import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeReference;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.fs.permission.PermissionStatus;
+import org.apache.hadoop.hdfs.server.namenode.FSDirectory;
+import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf;
+import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.CreatedListEntry;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.DiffEntry.Type;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotSection;
+import org.apache.hadoop.hdfs.server.namenode.INode;
+import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
+import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes;
+import org.apache.hadoop.hdfs.server.namenode.INodeFile;
+import org.apache.hadoop.hdfs.server.namenode.INodeFileAttributes;
+import org.apache.hadoop.hdfs.server.namenode.INodeMap;
+import org.apache.hadoop.hdfs.server.namenode.INodeReference;
+import org.apache.hadoop.hdfs.server.namenode.INodeWithAdditionalFields;
+import org.apache.hadoop.hdfs.server.namenode.SaveNamespaceContext;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiff;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot.Root;
+import org.apache.hadoop.hdfs.util.Diff.ListType;
+
+import com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
+
+@InterfaceAudience.Private
+public class FSImageFormatPBSnapshot {
+ /**
+ * Loading snapshot related information from protobuf based FSImage
+ */
+ public final static class Loader {
+ private final FSNamesystem fsn;
+ private final FSDirectory fsDir;
+ private final FSImageFormatProtobuf.Loader parent;
+ private final Map snapshotMap;
+
+
+ public Loader(FSNamesystem fsn, FSImageFormatProtobuf.Loader parent) {
+ this.fsn = fsn;
+ this.fsDir = fsn.getFSDirectory();
+ this.snapshotMap = new HashMap();
+ this.parent = parent;
+ }
+
+ /**
+ * Load the snapshots section from fsimage. Also convert snapshottable
+ * directories into {@link INodeDirectorySnapshottable}.
+ *
+ */
+ public void loadSnapshotSection(InputStream in) throws IOException {
+ SnapshotManager sm = fsn.getSnapshotManager();
+ SnapshotSection section = SnapshotSection.parseDelimitedFrom(in);
+ int snum = section.getNumSnapshots();
+ sm.setNumSnapshots(snum);
+ sm.setSnapshotCounter(section.getSnapshotCounter());
+ for (long sdirId : section.getSnapshottableDirList()) {
+ INodeDirectory dir = fsDir.getInode(sdirId).asDirectory();
+ final INodeDirectorySnapshottable sdir;
+ if (!dir.isSnapshottable()) {
+ sdir = new INodeDirectorySnapshottable(dir);
+ fsDir.addToInodeMap(sdir);
+ } else {
+ // dir is root, and admin set root to snapshottable before
+ sdir = (INodeDirectorySnapshottable) dir;
+ sdir.setSnapshotQuota(INodeDirectorySnapshottable.SNAPSHOT_LIMIT);
+ }
+ sm.addSnapshottable(sdir);
+ }
+ loadSnapshots(in, snum);
+ }
+
+ private void loadSnapshots(InputStream in, int size) throws IOException {
+ for (int i = 0; i < size; i++) {
+ SnapshotSection.Snapshot pbs = SnapshotSection.Snapshot
+ .parseDelimitedFrom(in);
+ INodeDirectory root = loadINodeDirectory(pbs.getRoot(),
+ parent.getStringTable());
+ int sid = pbs.getSnapshotId();
+ INodeDirectorySnapshottable parent = (INodeDirectorySnapshottable) fsDir
+ .getInode(root.getId()).asDirectory();
+ Snapshot snapshot = new Snapshot(sid, root, parent);
+ // add the snapshot to parent, since we follow the sequence of
+ // snapshotsByNames when saving, we do not need to sort when loading
+ parent.addSnapshot(snapshot);
+ snapshotMap.put(sid, snapshot);
+ }
+ }
+
+ /**
+ * Load the snapshot diff section from fsimage.
+ */
+ public void loadSnapshotDiffSection(InputStream in) throws IOException {
+ while (true) {
+ SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
+ .parseDelimitedFrom(in);
+ if (entry == null) {
+ break;
+ }
+ long inodeId = entry.getInodeId();
+ INode inode = fsDir.getInode(inodeId);
+ SnapshotDiffSection.DiffEntry.Type type = entry.getType();
+ switch (type) {
+ case FILEDIFF:
+ loadFileDiffList(in, inode.asFile(), entry.getNumOfDiff());
+ break;
+ case DIRECTORYDIFF:
+ loadDirectoryDiffList(in, inode.asDirectory(), entry.getNumOfDiff());
+ break;
+ }
+ }
+ }
+
+ /** Load FileDiff list for a file with snapshot feature */
+ private void loadFileDiffList(InputStream in, INodeFile file, int size)
+ throws IOException {
+ final FileDiffList diffs = new FileDiffList();
+ for (int i = 0; i < size; i++) {
+ SnapshotDiffSection.FileDiff pbf = SnapshotDiffSection.FileDiff
+ .parseDelimitedFrom(in);
+ INodeFileAttributes copy = null;
+ if (pbf.hasSnapshotCopy()) {
+ INodeSection.INodeFile fileInPb = pbf.getSnapshotCopy();
+ PermissionStatus permission = loadPermission(
+ fileInPb.getPermission(), parent.getStringTable());
+ copy = new INodeFileAttributes.SnapshotCopy(pbf.getName()
+ .toByteArray(), permission, fileInPb.getModificationTime(),
+ fileInPb.getAccessTime(), (short) fileInPb.getReplication(),
+ fileInPb.getPreferredBlockSize());
+ }
+
+ FileDiff diff = new FileDiff(pbf.getSnapshotId(), copy, null,
+ pbf.getFileSize());
+ diffs.addFirst(diff);
+ }
+ file.addSnapshotFeature(diffs);
+ }
+
+ /** Load the created list in a DirectoryDiff */
+ private List loadCreatedList(InputStream in, INodeDirectory dir,
+ int size) throws IOException {
+ List clist = new ArrayList(size);
+ for (long c = 0; c < size; c++) {
+ CreatedListEntry entry = CreatedListEntry.parseDelimitedFrom(in);
+ INode created = SnapshotFSImageFormat.loadCreated(entry.getName()
+ .toByteArray(), dir);
+ clist.add(created);
+ }
+ return clist;
+ }
+
+ private void addToDeletedList(INode dnode, INodeDirectory parent) {
+ dnode.setParent(parent);
+ if (dnode.isFile()) {
+ updateBlocksMap(dnode.asFile(), fsn.getBlockManager());
+ }
+ }
+
+ /**
+ * Load the deleted list in a DirectoryDiff
+ * @param totalSize the total size of the deleted list
+ * @param deletedNodes non-reference inodes in the deleted list. These
+ * inodes' ids are directly recorded in protobuf
+ */
+ private List loadDeletedList(InputStream in, INodeDirectory dir,
+ int refNum, List deletedNodes) throws IOException {
+ List dlist = new ArrayList(refNum + deletedNodes.size());
+ // load non-reference inodes
+ for (long deletedId : deletedNodes) {
+ INode deleted = fsDir.getInode(deletedId);
+ dlist.add(deleted);
+ addToDeletedList(deleted, dir);
+ }
+ // load reference nodes in the deleted list
+ for (int r = 0; r < refNum; r++) {
+ INodeSection.INodeReference ref = INodeSection.INodeReference
+ .parseDelimitedFrom(in);
+ INodeReference refNode = loadINodeReference(ref, fsDir);
+ dlist.add(refNode);
+ addToDeletedList(refNode, dir);
+ }
+ Collections.sort(dlist, new Comparator() {
+ @Override
+ public int compare(INode n1, INode n2) {
+ return n1.compareTo(n2.getLocalNameBytes());
+ }
+ });
+ return dlist;
+ }
+
+ /** Load DirectoryDiff list for a directory with snapshot feature */
+ private void loadDirectoryDiffList(InputStream in, INodeDirectory dir,
+ int size) throws IOException {
+ if (!dir.isWithSnapshot()) {
+ dir.addSnapshotFeature(null);
+ }
+ DirectoryDiffList diffs = dir.getDiffs();
+ for (int i = 0; i < size; i++) {
+ // load a directory diff
+ SnapshotDiffSection.DirectoryDiff diffInPb = SnapshotDiffSection.
+ DirectoryDiff.parseDelimitedFrom(in);
+ final int snapshotId = diffInPb.getSnapshotId();
+ final Snapshot snapshot = snapshotMap.get(snapshotId);
+ int childrenSize = diffInPb.getChildrenSize();
+ boolean useRoot = diffInPb.getIsSnapshotRoot();
+ INodeDirectoryAttributes copy = null;
+ if (useRoot) {
+ copy = snapshot.getRoot();
+ }else if (diffInPb.hasSnapshotCopy()) {
+ INodeSection.INodeDirectory dirCopyInPb = diffInPb.getSnapshotCopy();
+ final byte[] name = diffInPb.getName().toByteArray();
+ PermissionStatus permission = loadPermission(dirCopyInPb
+ .getPermission(), parent.getStringTable());
+ long modTime = dirCopyInPb.getModificationTime();
+ boolean noQuota = dirCopyInPb.getNsQuota() == -1
+ && dirCopyInPb.getDsQuota() == -1;
+ copy = noQuota ? new INodeDirectoryAttributes.SnapshotCopy(name,
+ permission, modTime)
+ : new INodeDirectoryAttributes.CopyWithQuota(name, permission,
+ modTime, dirCopyInPb.getNsQuota(), dirCopyInPb.getDsQuota());
+ }
+ // load created list
+ List clist = loadCreatedList(in, dir,
+ diffInPb.getCreatedListSize());
+ // load deleted list
+ List dlist = loadDeletedList(in, dir,
+ diffInPb.getNumOfDeletedRef(), diffInPb.getDeletedINodeList());
+ // create the directory diff
+ DirectoryDiff diff = new DirectoryDiff(snapshotId, copy, null,
+ childrenSize, clist, dlist, useRoot);
+ diffs.addFirst(diff);
+ }
+ }
+ }
+
+ /**
+ * Saving snapshot related information to protobuf based FSImage
+ */
+ public final static class Saver {
+ private final FSNamesystem fsn;
+ private final FileSummary.Builder headers;
+ private final FSImageFormatProtobuf.Saver parent;
+ private final SaveNamespaceContext context;
+
+ public Saver(FSImageFormatProtobuf.Saver parent,
+ FileSummary.Builder headers, SaveNamespaceContext context, FSNamesystem fsn) {
+ this.parent = parent;
+ this.headers = headers;
+ this.context = context;
+ this.fsn = fsn;
+ }
+
+ /**
+ * save all the snapshottable directories and snapshots to fsimage
+ */
+ public void serializeSnapshotSection(OutputStream out) throws IOException {
+ SnapshotManager sm = fsn.getSnapshotManager();
+ SnapshotSection.Builder b = SnapshotSection.newBuilder()
+ .setSnapshotCounter(sm.getSnapshotCounter())
+ .setNumSnapshots(sm.getNumSnapshots());
+
+ INodeDirectorySnapshottable[] snapshottables = sm.getSnapshottableDirs();
+ for (INodeDirectorySnapshottable sdir : snapshottables) {
+ b.addSnapshottableDir(sdir.getId());
+ }
+ b.build().writeDelimitedTo(out);
+ int i = 0;
+ for(INodeDirectorySnapshottable sdir : snapshottables) {
+ for(Snapshot s : sdir.getSnapshotsByNames()) {
+ Root sroot = s.getRoot();
+ SnapshotSection.Snapshot.Builder sb = SnapshotSection.Snapshot
+ .newBuilder().setSnapshotId(s.getId());
+ INodeSection.INodeDirectory.Builder db = buildINodeDirectory(sroot,
+ parent.getStringMap());
+ INodeSection.INode r = INodeSection.INode.newBuilder()
+ .setId(sroot.getId())
+ .setType(INodeSection.INode.Type.DIRECTORY)
+ .setName(ByteString.copyFrom(sroot.getLocalNameBytes()))
+ .setDirectory(db).build();
+ sb.setRoot(r).build().writeDelimitedTo(out);
+ i++;
+ if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
+ context.checkCancelled();
+ }
+ }
+ }
+ Preconditions.checkState(i == sm.getNumSnapshots());
+ parent.commitSection(headers, FSImageFormatProtobuf.SectionName.SNAPSHOT);
+ }
+
+ /**
+ * save all the snapshot diff to fsimage
+ */
+ public void serializeSnapshotDiffSection(OutputStream out)
+ throws IOException {
+ INodeMap inodesMap = fsn.getFSDirectory().getINodeMap();
+ int i = 0;
+ Iterator iter = inodesMap.getMapIterator();
+ while (iter.hasNext()) {
+ INodeWithAdditionalFields inode = iter.next();
+ if (inode.isFile()) {
+ serializeFileDiffList(inode.asFile(), out);
+ } else if (inode.isDirectory()) {
+ serializeDirDiffList(inode.asDirectory(), out);
+ }
+ ++i;
+ if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
+ context.checkCancelled();
+ }
+ }
+ parent.commitSection(headers,
+ FSImageFormatProtobuf.SectionName.SNAPSHOT_DIFF);
+ }
+
+ private void serializeFileDiffList(INodeFile file, OutputStream out)
+ throws IOException {
+ FileWithSnapshotFeature sf = file.getFileWithSnapshotFeature();
+ if (sf != null) {
+ List diffList = sf.getDiffs().asList();
+ SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
+ .newBuilder().setInodeId(file.getId()).setType(Type.FILEDIFF)
+ .setNumOfDiff(diffList.size()).build();
+ entry.writeDelimitedTo(out);
+ for (int i = diffList.size() - 1; i >= 0; i--) {
+ FileDiff diff = diffList.get(i);
+ SnapshotDiffSection.FileDiff.Builder fb = SnapshotDiffSection.FileDiff
+ .newBuilder().setSnapshotId(diff.getSnapshotId())
+ .setFileSize(diff.getFileSize());
+ INodeFileAttributes copy = diff.snapshotINode;
+ if (copy != null) {
+ fb.setName(ByteString.copyFrom(copy.getLocalNameBytes()))
+ .setSnapshotCopy(buildINodeFile(copy, parent.getStringMap()));
+ }
+ fb.build().writeDelimitedTo(out);
+ }
+ }
+ }
+
+ private void saveCreatedDeletedList(List created,
+ List deletedRefs, OutputStream out) throws IOException {
+ // local names of the created list member
+ for (INode c : created) {
+ SnapshotDiffSection.CreatedListEntry.newBuilder()
+ .setName(ByteString.copyFrom(c.getLocalNameBytes())).build()
+ .writeDelimitedTo(out);
+ }
+ // reference nodes in deleted list
+ for (INodeReference ref : deletedRefs) {
+ INodeSection.INodeReference.Builder rb = buildINodeReference(ref);
+ rb.build().writeDelimitedTo(out);
+ }
+ }
+
+ private void serializeDirDiffList(INodeDirectory dir, OutputStream out)
+ throws IOException {
+ DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature();
+ if (sf != null) {
+ List diffList = sf.getDiffs().asList();
+ SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
+ .newBuilder().setInodeId(dir.getId()).setType(Type.DIRECTORYDIFF)
+ .setNumOfDiff(diffList.size()).build();
+ entry.writeDelimitedTo(out);
+ for (int i = diffList.size() - 1; i >= 0; i--) { // reverse order!
+ DirectoryDiff diff = diffList.get(i);
+ SnapshotDiffSection.DirectoryDiff.Builder db = SnapshotDiffSection.
+ DirectoryDiff.newBuilder().setSnapshotId(diff.getSnapshotId())
+ .setChildrenSize(diff.getChildrenSize())
+ .setIsSnapshotRoot(diff.isSnapshotRoot());
+ INodeDirectoryAttributes copy = diff.snapshotINode;
+ if (!diff.isSnapshotRoot() && copy != null) {
+ db.setName(ByteString.copyFrom(copy.getLocalNameBytes()))
+ .setSnapshotCopy(
+ buildINodeDirectory(copy, parent.getStringMap()));
+ }
+ // process created list and deleted list
+ List created = diff.getChildrenDiff()
+ .getList(ListType.CREATED);
+ db.setCreatedListSize(created.size());
+ List deleted = diff.getChildrenDiff().getList(ListType.DELETED);
+ List refs = new ArrayList();
+ for (INode d : deleted) {
+ if (d.isReference()) {
+ refs.add(d.asReference());
+ } else {
+ db.addDeletedINode(d.getId());
+ }
+ }
+ db.setNumOfDeletedRef(refs.size());
+ db.build().writeDelimitedTo(out);
+ saveCreatedDeletedList(created, refs, out);
+ }
+ }
+ }
+ }
+
+ private FSImageFormatPBSnapshot(){}
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotFSImageFormat.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotFSImageFormat.java
index e836cd87959..69fdf97391c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotFSImageFormat.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotFSImageFormat.java
@@ -27,7 +27,6 @@ import java.util.Map;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormat;
-import org.apache.hadoop.hdfs.server.namenode.FSImageFormat.Loader;
import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
import org.apache.hadoop.hdfs.server.namenode.INode;
import org.apache.hadoop.hdfs.server.namenode.INodeAttributes;
@@ -137,7 +136,7 @@ public class SnapshotFSImageFormat {
* @param parent The directory that the created list belongs to.
* @return The created node.
*/
- private static INode loadCreated(byte[] createdNodeName,
+ public static INode loadCreated(byte[] createdNodeName,
INodeDirectory parent) throws IOException {
// the INode in the created list should be a reference to another INode
// in posterior SnapshotDiffs or one of the current children
@@ -209,11 +208,13 @@ public class SnapshotFSImageFormat {
/**
* Load snapshots and snapshotQuota for a Snapshottable directory.
- * @param snapshottableParent The snapshottable directory for loading.
- * @param numSnapshots The number of snapshots that the directory has.
- * @param in The {@link DataInput} instance to read.
- * @param loader The {@link Loader} instance that this loading procedure is
- * using.
+ *
+ * @param snapshottableParent
+ * The snapshottable directory for loading.
+ * @param numSnapshots
+ * The number of snapshots that the directory has.
+ * @param loader
+ * The loader
*/
public static void loadSnapshotList(
INodeDirectorySnapshottable snapshottableParent, int numSnapshots,
@@ -231,10 +232,13 @@ public class SnapshotFSImageFormat {
/**
* Load the {@link SnapshotDiff} list for the INodeDirectoryWithSnapshot
* directory.
- * @param dir The snapshottable directory for loading.
- * @param in The {@link DataInput} instance to read.
- * @param loader The {@link Loader} instance that this loading procedure is
- * using.
+ *
+ * @param dir
+ * The snapshottable directory for loading.
+ * @param in
+ * The {@link DataInput} instance to read.
+ * @param loader
+ * The loader
*/
public static void loadDirectoryDiffList(INodeDirectory dir,
DataInput in, FSImageFormat.Loader loader) throws IOException {
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java
index 8fa0f0c932b..be1ddc0e9e6 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java
@@ -270,6 +270,23 @@ public class SnapshotManager implements SnapshotStats {
return numSnapshots.get();
}
+ void setNumSnapshots(int num) {
+ numSnapshots.set(num);
+ }
+
+ int getSnapshotCounter() {
+ return snapshotCounter;
+ }
+
+ void setSnapshotCounter(int counter) {
+ snapshotCounter = counter;
+ }
+
+ INodeDirectorySnapshottable[] getSnapshottableDirs() {
+ return snapshottables.values().toArray(
+ new INodeDirectorySnapshottable[snapshottables.size()]);
+ }
+
/**
* Write {@link #snapshotCounter}, {@link #numSnapshots},
* and all snapshots to the DataOutput.
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/FileDistributionCalculator.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/FileDistributionCalculator.java
new file mode 100644
index 00000000000..2433b28a859
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/FileDistributionCalculator.java
@@ -0,0 +1,160 @@
+/**
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.BlockProto;
+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.FileSummary;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
+import org.apache.hadoop.io.IOUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.LimitInputStream;
+
+/**
+ * This is the tool for analyzing file sizes in the namespace image. In order to
+ * run the tool one should define a range of integers [0, maxSize] by
+ * specifying maxSize and a step. The range of integers is
+ * divided into segments of size step:
+ * [0, s1, ..., sn-1, maxSize], and the visitor
+ * calculates how many files in the system fall into each segment
+ * [si-1, si). Note that files larger than
+ * maxSize always fall into the very last segment.
+ *
+ * Input.
+ *
+ * - filename specifies the location of the image file;
+ * - maxSize determines the range [0, maxSize] of files
+ * sizes considered by the visitor;
+ * - step the range is divided into segments of size step.
+ *
+ *
+ * Output.
The output file is formatted as a tab separated two column
+ * table: Size and NumFiles. Where Size represents the start of the segment, and
+ * numFiles is the number of files form the image which size falls in this
+ * segment.
+ *
+ */
+final class FileDistributionCalculator {
+ private final static long MAX_SIZE_DEFAULT = 0x2000000000L; // 1/8 TB = 2^37
+ private final static int INTERVAL_DEFAULT = 0x200000; // 2 MB = 2^21
+
+ private final Configuration conf;
+ private final long maxSize;
+ private final int steps;
+ private final PrintWriter out;
+
+ private int[] distribution;
+ private int totalFiles;
+ private int totalDirectories;
+ private int totalBlocks;
+ private long totalSpace;
+ private long maxFileSize;
+
+ FileDistributionCalculator(Configuration conf, long maxSize, int steps,
+ PrintWriter out) {
+ this.conf = conf;
+ this.maxSize = maxSize == 0 ? MAX_SIZE_DEFAULT : maxSize;
+ this.steps = steps == 0 ? INTERVAL_DEFAULT : steps;
+ this.out = out;
+ long numIntervals = this.maxSize / this.steps;
+ this.distribution = new int[1 + (int) (numIntervals)];
+ Preconditions.checkState(numIntervals < Integer.MAX_VALUE,
+ "Too many distribution intervals");
+ }
+
+ void visit(RandomAccessFile file) throws IOException {
+ if (!FSImageUtil.checkFileFormat(file)) {
+ throw new IOException("Unrecognized FSImage");
+ }
+
+ FileSummary summary = FSImageUtil.loadSummary(file);
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(file.getFD());
+ for (FileSummary.Section s : summary.getSectionsList()) {
+ if (SectionName.fromString(s.getName()) != SectionName.INODE) {
+ continue;
+ }
+
+ in.getChannel().position(s.getOffset());
+ InputStream is = FSImageUtil.wrapInputStreamForCompression(conf,
+ summary.getCodec(), new BufferedInputStream(new LimitInputStream(
+ in, s.getLength())));
+ run(is);
+ output();
+ }
+ } finally {
+ IOUtils.cleanup(null, in);
+ }
+ }
+
+ private void run(InputStream in) throws IOException {
+ INodeSection s = INodeSection.parseDelimitedFrom(in);
+ for (int i = 0; i < s.getNumInodes(); ++i) {
+ INodeSection.INode p = INodeSection.INode.parseDelimitedFrom(in);
+ if (p.getType() == INodeSection.INode.Type.FILE) {
+ ++totalFiles;
+ INodeSection.INodeFile f = p.getFile();
+ totalBlocks += f.getBlocksCount();
+ long fileSize = 0;
+ for (BlockProto b : f.getBlocksList()) {
+ fileSize += b.getNumBytes() * f.getReplication();
+ }
+ maxFileSize = Math.max(fileSize, maxFileSize);
+ totalSpace += fileSize;
+
+ int bucket = fileSize > maxSize ? distribution.length - 1 : (int) Math
+ .ceil((double)fileSize / steps);
+ ++distribution[bucket];
+
+ } else if (p.getType() == INodeSection.INode.Type.DIRECTORY) {
+ ++totalDirectories;
+ }
+
+ if (i % (1 << 20) == 0) {
+ out.println("Processed " + i + " inodes.");
+ }
+ }
+ }
+
+ private void output() {
+ // write the distribution into the output file
+ out.print("Size\tNumFiles\n");
+ for (int i = 0; i < distribution.length; i++) {
+ if (distribution[i] != 0) {
+ out.print(((long) i * steps) + "\t" + distribution[i]);
+ out.print('\n');
+ }
+ }
+ out.print("totalFiles = " + totalFiles + "\n");
+ out.print("totalDirectories = " + totalDirectories + "\n");
+ out.print("totalBlocks = " + totalBlocks + "\n");
+ out.print("totalSpace = " + totalSpace + "\n");
+ out.print("maxFileSize = " + maxFileSize + "\n");
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/ImageLoaderCurrent.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/ImageLoaderCurrent.java
index c529fb5cdc2..19b859118ec 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/ImageLoaderCurrent.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/ImageLoaderCurrent.java
@@ -127,7 +127,7 @@ class ImageLoaderCurrent implements ImageLoader {
new SimpleDateFormat("yyyy-MM-dd HH:mm");
private static int[] versions = { -16, -17, -18, -19, -20, -21, -22, -23,
-24, -25, -26, -27, -28, -30, -31, -32, -33, -34, -35, -36, -37, -38, -39,
- -40, -41, -42, -43, -44, -45, -46, -47, -48, -49, -50, -51 };
+ -40, -41, -42, -43, -44, -45, -46, -47, -48, -49, -50, -51, -52 };
private int imageVersion = 0;
private final Map subtreeMap = new HashMap();
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/LsrPBImage.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/LsrPBImage.java
new file mode 100644
index 00000000000..e467725646e
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/LsrPBImage.java
@@ -0,0 +1,233 @@
+/**
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.permission.PermissionStatus;
+import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.BlockProto;
+import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
+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.FileSummary;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeDirectorySection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
+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;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeSymlink;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.StringTableSection;
+import org.apache.hadoop.hdfs.server.namenode.INodeId;
+import org.apache.hadoop.io.IOUtils;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.LimitInputStream;
+
+/**
+ * This is the tool for analyzing file sizes in the namespace image. In order to
+ * run the tool one should define a range of integers [0, maxSize] by
+ * specifying maxSize and a step. The range of integers is
+ * divided into segments of size step:
+ * [0, s1, ..., sn-1, maxSize], and the visitor
+ * calculates how many files in the system fall into each segment
+ * [si-1, si). Note that files larger than
+ * maxSize always fall into the very last segment.
+ *
+ * Input.
+ *
+ * - filename specifies the location of the image file;
+ * - maxSize determines the range [0, maxSize] of files
+ * sizes considered by the visitor;
+ * - step the range is divided into segments of size step.
+ *
+ *
+ * Output.
The output file is formatted as a tab separated two column
+ * table: Size and NumFiles. Where Size represents the start of the segment, and
+ * numFiles is the number of files form the image which size falls in this
+ * segment.
+ *
+ */
+final class LsrPBImage {
+ private final Configuration conf;
+ private final PrintWriter out;
+ private String[] stringTable;
+ private HashMap inodes = Maps.newHashMap();
+ private HashMap dirmap = Maps.newHashMap();
+
+ public LsrPBImage(Configuration conf, PrintWriter out) {
+ this.conf = conf;
+ this.out = out;
+ }
+
+ public void visit(RandomAccessFile file) throws IOException {
+ if (!FSImageUtil.checkFileFormat(file)) {
+ throw new IOException("Unrecognized FSImage");
+ }
+
+ FileSummary summary = FSImageUtil.loadSummary(file);
+ FileInputStream fin = null;
+ try {
+ fin = new FileInputStream(file.getFD());
+
+ ArrayList sections = Lists.newArrayList(summary
+ .getSectionsList());
+ Collections.sort(sections, new Comparator() {
+ @Override
+ public int compare(FileSummary.Section s1, FileSummary.Section s2) {
+ SectionName n1 = SectionName.fromString(s1.getName());
+ SectionName n2 = 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 (FileSummary.Section s : sections) {
+ fin.getChannel().position(s.getOffset());
+ InputStream is = FSImageUtil.wrapInputStreamForCompression(conf,
+ summary.getCodec(), new BufferedInputStream(new LimitInputStream(
+ fin, s.getLength())));
+
+ switch (SectionName.fromString(s.getName())) {
+ case STRING_TABLE:
+ loadStringTable(is);
+ break;
+ case INODE:
+ loadINodeSection(is);
+ break;
+ case INODE_DIR:
+ loadINodeDirectorySection(is);
+ break;
+ default:
+ break;
+ }
+ }
+ list("", INodeId.ROOT_INODE_ID);
+ } finally {
+ IOUtils.cleanup(null, fin);
+ }
+ }
+
+ private void list(String parent, long dirId) {
+ INode inode = inodes.get(dirId);
+ listINode(parent.isEmpty() ? "/" : parent, inode);
+ long[] children = dirmap.get(dirId);
+ if (children == null) {
+ return;
+ }
+ String newParent = parent + inode.getName().toStringUtf8() + "/";
+ for (long cid : children) {
+ list(newParent, cid);
+ }
+ }
+
+ private void listINode(String parent, INode inode) {
+ switch (inode.getType()) {
+ case FILE: {
+ INodeFile f = inode.getFile();
+ PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(
+ f.getPermission(), stringTable);
+ out.print(String.format("-%s %2s %8s %10s %10s %10d %s%s\n", p
+ .getPermission().toString(), f.getReplication(), p.getUserName(), p
+ .getGroupName(), f.getModificationTime(), getFileSize(f), parent,
+ inode.getName().toStringUtf8()));
+ }
+ break;
+ case DIRECTORY: {
+ INodeDirectory d = inode.getDirectory();
+ PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(
+ d.getPermission(), stringTable);
+ out.print(String.format("d%s - %8s %10s %10s %10d %s%s\n", p
+ .getPermission().toString(), p.getUserName(), p.getGroupName(), d
+ .getModificationTime(), 0, parent, inode.getName().toStringUtf8()));
+ }
+ break;
+ case SYMLINK: {
+ INodeSymlink d = inode.getSymlink();
+ PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(
+ d.getPermission(), stringTable);
+ out.print(String.format("-%s - %8s %10s %10s %10d %s%s -> %s\n", p
+ .getPermission().toString(), p.getUserName(), p.getGroupName(), 0, 0,
+ parent, inode.getName().toStringUtf8(), d.getTarget().toStringUtf8()));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private long getFileSize(INodeFile f) {
+ long size = 0;
+ for (BlockProto p : f.getBlocksList()) {
+ size += p.getNumBytes();
+ }
+ return size;
+ }
+
+ private void loadINodeDirectorySection(InputStream in) throws IOException {
+ while (true) {
+ INodeDirectorySection.DirEntry e = INodeDirectorySection.DirEntry
+ .parseDelimitedFrom(in);
+ // note that in is a LimitedInputStream
+ if (e == null) {
+ break;
+ }
+ long[] l = new long[e.getChildrenCount()];
+ for (int i = 0; i < l.length; ++i) {
+ l[i] = e.getChildren(i);
+ }
+ dirmap.put(e.getParent(), l);
+ for (int i = 0; i < e.getNumOfRef(); i++) {
+ INodeSection.INodeReference.parseDelimitedFrom(in);
+ }
+ }
+ }
+
+ private void loadINodeSection(InputStream in) throws IOException {
+ INodeSection s = INodeSection.parseDelimitedFrom(in);
+ for (int i = 0; i < s.getNumInodes(); ++i) {
+ INodeSection.INode p = INodeSection.INode.parseDelimitedFrom(in);
+ inodes.put(p.getId(), p);
+ }
+ }
+
+ private void loadStringTable(InputStream in) throws IOException {
+ StringTableSection s = StringTableSection.parseDelimitedFrom(in);
+ stringTable = new String[s.getNumEntry() + 1];
+ for (int i = 0; i < s.getNumEntry(); ++i) {
+ StringTableSection.Entry e = StringTableSection.Entry
+ .parseDelimitedFrom(in);
+ stringTable[e.getId()] = e.getStr();
+ }
+ }
+}
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
new file mode 100644
index 00000000000..2d8c42d39d1
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/OfflineImageViewerPB.java
@@ -0,0 +1,178 @@
+/**
+ * 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.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+
+/**
+ * OfflineImageViewer to dump the contents of an Hadoop image file to XML or the
+ * console. Main entry point into utility, either via the command line or
+ * programatically.
+ */
+@InterfaceAudience.Private
+public class OfflineImageViewerPB {
+ public static final Log LOG = LogFactory.getLog(OfflineImageViewerPB.class);
+
+ private final static String usage = "Usage: bin/hdfs oiv [OPTIONS] -i INPUTFILE -o OUTPUTFILE\n"
+ + "Offline Image Viewer\n"
+ + "View a Hadoop fsimage INPUTFILE using the specified PROCESSOR,\n"
+ + "saving the results in OUTPUTFILE.\n"
+ + "\n"
+ + "The oiv utility will attempt to parse correctly formed image files\n"
+ + "and will abort fail with mal-formed image files.\n"
+ + "\n"
+ + "The tool works offline and does not require a running cluster in\n"
+ + "order to process an image file.\n"
+ + "\n"
+ + "The following image processors are available:\n"
+ + " * Ls: The default image processor generates an lsr-style listing\n"
+ + " of the files in the namespace, with the same fields in the same\n"
+ + " order. Note that in order to correctly determine file sizes,\n"
+ + " this formatter cannot skip blocks and will override the\n"
+ + " -skipBlocks option.\n"
+ + " * XML: This processor creates an XML document with all elements of\n"
+ + " the fsimage enumerated, suitable for further analysis by XML\n"
+ + " tools.\n"
+ + " * FileDistribution: This processor analyzes the file size\n"
+ + " distribution in the image.\n"
+ + " -maxSize specifies the range [0, maxSize] of file sizes to be\n"
+ + " analyzed (128GB by default).\n"
+ + " -step defines the granularity of the distribution. (2MB by default)\n"
+ + "\n"
+ + "Required command line arguments:\n"
+ + "-i,--inputFile FSImage file to process.\n"
+ + "-o,--outputFile Name of output file. If the specified\n"
+ + " file exists, it will be overwritten.\n"
+ + "\n"
+ + "Optional command line arguments:\n"
+ + "-p,--processor Select which type of processor to apply\n"
+ + " against image file."
+ + " (Ls|XML|FileDistribution).\n"
+ + "-h,--help Display usage information and exit\n";
+
+ /**
+ * Build command-line options and descriptions
+ */
+ private static Options buildOptions() {
+ Options options = new Options();
+
+ // Build in/output file arguments, which are required, but there is no
+ // addOption method that can specify this
+ OptionBuilder.isRequired();
+ OptionBuilder.hasArgs();
+ OptionBuilder.withLongOpt("outputFile");
+ options.addOption(OptionBuilder.create("o"));
+
+ OptionBuilder.isRequired();
+ OptionBuilder.hasArgs();
+ OptionBuilder.withLongOpt("inputFile");
+ options.addOption(OptionBuilder.create("i"));
+
+ options.addOption("p", "processor", true, "");
+ options.addOption("h", "help", false, "");
+ options.addOption("skipBlocks", false, "");
+ options.addOption("printToScreen", false, "");
+ options.addOption("delimiter", true, "");
+
+ return options;
+ }
+
+ /**
+ * Entry point to command-line-driven operation. User may specify options and
+ * start fsimage viewer from the command line. Program will process image file
+ * and exit cleanly or, if an error is encountered, inform user and exit.
+ *
+ * @param args
+ * Command line options
+ * @throws IOException
+ */
+ public static void main(String[] args) throws IOException {
+ Options options = buildOptions();
+ if (args.length == 0) {
+ printUsage();
+ return;
+ }
+
+ CommandLineParser parser = new PosixParser();
+ CommandLine cmd;
+
+ try {
+ cmd = parser.parse(options, args);
+ } catch (ParseException e) {
+ System.out.println("Error parsing command-line options: ");
+ printUsage();
+ return;
+ }
+
+ if (cmd.hasOption("h")) { // print help and exit
+ printUsage();
+ return;
+ }
+
+ String inputFile = cmd.getOptionValue("i");
+ String processor = cmd.getOptionValue("p", "Ls");
+ String outputFile = cmd.getOptionValue("o");
+
+ PrintWriter out = (outputFile == null || outputFile.equals("-")) ? new PrintWriter(
+ System.out) : new PrintWriter(new File(outputFile));
+
+ Configuration conf = new Configuration();
+ try {
+ if (processor.equals("FileDistribution")) {
+ long maxSize = Long.parseLong(cmd.getOptionValue("maxSize", "0"));
+ int step = Integer.parseInt(cmd.getOptionValue("step", "0"));
+ new FileDistributionCalculator(conf, maxSize, step, out)
+ .visit(new RandomAccessFile(inputFile, "r"));
+ } else if (processor.equals("XML")) {
+ new PBImageXmlWriter(conf, out).visit(new RandomAccessFile(inputFile,
+ "r"));
+ } else {
+ new LsrPBImage(conf, out).visit(new RandomAccessFile(inputFile, "r"));
+ }
+ } catch (EOFException e) {
+ System.err.println("Input file ended unexpectedly. Exiting");
+ } catch (IOException e) {
+ System.err.println("Encountered exception. Exiting: " + e.getMessage());
+ } finally {
+ out.close();
+ }
+
+ }
+
+ /**
+ * Print application usage instructions.
+ */
+ private static void printUsage() {
+ System.out.println(usage);
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageXmlWriter.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageXmlWriter.java
new file mode 100644
index 00000000000..7ebf1196c4b
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/PBImageXmlWriter.java
@@ -0,0 +1,415 @@
+/**
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+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.HdfsProtos.BlockProto;
+import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
+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.CacheManagerSection;
+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.INodeDirectorySection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeDirectory;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeSymlink;
+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;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotSection;
+import org.apache.hadoop.hdfs.server.namenode.FsImageProto.StringTableSection;
+import org.apache.hadoop.io.IOUtils;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.LimitInputStream;
+
+/**
+ * This is the tool for analyzing file sizes in the namespace image. In order to
+ * run the tool one should define a range of integers [0, maxSize] by
+ * specifying maxSize and a step. The range of integers is
+ * divided into segments of size step:
+ * [0, s1, ..., sn-1, maxSize], and the visitor
+ * calculates how many files in the system fall into each segment
+ * [si-1, si). Note that files larger than
+ * maxSize always fall into the very last segment.
+ *
+ * Input.
+ *
+ * - filename specifies the location of the image file;
+ * - maxSize determines the range [0, maxSize] of files
+ * sizes considered by the visitor;
+ * - step the range is divided into segments of size step.
+ *
+ *
+ * Output.
The output file is formatted as a tab separated two column
+ * table: Size and NumFiles. Where Size represents the start of the segment, and
+ * numFiles is the number of files form the image which size falls in this
+ * segment.
+ *
+ */
+@InterfaceAudience.Private
+public final class PBImageXmlWriter {
+ private final Configuration conf;
+ private final PrintWriter out;
+ private String[] stringTable;
+
+ public PBImageXmlWriter(Configuration conf, PrintWriter out) {
+ this.conf = conf;
+ this.out = out;
+ }
+
+ public void visit(RandomAccessFile file) throws IOException {
+ if (!FSImageUtil.checkFileFormat(file)) {
+ throw new IOException("Unrecognized FSImage");
+ }
+
+ FileSummary summary = FSImageUtil.loadSummary(file);
+ FileInputStream fin = null;
+ try {
+ fin = new FileInputStream(file.getFD());
+ out.print("\n");
+
+ ArrayList sections = Lists.newArrayList(summary
+ .getSectionsList());
+ Collections.sort(sections, new Comparator() {
+ @Override
+ public int compare(FileSummary.Section s1, FileSummary.Section s2) {
+ SectionName n1 = SectionName.fromString(s1.getName());
+ SectionName n2 = 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 (FileSummary.Section s : sections) {
+ fin.getChannel().position(s.getOffset());
+ InputStream is = FSImageUtil.wrapInputStreamForCompression(conf,
+ summary.getCodec(), new BufferedInputStream(new LimitInputStream(
+ fin, s.getLength())));
+
+ switch (SectionName.fromString(s.getName())) {
+ case NS_INFO:
+ dumpNameSection(is);
+ break;
+ case STRING_TABLE:
+ loadStringTable(is);
+ break;
+ case INODE:
+ dumpINodeSection(is);
+ break;
+ case INODE_DIR:
+ dumpINodeDirectorySection(is);
+ break;
+ case FILES_UNDERCONSTRUCTION:
+ dumpFileUnderConstructionSection(is);
+ break;
+ case SNAPSHOT:
+ dumpSnapshotSection(is);
+ break;
+ case SNAPSHOT_DIFF:
+ dumpSnapshotDiffSection(is);
+ break;
+ case SECRET_MANAGER:
+ dumpSecretManagerSection(is);
+ break;
+ case CACHE_MANAGER:
+ dumpCacheManagerSection(is);
+ break;
+ default:
+ break;
+ }
+ }
+ } finally {
+ IOUtils.cleanup(null, fin);
+ }
+ }
+
+ private void dumpCacheManagerSection(InputStream is) throws IOException {
+ out.print("");
+ CacheManagerSection s = CacheManagerSection.parseDelimitedFrom(is);
+ o("nextDirectiveId", s.getNextDirectiveId());
+ for (int i = 0; i < s.getNumPools(); ++i) {
+ CachePoolInfoProto p = CachePoolInfoProto.parseDelimitedFrom(is);
+ out.print("");
+ o("poolName", p.getPoolName()).o("ownerName", p.getOwnerName())
+ .o("groupName", p.getGroupName()).o("mode", p.getMode())
+ .o("limit", p.getLimit())
+ .o("maxRelativeExpiry", p.getMaxRelativeExpiry());
+ out.print("\n");
+ }
+ for (int i = 0; i < s.getNumPools(); ++i) {
+ CacheDirectiveInfoProto p = CacheDirectiveInfoProto
+ .parseDelimitedFrom(is);
+ out.print("");
+ o("id", p.getId()).o("path", p.getPath())
+ .o("replication", p.getReplication()).o("pool", p.getPool());
+ out.print("");
+ CacheDirectiveInfoExpirationProto e = p.getExpiration();
+ o("millis", e.getMillis()).o("relatilve", e.getIsRelative());
+ out.print("\n");
+ out.print("\n");
+ }
+ out.print("\n");
+
+ }
+
+ private void dumpFileUnderConstructionSection(InputStream in)
+ throws IOException {
+ out.print("");
+ while (true) {
+ FileUnderConstructionEntry e = FileUnderConstructionEntry
+ .parseDelimitedFrom(in);
+ if (e == null) {
+ break;
+ }
+ out.print("");
+ o("id", e.getInodeId()).o("path", e.getFullPath());
+ out.print("\n");
+ }
+ out.print("\n");
+ }
+
+ private void dumpINodeDirectory(INodeDirectory d) {
+ o("mtime", d.getModificationTime()).o("permission",
+ dumpPermission(d.getPermission()));
+
+ if (d.hasDsQuota() && d.hasNsQuota()) {
+ o("nsquota", d.getNsQuota()).o("dsquota", d.getDsQuota());
+ }
+ }
+
+ private void dumpINodeDirectorySection(InputStream in) throws IOException {
+ out.print("");
+ while (true) {
+ INodeDirectorySection.DirEntry e = INodeDirectorySection.DirEntry
+ .parseDelimitedFrom(in);
+ // note that in is a LimitedInputStream
+ if (e == null) {
+ break;
+ }
+ out.print("");
+ o("parent", e.getParent());
+ for (long id : e.getChildrenList()) {
+ o("inode", id);
+ }
+ for (int i = 0; i < e.getNumOfRef(); i++) {
+ INodeSection.INodeReference r = INodeSection.INodeReference
+ .parseDelimitedFrom(in);
+ dumpINodeReference(r);
+
+ }
+ out.print("\n");
+ }
+ out.print("\n");
+ }
+
+ private void dumpINodeReference(INodeSection.INodeReference r) {
+ out.print("[");
+ o("referredId", r.getReferredId()).o("name", r.getName().toStringUtf8())
+ .o("dstSnapshotId", r.getDstSnapshotId())
+ .o("lastSnapshotId", r.getLastSnapshotId());
+ out.print("]\n");
+ }
+
+ private void dumpINodeFile(INodeSection.INodeFile f) {
+ o("replication", f.getReplication()).o("mtime", f.getModificationTime())
+ .o("atime", f.getAccessTime())
+ .o("perferredBlockSize", f.getPreferredBlockSize())
+ .o("permission", dumpPermission(f.getPermission()));
+
+ if (f.getBlocksCount() > 0) {
+ out.print("");
+ for (BlockProto b : f.getBlocksList()) {
+ out.print("");
+ o("id", b.getBlockId()).o("genstamp", b.getGenStamp()).o("numBytes",
+ b.getNumBytes());
+ out.print("\n");
+ }
+ out.print("\n");
+ }
+
+ if (f.hasFileUC()) {
+ INodeSection.FileUnderConstructionFeature u = f.getFileUC();
+ out.print("");
+ o("clientName", u.getClientName()).o("clientMachine",
+ u.getClientMachine());
+ out.print("\n");
+ }
+ }
+
+ private void dumpINodeSection(InputStream in) throws IOException {
+ INodeSection s = INodeSection.parseDelimitedFrom(in);
+ out.print("");
+ o("lastInodeId", s.getLastInodeId());
+ for (int i = 0; i < s.getNumInodes(); ++i) {
+ INodeSection.INode p = INodeSection.INode.parseDelimitedFrom(in);
+ out.print("");
+ o("id", p.getId()).o("type", p.getType()).o("name",
+ p.getName().toStringUtf8());
+
+ if (p.hasFile()) {
+ dumpINodeFile(p.getFile());
+ } else if (p.hasDirectory()) {
+ dumpINodeDirectory(p.getDirectory());
+ } else if (p.hasSymlink()) {
+ dumpINodeSymlink(p.getSymlink());
+ }
+
+ out.print("\n");
+ }
+ out.print("\n");
+ }
+
+ private void dumpINodeSymlink(INodeSymlink s) {
+ o("permission", dumpPermission(s.getPermission())).o("target",
+ s.getTarget().toStringUtf8());
+ }
+
+ private void dumpNameSection(InputStream in) throws IOException {
+ NameSystemSection s = NameSystemSection.parseDelimitedFrom(in);
+ out.print("\n");
+ o("genstampV1", s.getGenstampV1()).o("genstampV2", s.getGenstampV2())
+ .o("genstampV1Limit", s.getGenstampV1Limit())
+ .o("lastAllocatedBlockId", s.getLastAllocatedBlockId())
+ .o("txid", s.getTransactionId());
+ out.print("\n");
+ }
+
+ private String dumpPermission(long permission) {
+ return FSImageFormatPBINode.Loader.loadPermission(permission, stringTable)
+ .toString();
+ }
+
+ private void dumpSecretManagerSection(InputStream is) throws IOException {
+ out.print("");
+ SecretManagerSection s = SecretManagerSection.parseDelimitedFrom(is);
+ o("currentId", s.getCurrentId()).o("tokenSequenceNumber",
+ s.getTokenSequenceNumber());
+ out.print("");
+ }
+
+ private void dumpSnapshotDiffSection(InputStream in) throws IOException {
+ out.print("");
+ while (true) {
+ SnapshotDiffSection.DiffEntry e = SnapshotDiffSection.DiffEntry
+ .parseDelimitedFrom(in);
+ if (e == null) {
+ break;
+ }
+ out.print("");
+ o("inodeid", e.getInodeId());
+ switch (e.getType()) {
+ case FILEDIFF: {
+ for (int i = 0; i < e.getNumOfDiff(); ++i) {
+ out.print("");
+ SnapshotDiffSection.FileDiff f = SnapshotDiffSection.FileDiff
+ .parseDelimitedFrom(in);
+ o("snapshotId", f.getSnapshotId()).o("size", f.getFileSize()).o(
+ "name", f.getName().toStringUtf8());
+ out.print("\n");
+ }
+ }
+ break;
+ case DIRECTORYDIFF: {
+ for (int i = 0; i < e.getNumOfDiff(); ++i) {
+ out.print("");
+ SnapshotDiffSection.DirectoryDiff d = SnapshotDiffSection.DirectoryDiff
+ .parseDelimitedFrom(in);
+ o("snapshotId", d.getSnapshotId())
+ .o("isSnapshotroot", d.getIsSnapshotRoot())
+ .o("childrenSize", d.getChildrenSize())
+ .o("name", d.getName().toStringUtf8());
+
+ for (int j = 0; j < d.getCreatedListSize(); ++j) {
+ SnapshotDiffSection.CreatedListEntry ce = SnapshotDiffSection.CreatedListEntry
+ .parseDelimitedFrom(in);
+ out.print("");
+ o("name", ce.getName().toStringUtf8());
+ out.print("\n");
+ }
+ for (int j = 0; j < d.getNumOfDeletedRef(); ++j) {
+ INodeSection.INodeReference r = INodeSection.INodeReference
+ .parseDelimitedFrom(in);
+ dumpINodeReference(r);
+ }
+ out.print("\n");
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ out.print("");
+ }
+ out.print("\n");
+ }
+
+ private void dumpSnapshotSection(InputStream in) throws IOException {
+ out.print("");
+ SnapshotSection s = SnapshotSection.parseDelimitedFrom(in);
+ o("snapshotCounter", s.getSnapshotCounter());
+ if (s.getSnapshottableDirCount() > 0) {
+ out.print("");
+ for (long id : s.getSnapshottableDirList()) {
+ o("dir", id);
+ }
+ out.print("\n");
+ }
+ for (int i = 0; i < s.getNumSnapshots(); ++i) {
+ SnapshotSection.Snapshot pbs = SnapshotSection.Snapshot
+ .parseDelimitedFrom(in);
+ o("snapshot", pbs.getSnapshotId());
+ }
+ out.print("\n");
+ }
+
+ private void loadStringTable(InputStream in) throws IOException {
+ StringTableSection s = StringTableSection.parseDelimitedFrom(in);
+ stringTable = new String[s.getNumEntry() + 1];
+ for (int i = 0; i < s.getNumEntry(); ++i) {
+ StringTableSection.Entry e = StringTableSection.Entry
+ .parseDelimitedFrom(in);
+ stringTable[e.getId()] = e.getStr();
+ }
+ }
+
+ private PBImageXmlWriter o(final String e, final Object v) {
+ out.print("<" + e + ">" + v + "" + e + ">");
+ return this;
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/proto/fsimage.proto b/hadoop-hdfs-project/hadoop-hdfs/src/main/proto/fsimage.proto
new file mode 100644
index 00000000000..af7ba874d29
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/proto/fsimage.proto
@@ -0,0 +1,280 @@
+/**
+ * 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.
+ */
+
+option java_package = "org.apache.hadoop.hdfs.server.namenode";
+option java_outer_classname = "FsImageProto";
+
+package hadoop.hdfs.fsimage;
+
+import "hdfs.proto";
+
+/**
+ * This file defines the on-disk layout of the file system image. The
+ * layout is defined by the following EBNF grammar, in which angle
+ * brackets mark protobuf definitions. (e.g., )
+ *
+ * FILE := MAGIC SECTION* FileSummaryLength
+ * MAGIC := 'HDFSIMG1'
+ * SECTION := | ...
+ * FileSummaryLength := 4 byte int
+ *
+ * Some notes:
+ *
+ * The codec field in FileSummary describes the compression codec used
+ * for all sections. The fileheader is always uncompressed.
+ *
+ * All protobuf messages are serialized in delimited form, which means
+ * that there always will be an integer indicates the size of the
+ * protobuf message.
+ *
+ */
+
+message FileSummary {
+ // The version of the above EBNF grammars.
+ required uint32 ondiskVersion = 1;
+ // layoutVersion describes which features are available in the
+ // FSImage.
+ required uint32 layoutVersion = 2;
+ optional string codec = 3;
+ // index for each section
+ message Section {
+ optional string name = 1;
+ optional uint64 length = 2;
+ optional uint64 offset = 3;
+ }
+ repeated Section sections = 4;
+}
+
+/**
+ * Name: NS_INFO
+ */
+message NameSystemSection {
+ optional uint32 namespaceId = 1;
+ optional uint64 genstampV1 = 2;
+ optional uint64 genstampV2 = 3;
+ optional uint64 genstampV1Limit = 4;
+ optional uint64 lastAllocatedBlockId = 5;
+ optional uint64 transactionId = 6;
+}
+
+/**
+ * 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.
+ *
+ * Name: INODE
+ */
+message INodeSection {
+ /**
+ * under-construction feature for INodeFile
+ */
+ message FileUnderConstructionFeature {
+ optional string clientName = 1;
+ optional string clientMachine = 2;
+ }
+
+ message INodeFile {
+ optional uint32 replication = 1;
+ optional uint64 modificationTime = 2;
+ optional uint64 accessTime = 3;
+ optional uint64 preferredBlockSize = 4;
+ optional fixed64 permission = 5;
+ repeated BlockProto blocks = 6;
+ optional FileUnderConstructionFeature fileUC = 7;
+ }
+
+ message INodeDirectory {
+ optional uint64 modificationTime = 1;
+ // namespace quota
+ optional uint64 nsQuota = 2;
+ // diskspace quota
+ optional uint64 dsQuota = 3;
+ optional fixed64 permission = 4;
+ }
+
+ message INodeSymlink {
+ optional fixed64 permission = 1;
+ optional bytes target = 2;
+ }
+
+ message INodeReference {
+ // id of the referred inode
+ optional uint64 referredId = 1;
+ // local name recorded in WithName
+ optional bytes name = 2;
+ // recorded in DstReference
+ optional uint32 dstSnapshotId = 3;
+ // recorded in WithName
+ optional uint32 lastSnapshotId = 4;
+ }
+
+ message INode {
+ enum Type {
+ FILE = 1;
+ DIRECTORY = 2;
+ SYMLINK = 3;
+ };
+ required Type type = 1;
+ required uint64 id = 2;
+ optional bytes name = 3;
+
+ optional INodeFile file = 4;
+ optional INodeDirectory directory = 5;
+ optional INodeSymlink symlink = 6;
+ }
+
+ optional uint64 lastInodeId = 1;
+ optional uint64 numInodes = 2;
+ // repeated INodes..
+}
+
+/**
+ * This section records information about under-construction files for
+ * reconstructing the lease map.
+ * NAME: FILES_UNDERCONSTRUCTION
+ */
+message FilesUnderConstructionSection {
+ message FileUnderConstructionEntry {
+ optional uint64 inodeId = 1;
+ optional string fullPath = 2;
+ }
+ // repeated FileUnderConstructionEntry...
+}
+
+/**
+ * This section records the children of each directories
+ * NAME: INODE_DIR
+ */
+message INodeDirectorySection {
+ message DirEntry {
+ optional uint64 parent = 1;
+ repeated uint64 children = 2 [packed = true];
+ optional uint64 numOfRef = 3;
+ // repeated INodeReference...
+ }
+ // repeated DirEntry, ended at the boundary of the section.
+}
+
+/**
+ * This section records the information about snapshot
+ * NAME: SNAPSHOT
+ */
+message SnapshotSection {
+ message Snapshot {
+ optional uint32 snapshotId = 1;
+ // Snapshot root
+ optional INodeSection.INode root = 2;
+ }
+
+ optional uint32 snapshotCounter = 1;
+ repeated uint64 snapshottableDir = 2 [packed = true];
+ // total number of snapshots
+ optional uint32 numSnapshots = 3;
+ // repeated Snapshot...
+}
+
+/**
+ * This section records information about snapshot diffs
+ * NAME: SNAPSHOT_DIFF
+ */
+message SnapshotDiffSection {
+ message CreatedListEntry {
+ optional bytes name = 1;
+ }
+
+ message DirectoryDiff {
+ optional uint32 snapshotId = 1;
+ optional uint32 childrenSize = 2;
+ optional bool isSnapshotRoot = 3;
+ optional bytes name = 4;
+ optional INodeSection.INodeDirectory snapshotCopy = 5;
+ optional uint32 createdListSize = 6;
+ optional uint32 numOfDeletedRef = 7; // number of reference nodes in deleted list
+ repeated uint64 deletedINode = 8 [packed = true]; // id of deleted inode
+ // repeated CreatedListEntry (size is specified by createdListSize)
+ // repeated INodeReference (reference inodes in deleted list)
+ }
+
+ message FileDiff {
+ optional uint32 snapshotId = 1;
+ optional uint64 fileSize = 2;
+ optional bytes name = 3;
+ optional INodeSection.INodeFile snapshotCopy = 4;
+ }
+
+ message DiffEntry {
+ enum Type {
+ FILEDIFF = 1;
+ DIRECTORYDIFF = 2;
+ }
+ required Type type = 1;
+ optional uint64 inodeId = 2;
+ optional uint32 numOfDiff = 3;
+
+ // repeated DirectoryDiff or FileDiff
+ }
+
+ // repeated DiffEntry
+}
+
+/**
+ * This section maps string to id
+ * NAME: STRING_TABLE
+ */
+message StringTableSection {
+ message Entry {
+ optional uint32 id = 1;
+ optional string str = 2;
+ }
+ optional uint32 numEntry = 1;
+ // repeated Entry
+}
+
+message SecretManagerSection {
+ message DelegationKey {
+ optional uint32 id = 1;
+ optional uint64 expiryDate = 2;
+ optional bytes key = 3;
+ }
+ message PersistToken {
+ optional uint32 version = 1;
+ optional string owner = 2;
+ optional string renewer = 3;
+ optional string realUser = 4;
+ optional uint64 issueDate = 5;
+ optional uint64 maxDate = 6;
+ optional uint32 sequenceNumber = 7;
+ optional uint32 masterKeyId = 8;
+ optional uint64 expiryDate = 9;
+ }
+ optional uint32 currentId = 1;
+ optional uint32 tokenSequenceNumber = 2;
+ optional uint32 numKeys = 3;
+ optional uint32 numTokens = 4;
+ // repeated DelegationKey keys
+ // repeated PersistToken tokens
+}
+
+message CacheManagerSection {
+ required uint64 nextDirectiveId = 1;
+ required uint32 numPools = 2;
+ required uint32 numDirectives = 3;
+ // repeated CachePoolInfoProto pools
+ // repeated CacheDirectiveInfoProto directives
+}
+
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImage.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImage.java
new file mode 100644
index 00000000000..552b091b7b4
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImage.java
@@ -0,0 +1,138 @@
+/**
+ * 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.server.namenode;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.EnumSet;
+
+import junit.framework.Assert;
+
+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.DFSOutputStream;
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.client.HdfsDataOutputStream.SyncFlag;
+import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction;
+import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo;
+import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.BlockUCState;
+import org.apache.hadoop.hdfs.server.namenode.LeaseManager.Lease;
+import org.apache.hadoop.hdfs.util.MD5FileUtils;
+import org.junit.Test;
+
+public class TestFSImage {
+
+ @Test
+ public void testPersist() throws IOException {
+ Configuration conf = new Configuration();
+ testPersistHelper(conf);
+ }
+
+ @Test
+ public void testCompression() throws IOException {
+ Configuration conf = new Configuration();
+ conf.setBoolean(DFSConfigKeys.DFS_IMAGE_COMPRESS_KEY, true);
+ conf.set(DFSConfigKeys.DFS_IMAGE_COMPRESSION_CODEC_KEY,
+ "org.apache.hadoop.io.compress.GzipCodec");
+ testPersistHelper(conf);
+ }
+
+ private void testPersistHelper(Configuration conf) throws IOException {
+ MiniDFSCluster cluster = null;
+ try {
+ cluster = new MiniDFSCluster.Builder(conf).build();
+ cluster.waitActive();
+ FSNamesystem fsn = cluster.getNamesystem();
+ DistributedFileSystem fs = cluster.getFileSystem();
+
+ final Path dir = new Path("/abc/def");
+ final Path file1 = new Path(dir, "f1");
+ final Path file2 = new Path(dir, "f2");
+
+ // create an empty file f1
+ fs.create(file1).close();
+
+ // create an under-construction file f2
+ FSDataOutputStream out = fs.create(file2);
+ out.writeBytes("hello");
+ ((DFSOutputStream) out.getWrappedStream()).hsync(EnumSet
+ .of(SyncFlag.UPDATE_LENGTH));
+
+ // checkpoint
+ fs.setSafeMode(SafeModeAction.SAFEMODE_ENTER);
+ fs.saveNamespace();
+ fs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE);
+
+ cluster.restartNameNode();
+ cluster.waitActive();
+ fs = cluster.getFileSystem();
+
+ assertTrue(fs.isDirectory(dir));
+ assertTrue(fs.exists(file1));
+ assertTrue(fs.exists(file2));
+
+ // check internals of file2
+ INodeFile file2Node = fsn.dir.getINode4Write(file2.toString()).asFile();
+ assertEquals("hello".length(), file2Node.computeFileSize());
+ assertTrue(file2Node.isUnderConstruction());
+ BlockInfo[] blks = file2Node.getBlocks();
+ assertEquals(1, blks.length);
+ assertEquals(BlockUCState.UNDER_CONSTRUCTION, blks[0].getBlockUCState());
+ // check lease manager
+ Lease lease = fsn.leaseManager.getLeaseByPath(file2.toString());
+ Assert.assertNotNull(lease);
+ } finally {
+ if (cluster != null) {
+ cluster.shutdown();
+ }
+ }
+ }
+
+ /**
+ * Ensure that the digest written by the saver equals to the digest of the
+ * file.
+ */
+ @Test
+ public void testDigest() throws IOException {
+ Configuration conf = new Configuration();
+ MiniDFSCluster cluster = null;
+ try {
+ cluster = new MiniDFSCluster.Builder(conf).numDataNodes(0).build();
+ DistributedFileSystem fs = cluster.getFileSystem();
+ fs.setSafeMode(SafeModeAction.SAFEMODE_ENTER);
+ fs.saveNamespace();
+ fs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE);
+ File currentDir = FSImageTestUtil.getNameNodeCurrentDirs(cluster, 0).get(
+ 0);
+ File fsimage = FSImageTestUtil.findNewestImageFile(currentDir
+ .getAbsolutePath());
+ assertEquals(MD5FileUtils.readStoredMd5ForFile(fsimage),
+ MD5FileUtils.computeMd5ForFile(fsimage));
+ } finally {
+ if (cluster != null) {
+ cluster.shutdown();
+ }
+ }
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithSnapshot.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithSnapshot.java
index 21935d05d9c..f3cbf15aae2 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithSnapshot.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithSnapshot.java
@@ -140,7 +140,7 @@ public class TestFSImageWithSnapshot {
private File saveFSImageToTempFile() throws IOException {
SaveNamespaceContext context = new SaveNamespaceContext(fsn, txid,
new Canceler());
- FSImageFormat.Saver saver = new FSImageFormat.Saver(context);
+ FSImageFormatProtobuf.Saver saver = new FSImageFormatProtobuf.Saver(context);
FSImageCompression compression = FSImageCompression.createCompression(conf);
File imageFile = getImageFile(testDir, txid);
fsn.readLock();
@@ -154,7 +154,7 @@ public class TestFSImageWithSnapshot {
/** Load the fsimage from a temp file */
private void loadFSImageFromTempFile(File imageFile) throws IOException {
- FSImageFormat.Loader loader = new FSImageFormat.Loader(conf, fsn);
+ FSImageFormat.LoaderDelegator loader = FSImageFormat.newLoader(conf, fsn);
fsn.writeLock();
fsn.getFSDirectory().writeLock();
try {
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestStandbyCheckpoints.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestStandbyCheckpoints.java
index 3ff5d54dc66..0ca112da5c3 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestStandbyCheckpoints.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestStandbyCheckpoints.java
@@ -287,7 +287,6 @@ public class TestStandbyCheckpoints {
doEdits(0, 1000);
nn0.getRpcServer().rollEditLog();
answerer.waitForCall();
- answerer.proceed();
assertTrue("SBN is not performing checkpoint but it should be.",
answerer.getFireCount() == 1 && answerer.getResultCount() == 0);
@@ -306,6 +305,7 @@ public class TestStandbyCheckpoints {
// RPC to the SBN happened during the checkpoint.
assertTrue("SBN should have still been checkpointing.",
answerer.getFireCount() == 1 && answerer.getResultCount() == 0);
+ answerer.proceed();
answerer.waitForResult();
assertTrue("SBN should have finished checkpointing.",
answerer.getFireCount() == 1 && answerer.getResultCount() == 1);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestRenameWithSnapshots.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestRenameWithSnapshots.java
index 7fe8087f2a4..d4e887949e0 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestRenameWithSnapshots.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestRenameWithSnapshots.java
@@ -73,7 +73,6 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
-;
/** Testing rename with snapshots. */
public class TestRenameWithSnapshots {
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshot.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshot.java
index 27228bd0482..20cc1351e8d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshot.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshot.java
@@ -25,6 +25,9 @@ import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
@@ -53,8 +56,7 @@ import org.apache.hadoop.hdfs.server.namenode.INode;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotTestHelper.TestDirectoryTree;
import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotTestHelper.TestDirectoryTree.Node;
-import org.apache.hadoop.hdfs.tools.offlineImageViewer.OfflineImageViewer;
-import org.apache.hadoop.hdfs.tools.offlineImageViewer.XmlImageVisitor;
+import org.apache.hadoop.hdfs.tools.offlineImageViewer.PBImageXmlWriter;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
@@ -245,8 +247,8 @@ public class TestSnapshot {
* snapshots
*/
@Test
- public void testOfflineImageViewer() throws Throwable {
- runTestSnapshot(SNAPSHOT_ITERATION_NUMBER);
+ public void testOfflineImageViewer() throws Exception {
+ runTestSnapshot(1);
// retrieve the fsimage. Note that we already save namespace to fsimage at
// the end of each iteration of runTestSnapshot.
@@ -254,31 +256,10 @@ public class TestSnapshot {
FSImageTestUtil.getFSImage(
cluster.getNameNode()).getStorage().getStorageDir(0));
assertNotNull("Didn't generate or can't find fsimage", originalFsimage);
-
- String ROOT = System.getProperty("test.build.data", "build/test/data");
- File testFile = new File(ROOT, "/image");
- String xmlImage = ROOT + "/image_xml";
- boolean success = false;
-
- try {
- DFSTestUtil.copyFile(originalFsimage, testFile);
- XmlImageVisitor v = new XmlImageVisitor(xmlImage, true);
- OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v,
- true);
- oiv.go();
- success = true;
- } finally {
- if (testFile.exists()) {
- testFile.delete();
- }
- // delete the xml file if the parsing is successful
- if (success) {
- File xmlImageFile = new File(xmlImage);
- if (xmlImageFile.exists()) {
- xmlImageFile.delete();
- }
- }
- }
+ StringWriter output = new StringWriter();
+ PrintWriter o = new PrintWriter(output);
+ PBImageXmlWriter v = new PBImageXmlWriter(new Configuration(), o);
+ v.visit(new RandomAccessFile(originalFsimage, "r"));
}
private void runTestSnapshot(int iteration) throws Exception {
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewer.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewer.java
index 11aa3b821f0..91a5c1521c7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewer.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/tools/offlineImageViewer/TestOfflineImageViewer.java
@@ -20,23 +20,20 @@ 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.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.io.StringWriter;
import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -46,27 +43,29 @@ 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.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction;
import org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil;
+import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.test.PathUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
-
+import org.junit.rules.TemporaryFolder;
/**
- * 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.
+ * 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);
@@ -76,22 +75,22 @@ public class TestOfflineImageViewer {
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
+ private static final class LsElements {
+ private String perms;
+ private int replication;
+ private String username;
+ private String groupname;
+ private long filesize;
+ private boolean isDir;
}
-
+
// namespace as written to dfs, to be compared with viewer's output
- final static HashMap writtenFiles =
- new HashMap();
-
- private static String ROOT = PathUtils.getTestDirName(TestOfflineImageViewer.class);
-
- // Create a populated namespace for later testing. Save its contents to a
+ final static HashMap writtenFiles = new HashMap();
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ // 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.
@@ -100,35 +99,39 @@ public class TestOfflineImageViewer {
MiniDFSCluster cluster = null;
try {
Configuration conf = new HdfsConfiguration();
- 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.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(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL,
"RULE:[2:$1@$0](JobTracker@.*FOO.COM)s/@.*//" + "DEFAULT");
cluster = new MiniDFSCluster.Builder(conf).numDataNodes(4).build();
cluster.waitActive();
FileSystem hdfs = cluster.getFileSystem();
-
+
int filesize = 256;
-
- // Create a reasonable namespace
- for(int i = 0; i < NUM_DIRS; i++) {
+
+ // 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++) {
+ 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.write(new byte[filesize++]);
o.close();
-
- writtenFiles.put(file.toString(), pathToFileEntry(hdfs, file.toString()));
+
+ writtenFiles.put(file.toString(),
+ pathToFileEntry(hdfs, file.toString()));
}
}
// Get delegation tokens so we log the delegation token op
- Token>[] delegationTokens =
- hdfs.addDelegationTokens(TEST_RENEWER, null);
+ Token>[] delegationTokens = hdfs
+ .addDelegationTokens(TEST_RENEWER, null);
for (Token> t : delegationTokens) {
LOG.debug("got token " + t);
}
@@ -137,329 +140,113 @@ public class TestOfflineImageViewer {
cluster.getNameNodeRpc()
.setSafeMode(SafeModeAction.SAFEMODE_ENTER, false);
cluster.getNameNodeRpc().saveNamespace();
-
+
// Determine location of fsimage file
- originalFsimage = FSImageTestUtil.findLatestImageFile(
- FSImageTestUtil.getFSImage(
- cluster.getNameNode()).getStorage().getStorageDir(0));
+ 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)
+ if (cluster != null)
cluster.shutdown();
}
}
-
+
@AfterClass
public static void deleteOriginalFSImage() throws IOException {
- if(originalFsimage != null && originalFsimage.exists()) {
+ if (originalFsimage != null && originalFsimage.exists()) {
originalFsimage.delete();
}
}
-
- // Convenience method to generate a file status from file system for
+
+ // Convenience method to generate a file status from file system for
// later comparison
- private static FileStatus pathToFileEntry(FileSystem hdfs, String file)
- throws IOException {
+ 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
+
+ // 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 {
- DFSTestUtil.copyFile(originalFsimage, testFile);
-
- ImageVisitor v = new LsImageVisitor(outputFile.getPath(), true);
- OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, false);
-
- oiv.go();
-
- HashMap 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.");
+ StringWriter output = new StringWriter();
+ PrintWriter out = new PrintWriter(output);
+ LsrPBImage v = new LsrPBImage(new Configuration(), out);
+ v.visit(new RandomAccessFile(originalFsimage, "r"));
+ out.close();
+ Pattern pattern = Pattern
+ .compile("([d\\-])([rwx\\-]{9})\\s*(-|\\d+)\\s*(\\w+)\\s*(\\w+)\\s*(\\d+)\\s*(\\d+)\\s*([\b/]+)");
+ int count = 0;
+ for (String s : output.toString().split("\n")) {
+ Matcher m = pattern.matcher(s);
+ assertTrue(m.find());
+ LsElements e = new LsElements();
+ e.isDir = m.group(1).equals("d");
+ e.perms = m.group(2);
+ e.replication = m.group(3).equals("-") ? 0 : Integer.parseInt(m.group(3));
+ e.username = m.group(4);
+ e.groupname = m.group(5);
+ e.filesize = Long.parseLong(m.group(7));
+ String path = m.group(8);
+ if (!path.equals("/")) {
+ compareFiles(writtenFiles.get(path), e);
}
- } finally {
- if(testFile.exists()) testFile.delete();
- if(outputFile.exists()) outputFile.delete();
+ ++count;
}
+ assertEquals(writtenFiles.size() + 1, count);
}
-
- // 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(expected = IOException.class)
+ public void testTruncatedFSImage() throws IOException {
+ File truncatedFile = folder.newFile();
+ StringWriter output = new StringWriter();
+ copyPartOfFile(originalFsimage, truncatedFile);
+ new FileDistributionCalculator(new Configuration(), 0, 0, new PrintWriter(
+ output)).visit(new RandomAccessFile(truncatedFile, "r"));
}
-
- // Test that our ls file has all the same compenents of the original namespace
- private void compareNamespaces(HashMap written,
- HashMap 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 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.isDirectory() ? 'd' : '-', elements.dir);
- assertEquals("perms string equal",
- fs.getPermission().toString(), elements.perms);
+ assertEquals("directory listed as such", fs.isDirectory(), elements.isDir);
+ 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 readLsfile(File lsFile) throws IOException {
- BufferedReader br = new BufferedReader(new FileReader(lsFile));
- String line = null;
- HashMap fileContents = new HashMap();
-
- while((line = br.readLine()) != null)
- readLsLine(line, fileContents);
-
- br.close();
- 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 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;
-
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ final int MAX_BYTES = 700;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
-
- while( (count = in.read(b)) > 0 && bytesWritten < maxBytes ) {
- out.write(b);
- bytesWritten += count;
- }
+ in.getChannel().transferTo(0, MAX_BYTES, out.getChannel());
} finally {
- if(in != null) in.close();
- if(out != null) out.close();
+ IOUtils.cleanup(null, in);
+ IOUtils.cleanup(null, out);
}
}
@Test
- public void outputOfFileDistributionVisitor() throws IOException {
- File testFile = new File(ROOT, "/basicCheck");
- File outputFile = new File(ROOT, "/fileDistributionCheckOutput");
+ public void testFileDistributionVisitor() throws IOException {
+ StringWriter output = new StringWriter();
+ PrintWriter o = new PrintWriter(output);
+ new FileDistributionCalculator(new Configuration(), 0, 0, o)
+ .visit(new RandomAccessFile(originalFsimage, "r"));
+ o.close();
- int totalFiles = 0;
- BufferedReader reader = null;
- try {
- DFSTestUtil.copyFile(originalFsimage, testFile);
- ImageVisitor v = new FileDistributionVisitor(outputFile.getPath(), 0, 0);
- OfflineImageViewer oiv =
- new OfflineImageViewer(testFile.getPath(), v, false);
+ Pattern p = Pattern.compile("totalFiles = (\\d+)\n");
+ Matcher matcher = p.matcher(output.getBuffer());
- oiv.go();
-
- 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 (reader != null) {
- reader.close();
- }
- if(testFile.exists()) testFile.delete();
- if(outputFile.exists()) outputFile.delete();
- }
+ assertTrue(matcher.find() && matcher.groupCount() == 1);
+ int totalFiles = Integer.parseInt(matcher.group(1));
assertEquals(totalFiles, NUM_DIRS * FILES_PER_DIR);
}
-
- private static class TestImageVisitor extends ImageVisitor {
- private List delegationTokenRenewers = new LinkedList();
- TestImageVisitor() {
- }
-
- List 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 {
- DFSTestUtil.copyFile(originalFsimage, testFile);
- TestImageVisitor v = new TestImageVisitor();
- OfflineImageViewer oiv = new OfflineImageViewer(testFile.getPath(), v, true);
- oiv.go();
-
- // Validated stored delegation token identifiers.
- List dtrs = v.getDelegationTokenRenewers();
- assertEquals(1, dtrs.size());
- assertEquals(TEST_RENEWER, dtrs.get(0));
- } finally {
- if(testFile.exists()) testFile.delete();
- }
- LOG.debug("Passed TestVisitor validation.");
- }
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored
index c6174327d11..a3f3511c9eb 100644
Binary files a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored and b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored differ
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored.xml b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored.xml
index 3a60b6dc5c5..c7fafcccf5e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/editsStored.xml
@@ -1,6 +1,6 @@
- -51
+ -52
OP_START_LOG_SEGMENT